IO

The JNIOR Control Panel is shipped with a ‘click’ sound that plays when a switch is pressed. This gives immediate feedback to the user that the panel has power and that the software is running normally and is ready to accept the press. There are times when the panel is used in a situation where the ‘click’ may not want to be heard. In this situation we may want to turn off that sound. The following application will accomplish this goal.


To run this application you need to download it from the button and load it on your JNIOR. Applications are generally loaded in the flash directory. To run it simply type the command as you see it above.

ControlPanel Applicaiton - 7 KB - MD5: c573b4c989ea0380729fecae5283e937

In the following code you will notice two classes. The main class of interest here is the ControlPanel class. This class will load the arguments into a ParameterGroup class so that we can process different commands at some point as well as there specific options. For now our command line is pretty simple but we set this application up for the future.


package com.integ;

import com.integpg.sensor.SensorPort;
import com.integpg.system.JANOS;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;

/**
 * This application will allow you to set the volume of the control panel click
 */
public class ControlPanel {

    private static final String USAGE_STRING = "CONTROLPANEL\n\n"
            + "Options:\r\n"
            + "  -VOLUME volume     Sets the volume level to a value between 0% and 100%\n"
            + "\n"
            + "Set options for the control panel";



    public static void main(String[] args) {
        // load the arguments into our parameters class
        ParameterGroups parameters = new ParameterGroups(args);

        // if the paremters are empty or there is a -help parameter then print the usage string and exit
        if (parameters.isEmpty() || parameters.containsParameterGroup("help")) {
            System.out.println(USAGE_STRING);
            return;
        }

        // go through each parameter and process it
        while (parameters.hasMoreParameters()) {
            String parameterGroupName = parameters.getNextParameterGroup();
            ArrayList parameterOptions = parameters.getParameterOptions(parameterGroupName);

            switch (parameterGroupName) {
                case "volume":
                    try {
                        // process set volume
                        processSetVolume(parameterOptions);
                    } catch (Exception ex) {
                        System.out.println(ex.getMessage());
                    }
                    break;

                default:
                    System.out.println("unknown parameter: " + parameterGroupName);
                    return;

            }
        }

    }



    private static void processSetVolume(ArrayList options) {
//        ArrayList volumeArguments = _parameters.get("volume");
        if (0 == options.size())
            throw new RuntimeException("must specify a volume parameter between 0% and 100%.");

        int volume;
        try {
            // get the volume from the arguments.  allow the user to enter a double but 
            // save the integer part
            volume = Double.valueOf(options.get(0)).intValue();
            if (0 > volume || 100 < volume) throw new Exception();
        } catch (Exception ex) {
            throw new RuntimeException("invalid volume specified: '" + options.get(0)
                    + "', volume parameter must be between 0% and 100%.");
        }

        // go through the external devices and set the volume for any connected control panels
        for (long externalModuleAddress : getExternalAddresses()) {
            // get a string representation of the address
            String addressString = getAddressString(externalModuleAddress).toUpperCase();

            // determine the type of module and handle accordingly
            if (0xfa == (externalModuleAddress & 0xff)) {

                try {
                    byte[] writeBlock = getSetVolumeWriteBlock((int) (volume * 2.55));
                    SensorPort.writeDeviceBlock(externalModuleAddress, writeBlock);

                    // lets print the result to the screen as well as the syslog
                    String result = String.format("Panel at %s set volume to %d percent", addressString, volume);
                    System.out.println(result);
                    JANOS.syslog(result);
                } catch (IOException ex) {
                    throw new RuntimeException(
                            String.format("unable to write to the control panel: %s.", addressString));
                }
            }
        }

    }



    private static long[] getExternalAddresses() {
        try {
            // query the unit for an array of external module addresses
            return SensorPort.externalDeviceList();
        } catch (IOException ex) {
            throw new RuntimeException("unable to get external device list.");
        }
    }



    private static String getAddressString(long address) {
        String addressString = "0000000000000000" + Long.toHexString(address);
        return addressString.substring(addressString.length() - 16);
    }



    private static byte[] getSetVolumeWriteBlock(int volume) {
        // the write block for the control panel is 20 bytes
        byte[] bytes = new byte[20];

        // to set the volume we only need to set the command byte and the audio volume byte
        bytes[10] = 4; // set audio volume command
        bytes[11] = (byte) volume; // audio volume level

        return bytes;
    }
}


/**
 * A class for interpreting command line arguments into parameter groups
 */
class ParameterGroups {

    private final Hashtable> _parameters = new Hashtable<>();
    private Enumeration _enumeration;



    public ParameterGroups(String[] args) {
        // parse the arguments
        ArrayList options = null;
        for (int i = 0; i < args.length; i++) {
            String arg = args[i].toLowerCase();

            if (arg.charAt(0) == '-') {
                while (arg.charAt(0) == '-') {
                    arg = arg.substring(1);
                }
                options = new ArrayList<>();
                _parameters.put(arg, options);

            } else if (options != null) {
                options.add(arg);

            } else {
                System.out.println("Illegal parameter usage");
                return;

            }
        }
        _enumeration = _parameters.keys();
    }



    public boolean isEmpty() {
        return 0 == _parameters.size();
    }



    public boolean containsParameterGroup(String paramterGroup) {
        return _parameters.containsKey(paramterGroup);
    }



    public String getNextParameterGroup() {
        if (null == _enumeration) _enumeration = _parameters.keys();
        if (_enumeration.hasMoreElements()) {
            return _enumeration.nextElement();
        } else {
            return null;
        }
    }



    ArrayList getParameterOptions(String parameterGroupName) {
        return _parameters.get(parameterGroupName);
    }



    boolean hasMoreParameters() {
        return _enumeration.hasMoreElements();
    }

}


Once the ParameterGroups class has been instantiated with our command line arguments we loop through each parameter group. When we find the “volume” parameter group we call processSetVolume() and pass in the parameter options.

        // go through each parameter group and process it
        while (parameters.hasMoreParameters()) {
            String parameterGroupName = parameters.getNextParameterGroup();
            ArrayList<String> parameterOptions = parameters.getParameterOptions(parameterGroupName);

            switch (parameterGroupName) {
                case "volume":
                    try {
                        // process set volume
                        processSetVolume(parameterOptions);
                    } catch (Exception ex) {
                        System.out.println(ex.getMessage());
                    }
                    break;

                default:
                    System.out.println("unknown parameter: " + parameterGroupName);
                    return;

            }
        }

Now that we are inside the processSetVolume() method we need to validate the options provided. We make sure that a volume was provided and that it is a valid number between 0% and 100%.




    private static void processSetVolume(ArrayList<String> options) {
//        ArrayList<String> volumeArguments = _parameters.get("volume");
        if (0 == options.size())
            throw new RuntimeException("must specify a volume parameter between 0% and 100%.");

        int volume;
        try {
            // get the volume from the arguments.  allow the user to enter a double but 
            // save the integer part
            volume = Double.valueOf(options.get(0)).intValue();
            if (0 > volume || 100 < volume) throw new Exception();
        } catch (Exception ex) {
            throw new RuntimeException("invalid volume specified: '" + options.get(0)
                    + "', volume parameter must be between 0% and 100%.");
        }

...


Now that the options have been validated and we processed the assigned volume we are ready to write to the connected control panels. We loop through each connected panel and check to make sure it is a Control Panel which is type 0xFA. If it is a Control Panel we can get the write block and send it to the Sensor Port.


        // go through the external devices and set the volume for any connected control panels
        for (long externalModuleAddress : getExternalAddresses()) {
            // get a string representation of the address
            String addressString = getAddressString(externalModuleAddress).toUpperCase();

            // determine the type of module and handle accordingly
            if (0xfa == (externalModuleAddress & 0xff)) {

                try {
                    byte[] writeBlock = getSetVolumeWriteBlock((int) (volume * 2.55));
                    SensorPort.writeDeviceBlock(externalModuleAddress, writeBlock);

                    // lets print the result to the screen as well as the syslog
                    String result = String.format("Panel at %s set volume to %d percent", addressString, volume);
                    System.out.println(result);
                    JANOS.syslog(result);
                } catch (IOException ex) {
                    throw new RuntimeException(
                            String.format("unable to write to the control panel: %s.", addressString));
                }
            }
        }

This example will monitor the IO Log utilizing the IoLog class. There are many ways to watch IO for changes. You could simply poll the IO and see if it changes. You are likely to set up a polling routing on some interval. If that interval is not fast enough then you have the potential of missing an change. Using the IO Log ensures that you see that a transition occurred. You have the added benefit of getting the exact time of that transition to the millisecond. This helps if you are measuring the time between transitions.

package com.integpg;

import com.integpg.system.IoEvent;
import com.integpg.system.Iolog;
import java.util.Date;

public class IOLogSampleMain implements Runnable {

    private final Iolog _iolog = new Iolog();
    private Thread _thd;



    public static void main(String[] args) throws InterruptedException {
        new IOLogSampleMain().start();

        Thread.sleep(Integer.MAX_VALUE);
    }



    public void start() {
        if (null == _thd) {
            _thd = new Thread((Runnable) this);
            _thd.setName("IoLogMonitor");
            _thd.start();
        }
    }



    @Override
    @Override
    public void run() {
        long lastConsumedTimestamp = System.currentTimeMillis();

        while (true) {
            try {
                // refresh the iolog object so that we only get new events since the last time we queried it
                _iolog.refresh(lastConsumedTimestamp);
                // get the events for the inputs only
                IoEvent[] ioEvents = _iolog.getInputEvents();

                // make sure we were returned events
                if (ioEvents.length > 0) {
                    System.out.println("IoLogMonitor returned " + ioEvents.length + " events");
                    // go through each event and print some meaningful information about it
                    for (int index = ioEvents.length - 1; index >= 0; index--) {
                        IoEvent ioEvent = ioEvents[index];
                        System.out.println("ioEvent[" + index + "]: time: " + ioEvent.timestamp + " changed input mask: " + Long.toHexString(ioEvent.mask) + " input states mask: " + Long.toHexString(ioEvent.states));

                        // update our lastConsumedTimestamp with the date of the most recent event
                        if (ioEvent.timestamp > lastConsumedTimestamp) {
                            lastConsumedTimestamp = ioEvent.timestamp;
                            System.out.println("lastConsumedTimestamp: " + new Date(lastConsumedTimestamp));
                        }
                    }
                }

                Thread.sleep(100);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
}

This sample shows you how to pulse multiple outputs and a single output. The method must take two binary masks. One describing the desired states durring the pulse and the other describing which channels will be pulsed.

package com.integpg;
import com.integpg.system.JANOS;
public class PulseOutputs {
    public static void main(String[] args) {
        // to get the states of the outputs use the JANOS class and the getOutputStates method
        int outputStates = JANOS.getOutputStates();
        //print the Output States through telnet (console) connection.
        System.out.println("Output States are: " + outputStates);
        //Pulse 8 Relay Outputs On for 5 seconds (5000 milliseconds) after which outputs will return to previous state.
        //All channels (1111 1111b)
        JANOS.setOutputPulsed(255, 255, 5000);
        //Sleep 10 seconds to so that there is a noticable difference between on and off states.
        try {
            Thread.sleep(10000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        int counter = 0;
        while(counter<5){
        //Pulse Channel 5 Relay Output On for 5 seconds (5000 milliseconds) after which output will return to previous state.
        //Channels   8765 4321
        //Channel 5 (0001 0000b)
        JANOS.setOutputPulsed(16, 16, 5000);
            try {
                Thread.sleep(10000);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
            counter++;
            System.out.println("Counter: " + counter);
        }
    }
}

Sometimes you want to control the outputs. The outputs may be wired to things light lights, sirens, valves and maybe fans. This example will show you how

package com.integpg;
import com.integpg.system.JANOS;
public class WriteOutputs {
    public static void main(String[] args) {
        // to get the states of the outputs use the JANOS class and the getOutputStates method
        int outputStates = JANOS.getOutputStates();
        //print the Output States through telnet (console) connection.
        System.out.println("Output States are: " + outputStates);
        //set output relay for channel 5 (channel n-1) and true top turn the relay on, false to turn relay off.
        JANOS.setOutputRelay(4 , true);
        //Relay Output 5 should now be on.
        //print the Output States through telnet (console) connection.
        System.out.println("Output States are: " + outputStates);
    }
}