Java Code Samples

This post goes over how to handle needing more then 4 inputs, when there are only 4 on the JNIOR (This application creates three new inputs). Using multiple inputs being activated at the same time, we can count them as new inputs. This application will be able to send macros using these additional inputs we create.

Below is the code for the full application. Make sure to have properly setup the project before using this code, to learn how to setup a custom java application on the JNIOR, a link is here.

import com.integpg.system.IoEvent;
import com.integpg.system.Iolog;
import com.integpg.system.JANOS;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Date;

public class TestProject {

    //Used to get List of IO events
    public static Iolog _iolog = new Iolog();
    //Used to keep track of when to refresh Iolog
    public static long refresh_timestamp = 0;
    //Tracks commands that have the correct states to send a macro
    public static int timesCalled;
    //String Array for loading symbol
    public static String[] loadSymbol = {"|", "/", "-", "\\"};
    //loadSymbol's tracker
    public static int loadTracker = 0;

    public static long timeCheck = System.currentTimeMillis();
    
    public static ArrayList<IoEvent> ioevents = new ArrayList<>();

    public static ArrayList<EventInfo> ioEventClass = new ArrayList<>();

    public static class EventInfo {

        int macronumber;
        long timestamp;
        int mask;
        int inputstates;

        EventInfo(int macroNumber, long timeStamp, int Mask, int state) {
            // initialize the input variable from main 
            // function to the global variable of the class 
            macronumber = macroNumber;
            timestamp = timeStamp;
            mask = Mask;
            inputstates = state;
        }
        
        void setMacroNumber(int macroNumberChange) {
            
            if (macroNumberChange == 1) {
                macronumber = 1;
            } else if (macroNumberChange == 2) {
                macronumber = 2;
            } else if (macroNumberChange == 4) {
                macronumber = 3;
            } else if (macroNumberChange == 8) {
                macronumber = 4;
            } else if (macroNumberChange == 3) {
                macronumber = 5;
            } else if (macroNumberChange == 6) {
                macronumber = 6;
            } else if (macroNumberChange == 12) {
                macronumber = 7;
            }
            

            
        }



    }

    public static void createConnectionAndSendMacro(String address, int port) throws InterruptedException {

        String macro = "";
        byte[] macroToBytes;

        try {
            Socket socket = new Socket(address, port);
            DataOutputStream out = new DataOutputStream(socket.getOutputStream());
            System.out.print("Connected " + new Date() + " ");

            for (int index = 0; ioEventClass.size() > index;) {
                
                System.out.println("timeCheck: " + timeCheck + " - Event timestampe " + ioEventClass.get(index).timestamp + " = " + (ioEventClass.get(index).timestamp -  timeCheck));

                if ((ioEventClass.get(index).timestamp -  timeCheck) >= 50) {
                    
                    timeCheck = System.currentTimeMillis();

                    if (ioEventClass.get(index).macronumber > 0 && ioEventClass.get(index).macronumber < 8) {
                        macro = "run Input " + ioEventClass.get(index).macronumber;
                        System.out.println(" " + macro);
                        macroToBytes = macro.getBytes();
                        out.write(macroToBytes);
                        ioEventClass.remove(index);
                        ioevents.remove(index);
                    } else {
                        System.out.println("Macro number invalid, no execution");
                        ioEventClass.remove(index);
                        ioevents.remove(index);
                    }
                } else {

                    System.out.println("Macro is too new, holding info in ioEventClass to get complete macro.");
                    break;
                    
                }
            }

            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public static void getEventInfo() {

        _iolog.refresh(refresh_timestamp);
        IoEvent[] ioEvents = _iolog.getInputEvents();

        //loop to send macros if their states match correct input states
        for (int index = ioEvents.length - 1; index >= 0; index--) {

            IoEvent ioEvent = ioEvents[index];

            if (ioEvent.states >= 0 && ioEvent.states < 13) {
                int statesTurnedHigh = ioEvent.states & ioEvent.mask;

                if (0 != statesTurnedHigh) {

                    System.out.println("statesTurnedHigh; " + statesTurnedHigh);
                    if (0 < ioevents.size()) {
                        System.out.println("path 1");
                        IoEvent lastIoEvent = ioevents.get(ioevents.size() - 1);
                        long elapsedtime = ioEvent.timestamp - lastIoEvent.timestamp;
                        System.out.println("elapsedtime; " + elapsedtime);
                        if (elapsedtime >= 20) {
                            System.out.println("path 1-1");
                            ioevents.add(ioEvent);
                            addEventInfoToList(ioevents.get(ioevents.size() - 1));
                        } else {
                            System.out.println("path 1-2");
                            lastIoEvent.states |= statesTurnedHigh;
                            ioEventClass.get(0).setMacroNumber(lastIoEvent.states | statesTurnedHigh);
                        }
                    } else {
                        System.out.println("path 2");
                        ioevents.add(ioEvent);
                        addEventInfoToList(ioevents.get(ioevents.size() - 1));
                    }
                }

                refresh_timestamp = ioEvents[0].timestamp;

            }
        }

        if (!ioevents.isEmpty()) {

            System.out.println("ioevents; " + ioevents.size());
            for (int increment = 0; increment < ioevents.size(); increment++) {
                System.out.println("ioevents info " + ioevents.get(increment).states + " " + ioevents.get(increment).timestamp);
            }

        }

    }

    public static void addEventInfoToList(IoEvent iologEvent) {
        int macroNumber = 0;

        if (iologEvent.states == 1) {
            macroNumber = 1;
        } else if (iologEvent.states == 2) {
            macroNumber = 2;
        } else if (iologEvent.states == 4) {
            macroNumber = 3;
        } else if (iologEvent.states == 8) {
            macroNumber = 4;
        } else if (iologEvent.states == 3) {
            macroNumber = 5;
        } else if (iologEvent.states == 6) {
            macroNumber = 6;
        } else if (iologEvent.states == 12) {
            macroNumber = 7;
        } else {
            System.out.println("Macro number not correct");
        }

        EventInfo eventInfoPlaceholder = new EventInfo(macroNumber, iologEvent.timestamp, iologEvent.mask, iologEvent.states);
        ioEventClass.add(eventInfoPlaceholder);

    }

    public static void main(String[] args) throws InterruptedException {

        //Making sure all lists are cleared of previous information
        _iolog.refresh();
        ioevents.clear();
        ioevents.clear();
        
        //Making sure that Registry keys are set to make socket connection
        String IP = JANOS.getRegistryString("IpConfig/IPAddress", "Went to default");
        int portNumber = JANOS.getRegistryInt("AppData/Cinema/CinemaServerClient/TcpPort", 5000);
        if (portNumber == -1) {
            System.out.println("TcpPort number not set properly, closing application.");
            System.exit(0);
        }
        System.out.println(IP);
        System.out.println(portNumber + "\n");

        while (true) {

            System.out.print("\r                                                                                                            ");
            if (loadTracker > 3) {
                loadTracker = 0;
            }
            System.out.print("\r" + loadSymbol[loadTracker]);
            loadTracker++;
            System.out.print(" [" + timesCalled + "] ");
            timesCalled++;

            Thread.sleep(300);
            getEventInfo();
            if (ioevents.isEmpty() == false) {
                createConnectionAndSendMacro(IP, portNumber);
            }

        }

    }

}

After setting up the project, the first part of the project is calling the right imports and setting the global variables. The _iolog is where to get our list of IOevents to see what inputs are being selected at any time. The refresh_timestamp is so that when we call the _iolog, its only getting the list from the last time we called the _iolog and not the entire list every time its called. The timesCalled is to track how many successful macros have been sent. The loadSymbol is used to create a a loading symbol as the JNIOR waits for inputs to be triggered. Lastly, the loadTracker is to show when the end of the loadSymbol list has been reached to rest it.

import com.integpg.system.IoEvent;
import com.integpg.system.Iolog;
import com.integpg.system.JANOS;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Date;

public class TestProject {

    //Used to get List of IO events
    public static Iolog _iolog = new Iolog();
    //Used to keep track of when to refresh Iolog
    public static long refresh_timestamp = 0;
    //Tracks commands that have the correct states to send a macro
    public static int timesCalled;
    //String Array for loading symbol
    public static String[] loadSymbol = {"|", "/", "-", "\\"};
    //loadSymbol's tracker
    public static int loadTracker = 0;

    public static long timeCheck = System.currentTimeMillis();
    
    public static ArrayList<IoEvent> ioevents = new ArrayList<>();

    public static ArrayList<EventInfo> ioEventClass = new ArrayList<>();

Moving on we have multiple functions, starting with EventInfo.

The EventInfo constructor grabs all the _iolog information along with the states of the inputs being converted from 1 to 7 . The function setMacroNumber grabs the current state of the inputs on the JNIOR and converts it to the input number 1 – 7

        EventInfo(int macroNumber, long timeStamp, int Mask, int state) {
            // initialize the input variable from main 
            // function to the global variable of the class 
            macronumber = macroNumber;
            timestamp = timeStamp;
            mask = Mask;
            inputstates = state;
        }

        void setMacroNumber(int macroNumberChange) {
            
            if (macroNumberChange == 1) {
                macronumber = 1;
            } else if (macroNumberChange == 2) {
                macronumber = 2;
            } else if (macroNumberChange == 4) {
                macronumber = 3;
            } else if (macroNumberChange == 8) {
                macronumber = 4;
            } else if (macroNumberChange == 3) {
                macronumber = 5;
            } else if (macroNumberChange == 6) {
                macronumber = 6;
            } else if (macroNumberChange == 12) {
                macronumber = 7;
            }
                        
        }

The next function is createConnectionAndSendMacro. This function takes the address and port number you specify and create a tcp connection on it. This will be connected to the Cinema application to create the macros from the inputs triggered. This function goes through the list of IOevents and sends through the tcp connection any of the 1 – 7 states its looking for. Then it closes the connection.

 public static void createConnectionAndSendMacro(String address, int port) throws InterruptedException {

        String macro = "";
        byte[] macroToBytes;

        try {
            Socket socket = new Socket(address, port);
            DataOutputStream out = new DataOutputStream(socket.getOutputStream());
            System.out.print("Connected " + new Date() + " ");

            for (int index = 0; ioEventClass.size() > index;) {
                
                System.out.println("timeCheck: " + timeCheck + " - Event timestampe " + ioEventClass.get(index).timestamp + " = " + (ioEventClass.get(index).timestamp -  timeCheck));

                if ((ioEventClass.get(index).timestamp -  timeCheck) >= 50) {
                    
                    timeCheck = System.currentTimeMillis();

                    if (ioEventClass.get(index).macronumber > 0 && ioEventClass.get(index).macronumber < 8) {
                        macro = "run Input " + ioEventClass.get(index).macronumber;
                        System.out.println(" " + macro);
                        macroToBytes = macro.getBytes();
                        out.write(macroToBytes);
                        ioEventClass.remove(index);
                        ioevents.remove(index);
                    } else {
                        System.out.println("Macro number invalid, no execution");
                        ioEventClass.remove(index);
                        ioevents.remove(index);
                    }
                } else {

                    System.out.println("Macro is too new, holding info in ioEventClass to get complete macro.");
                    break;
                    
                }
            }

            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

Next is the getEventInfo function. This function creates a list of IOevents from the _iolog. Then by going through the list and seeing which values are not within 20 milliseconds of each other and depending on where they are in the list, grab those values from the IOevent and use them in other functions to determine which macro to run.

    public static void getEventInfo2() {

        _iolog.refresh(refresh_timestamp);
        IoEvent[] ioEvents = _iolog.getInputEvents();

        //loop to send macros if their states match correct input states
        for (int index = ioEvents.length - 1; index >= 0; index--) {

            IoEvent ioEvent = ioEvents[index];

            if (ioEvent.states >= 0 && ioEvent.states < 13) {
                int statesTurnedHigh = ioEvent.states & ioEvent.mask;

                if (0 != statesTurnedHigh) {

                    System.out.println("statesTurnedHigh; " + statesTurnedHigh);
                    if (0 < ioevents.size()) {
                        System.out.println("path 1");
                        IoEvent lastIoEvent = ioevents.get(ioevents.size() - 1);
                        long elapsedtime = ioEvent.timestamp - lastIoEvent.timestamp;
                        System.out.println("elapsedtime; " + elapsedtime);
                        if (elapsedtime >= 20) {
                            System.out.println("path 1-1");
                            ioevents.add(ioEvent);
                            addEventInfoToList(ioevents.get(ioevents.size() - 1));
                        } else {
                            System.out.println("path 1-2");
                            lastIoEvent.states |= statesTurnedHigh;
                            ioEventClass.get(0).setMacroNumber(lastIoEvent.states | statesTurnedHigh);
                        }
                    } else {
                        System.out.println("path 2");
                        ioevents.add(ioEvent);
                        addEventInfoToList(ioevents.get(ioevents.size() - 1));
                    }
                }

                refresh_timestamp = ioEvents[0].timestamp;

            }
        }

        if (!ioevents.isEmpty()) {

            System.out.println("ioevents; " + ioevents.size());
            for (int increment = 0; increment < ioevents.size(); increment++) {
                System.out.println("ioevents info " + ioevents.get(increment).states + " " + ioevents.get(increment).timestamp);
            }

        }

    }

The addEventInfoList converts the current input states to an input number 1 – 7 similar to setMacroNumber, but returning all the IOevent information unlike setMacroNumber does.

public static void addEventInfoToList(IoEvent iologEvent) {
        int macroNumber = 0;

        if (iologEvent.states == 1) {
            macroNumber = 1;
        } else if (iologEvent.states == 2) {
            macroNumber = 2;
        } else if (iologEvent.states == 4) {
            macroNumber = 3;
        } else if (iologEvent.states == 8) {
            macroNumber = 4;
        } else if (iologEvent.states == 3) {
            macroNumber = 5;
        } else if (iologEvent.states == 6) {
            macroNumber = 6;
        } else if (iologEvent.states == 12) {
            macroNumber = 7;
        } else {
            System.out.println("Macro number not correct");
        }

        EventInfo eventInfoPlaceholder = new EventInfo(macroNumber, iologEvent.timestamp, iologEvent.mask, iologEvent.states);
        ioEventClass.add(eventInfoPlaceholder);

    }

Lastly, the main function of the program starts by choosing which IP and tcp port number need to be connected to, by having it set in the registry. Then after the loadingSymbol is setup, the functions getEventInfo and createConnectionAndSendMacro get called.

 public static void main(String[] args) throws InterruptedException {

        //Making sure all lists are cleared of previous information
        _iolog.refresh();
        ioevents.clear();
        ioevents.clear();
        
        //Making sure that Registry keys are set to make socket connection
        String IP = JANOS.getRegistryString("IpConfig/IPAddress", "Went to default");
        int portNumber = JANOS.getRegistryInt("AppData/Cinema/CinemaServerClient/TcpPort", 5000);
        if (portNumber == -1) {
            System.out.println("TcpPort number not set properly, closing application.");
            System.exit(0);
        }
        System.out.println(IP);
        System.out.println(portNumber + "\n");

        while (true) {

            System.out.print("\r                                                                                                            ");
            if (loadTracker > 3) {
                loadTracker = 0;
            }
            System.out.print("\r" + loadSymbol[loadTracker]);
            loadTracker++;
            System.out.print(" [" + timesCalled + "] ");
            timesCalled++;

            Thread.sleep(300);
            getEventInfo2();
            if (ioevents.isEmpty() == false) {
                createConnectionAndSendMacro(IP, portNumber);
            }

        }

    }

This post explains how to send and receive messages from a System Message Pump. There is a previous post showing how to create a System Message Pump. This example uses the additional applications the previous example did and should be included when the project for this application is created. That post can be accessed here. Please look over that post first as this one uses code from and references that post. 

package mqttmessagepump;

import com.integpg.system.JANOS;
import com.integpg.system.MessagePump;
import com.integpg.system.SystemMsg;
import java.util.Json;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

public class MQTTMessagePump implements Runnable {

    private static final MQTTMessagePump THIS = new MQTTMessagePump();
    private static final MessagePump MESSAGE_PUMP = new MessagePump();
    private static final Vector LISTENERS = new Vector<>();

    private static Thread _thread;

    /**
     * Singleton constructor
     */
    private MQTTMessagePump() {
    }

    /**
     * adds a listener that will be alerted of all received messages. The
     * listener will be responsible for determining if the message has meaning
     * to them
     *
     * @param listener
     */
    public static void addListener(MessagePumpListener listener) {
        synchronized (LISTENERS) {
            LISTENERS.addElement(listener);
        }
    }

    /**
     * starts our message pump engine.
     */
    static void start() {
        if (null == _thread) {
            _thread = new Thread(THIS);
            _thread.setName("message-pump-engine");
            _thread.setDaemon(true);
            _thread.start();
        }
    }
    
    //completes the message 1600 after being sent from the MQTT application
    
    public static void completeMessage1600 (String recievedJsonInfo) {
        
        Json holdinginfo = new Json(recievedJsonInfo);
        String[] arrayofkeys = holdinginfo.keyarray();
        String subscribecheck = holdinginfo.getString(arrayofkeys[0]);
        String topicscheck = holdinginfo.getString(arrayofkeys[1]);
        String infocheck = holdinginfo.getString(arrayofkeys[2]);
        System.out.println(arrayofkeys[0] + ": " + subscribecheck + ", " + arrayofkeys[1] + ": " + topicscheck + ", " + arrayofkeys[2] + ": " + infocheck);
 
    }
    
    //creates the message that will be sent to the other program to complete
    
    public static SystemMsg createMessage (int messageType, byte [] messageInfo) {
        
        SystemMsg returnMsg = new SystemMsg();
        returnMsg.type = messageType;
        returnMsg.msg = messageInfo;
        
        System.out.println(returnMsg);
        return returnMsg;
        
    }
    
    //adds a value to the Json object that we be sent as part of the message to the other program
    
    public static Json addToJson(Json Jsonadd, String valuename, Object jsonvalue) {
    
        Jsonadd.put(valuename, jsonvalue);
        return Jsonadd;
        
    }
    
    @Override
    public void run() {
        System.out.println("open the message pump and start monitoring.\r\n");
        MESSAGE_PUMP.open();

        for (;;) {
            // read all messages from the message pump
            SystemMsg systemMsg = MESSAGE_PUMP.getMessage();

            // we must repost as fast as we can
            MESSAGE_PUMP.postMessage(systemMsg);

            // notify all of our listeners

            if (systemMsg.type == 1600) {

                System.out.println();
                System.out.println("Recieved command 1600.\n");
                String jsoninfo = new String(systemMsg.msg);
                completeMessage1600(jsoninfo);           

            }
            
             Json thirdjson = new Json();
             addToJson(thirdjson, "Message", "publish"); 
             addToJson(thirdjson, "Topic", "jnior/(Your JNIOR's Serial Number)/set/digital/outputs/1/state");
             addToJson(thirdjson, "Payload", "true");
             SystemMsg returnMsg = createMessage(1600, thirdjson.toString().getBytes());
             MESSAGE_PUMP.postMessage(returnMsg);    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                Logger.getLogger(MQTTMessagePump.class.getName()).log(Level.SEVERE, null, ex);
            }

        }

    }

    /**
     * Exposes the postMesssage method of the System MessagePump
     *
     * @param msg the message the will be sent to the system message pump
     */
    public static void postMessage(SystemMsg msg) {
        MESSAGE_PUMP.postMessage(msg);
    }

}

To start, before we can create an application to talk with the MQTT application, we need to configure the MQTT application to hook up with a broker. To download the update project for the MQTT application, click here. An online broker is needed in order to use the MQTT protocol. Free ones can be found online, but for this example the free hivemq broker is being used, which can be found here. This broker is public and if you want to use MQTT protocols outside this example, a secure one should be used. The only information you need to have in the MQTT application is the host URL and the port number. Then click connect and make sure the MQTT application is running on the JNIOR.

Once the MQTT application is properly configured, the MQTT coding can be made. After creating a Message Pump program that could send messages to and from another application, the next step is to create and receive messages between the message pump and the MQTT application using the MQTT protocol. To start, we can create a new program that will interact with the MQTT application. We’ll call this program our MQTTMessage program. This sample program will only send a message to toggle an output, but could be edited to do more.

For this example, only the application containing the message pump from the previous example is needed. In the previous example, the message being sent between the applications we Json Objects. The message that needs to be created is formatted as this:

{
"Message" : "publish",
"Topic" : STRING,
"Payload" : STRING
}

The MQTT application also uses Json Objects to communicate through the messages. Looking at the for loop when creating the message to send, The message has to be “publish”, because when you are sending a message to the MQTT application, you are publishing information on a specified topic. The topic is the keyword that other devices are subscribed to. If a device is subscribed to that topic, then the device will receive the payload of the message. The payload is the actual data of the message that any device subscribed to the related topic will receive.

The run function in the previous example’s MessagePumpEngine application is replaced with a new for loop. When creating the message, it needs to be reformatted to match the Json message the MQTT application needs to receive. The message ID number has to be 1600, since that is the message ID the MQTT application is listening to. In this example, the Json object is created as:

{
  "Message" : "publish",
  "Topic" :jnior/"Your JNIOR's IP"/set/digital/outputs/1/state,
  "Payload" : true
}

The topic is to specify the JNIOR, what channel you want to toggle, and if its opened or closed. The payload sets that channels state to true, closing that output.

for (;;) {
            // read all messages from the message pump
            SystemMsg systemMsg = MESSAGE_PUMP.getMessage();

            // we must repost as fast as we can
            MESSAGE_PUMP.postMessage(systemMsg);

            // notify all of our listeners

            if (systemMsg.type == 1600) {

                System.out.println();
                System.out.println("Recieved command 1600.\n");
                String jsoninfo = new String(systemMsg.msg);
                completeMessage1600(jsoninfo);           

            }
            
             Json thirdjson = new Json();
             addToJson(thirdjson, "Message", "publish"); 
             addToJson(thirdjson, "Topic", "jnior/(Your JNIOR's Serial Number)/set/digital/outputs/1/state");
             addToJson(thirdjson, "Payload", "true");
             SystemMsg returnMsg = createMessage(1600, thirdjson.toString().getBytes());
             MESSAGE_PUMP.postMessage(returnMsg);    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                Logger.getLogger(MQTTMessagePump.class.getName()).log(Level.SEVERE, null, ex);
            }

        }

The if statement checking the (systemMsg.type == 1600) is there because the JNIOR being used to publish the message is also listening the for messages on the message pump too, and those messages will have the same message ID 1600.

if (systemMsg.type == 1600) {

                System.out.println();
                System.out.println("Recieved command 1600.\n");
                String jsoninfo = new String(systemMsg.msg);
                completeMessage1600(jsoninfo);           

            }

Inside that if statement is a call to the completeMessage1600() function. That function will grab the data out of the Json object from the message and print it out for you to see.

public static void completeMessage1600 (String recievedJsonInfo) {
        
        Json holdinginfo = new Json(recievedJsonInfo);
        String[] arrayofkeys = holdinginfo.keyarray();
        String subscribecheck = holdinginfo.getString(arrayofkeys[0]);
        String topicscheck = holdinginfo.getString(arrayofkeys[1]);
        String infocheck = holdinginfo.getString(arrayofkeys[2]);
        System.out.println(arrayofkeys[0] + ": " + subscribecheck + ", " + arrayofkeys[1] + ": " + topicscheck + ", " + arrayofkeys[2] + ": " + infocheck);
 
    }

With these changes to the previous message pump application and MQTT application configuration, after running this example program you should be able to create and send messages through the MQTT application using the MQTT protocol.

This post will outline and provide the source code to our Serial Control application.

The application should really be called ASCII Control instead of Serial Control. The application got it’s name because communication was originally only available via the serial ports. Ethernet communication was added in a later release.

The code was rewritten to clean it up and make it available as a sample. We will talk about the features of the application that deal with serial communication, Ethernet communication, I/O control and I/O monitoring. Features such as versioning and logging are parts of this application that are beyond the scope of this post.

This post explains how to send and receive messages from a System Message Pump. There is a previous post showing how to create a System Message Pump that you can access here. Please look over that post first as this one uses code from and references that post. 

After creating a Message Pump program from the previous System Message Pump sample, the next step is to create and receive messages between two different programs. To start, we can create a new program that will interact with the previous System Message Pump program. We’ll call this program our PostMessage program. This sample program only has two messages it can send, but could be edited to add more. These messages will also only pulse output relays but can be edited to do a lot of other actions as well.

import com.integpg.system.JANOS;
import com.integpg.system.SystemMsg;
import com.integpg.system.MessagePump;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Json;

public class CommunicationPost2 {

    //This funciton decides from the user input it gets whether or not the program should post another message or close the program.
    //The only message you can create is message 1300
    public static int getresponse(MessagePump closesPump) throws IOException {

        String type = "";
        BufferedReader reading = new BufferedReader(new InputStreamReader(System.in));
        int testtype = 0;
        boolean running = true;
        boolean typecheck = false;

        while (running == true) {

            System.out.println("\nPlease enter a System Pump command. Type 'create' to make a command. Type 'finished' when done.\n");
            String choice = reading.readLine();
            System.out.println("|" + choice + "|");

            switch (choice) {

                case "finished":
                    System.out.println("Finishing program...\n");
                    System.out.println();
                    closesPump.close();
                    System.out.println();
                    System.exit(0);

                case "create":
                    System.out.println();
                    System.out.println("\nPlease enter the type number\n");
                    System.out.println();
                    while (typecheck == false) {
                            type = reading.readLine();
                            try{
                            testtype = Integer.parseInt(type);
                            typecheck = true;
                            }
                            catch (NumberFormatException e) {
                                System.out.println("\nIncorrect input. Try again\n");
                            }
                    }
                    return testtype;

                default:
                    System.out.println("\nBad input. Try again.\n");
            }
        }
        return testtype;
    }

    //adds a value to the Json object that we be sent as part of the message to the other program
    public static Json addToJson(Json gettingJson, String value, Object jsonvalue) {

        gettingJson.put(value, jsonvalue);
        return gettingJson;

    }

    //creates the message that will be sent to the other program to complete
    public static SystemMsg createMessage(int messagetype, byte[] messageinfo) {

        SystemMsg newMsg = new SystemMsg();
        newMsg.type = messagetype;
        newMsg.msg = messageinfo;
        System.out.println(newMsg);
        return newMsg;

    }

    //completes the message 1301 if it was sent from the other program
    public static void completeMessage1301(String recievedJsonInfo) {

        Json holdinginfo = new Json(recievedJsonInfo);
        String[] arrayofkeys = holdinginfo.keyarray();
        int togglecheck = holdinginfo.getInt(arrayofkeys[0]);
        int channelscheck = holdinginfo.getInt(arrayofkeys[1]);
        int durationcheck = holdinginfo.getInt(arrayofkeys[2]);
        System.out.println(arrayofkeys[0] + ": " + togglecheck + ", " + arrayofkeys[1] + ": " + channelscheck + ", " + arrayofkeys[2] + ": " + durationcheck);
        try {
            JANOS.setOutputPulsed(togglecheck, channelscheck, durationcheck);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public static Json getInfoToJson() throws IOException {

        Json newJson = new Json();
        String tempduration = "";
        String tempchannels = "";
        String temp_O_or_C = "";
        boolean successcheck1 = false;
        boolean successcheck2 = false;
        boolean successcheck3 = false;
        BufferedReader reading = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("\nThis is for toggling pulse outputs.\n");
        System.out.println("\nPlease enter if which channels you are pulsing: ' 1 - 255'\n");

        while (successcheck1 == false) {
            try {
                temp_O_or_C = reading.readLine();
                successcheck1 = true;
            } catch (IOException | NumberFormatException e) {
                System.out.println("\nIncorrect format, try again.\n");
            }
            if (successcheck1 == true) {
                int O_or_C = Integer.parseInt(temp_O_or_C);
                newJson = addToJson(newJson, "Pulse", O_or_C);
            }
        }

        System.out.println("\nPlease enter what channels: '1 - 255'\n");
        while (successcheck2 == false) {
            try {
                tempchannels = reading.readLine();
                successcheck2 = true;
            } catch (IOException | NumberFormatException e) {
                System.out.println("\nIncorrect format, try again.\n");

            }
            if (successcheck2 == true) {
                int channels = Integer.parseInt(tempchannels);
                newJson = addToJson(newJson, "Channels", channels);
            }
        }

        System.out.println("\nPlease enter duration in milliseconds:\n");
        while (successcheck3 == false) {
            try {
                tempduration = reading.readLine();
                successcheck3 = true;
            } catch (IOException | NumberFormatException e) {
                System.out.println("\nIncorrect format, try again.\n");
            }
            if (successcheck3 == true) {
                int duration = Integer.parseInt(tempduration);
                newJson = addToJson(newJson, "Duration", duration);
            }
        }

        return newJson;

    }

    public static void main(String[] args) throws IOException, InterruptedException {

        MessagePump MESSAGE_PUMP = new MessagePump();

        MESSAGE_PUMP.open();

        while (true) {

            boolean checkmsg = false;

            int fixedtype = getresponse(MESSAGE_PUMP);

            if (fixedtype == 1300) {

                Json firstJson = getInfoToJson();
                SystemMsg systemMsg = createMessage(fixedtype, firstJson.toString().getBytes());
                MESSAGE_PUMP.postMessage(systemMsg);
                System.out.println();
            }

            while (checkmsg == false) {

                SystemMsg msgRecieved = MESSAGE_PUMP.getMessage(1301);

                if (msgRecieved.type == 1301) {
                    System.out.println("Message 1301 recieved, pulsing output 1.");
                    String jsoninfo2 = new String(msgRecieved.msg);
                    completeMessage1301(jsoninfo2);
                    checkmsg = true;
                }

            }

        }

    }

}

This PostMessage program will create a message that, after getting inputs from the user, will be sent to the previously created System Message Pump program. The program starts by asking the user if they wish to create a message or close the program, then calling the “getresponse” function to initiate whichever the user chooses.

    public static int getresponse(MessagePump closesPump) throws IOException {

        String type = "";
        BufferedReader reading = new BufferedReader(new InputStreamReader(System.in));
        int testtype = 0;
        boolean running = true;
        boolean typecheck = false;

        while (running == true) {

            System.out.println("\nPlease enter a System Pump command. Type 'create' to make a command. Type 'finished' when done.\n");
            String choice = reading.readLine();
            System.out.println("|" + choice + "|");

            switch (choice) {

                case "finished":
                    System.out.println("Finishing program...\n");
                    System.out.println();
                    closesPump.close();
                    System.out.println();
                    System.exit(0);

                case "create":
                    System.out.println();
                    System.out.println("\nPlease enter the type number\n");
                    System.out.println();
                    while (typecheck == false) {
                            type = reading.readLine();
                            try{
                            testtype = Integer.parseInt(type);
                            typecheck = true;
                            }
                            catch (NumberFormatException e) {
                                System.out.println("\nIncorrect input. Try again\n");
                            }
                    }
                    return testtype;

                default:
                    System.out.println("\nBad input. Try again.\n");
            }
        }
        return testtype;
    }

If the user decides to create a message, it will call a function called “getInfoToJson”. This will gather information to form a Json object. The first two values are from 1 – 255 to represent the 8 outputs in bit values, and the last value is time in milliseconds. The “getInfoToJson” function will call the “addToJson” function to create the Json object “getInfoToJson” will return. 

public static Json getInfoToJson() throws IOException {

        Json newJson = new Json();
        String tempduration = "";
        String tempchannels = "";
        String temp_O_or_C = "";
        boolean successcheck1 = false;
        boolean successcheck2 = false;
        boolean successcheck3 = false;
        BufferedReader reading = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("\nThis is for toggling pulse outputs.\n");
        System.out.println("\nPlease enter if which channels you are pulsing: ' 1 - 255'\n");

        while (successcheck1 == false) {
            try {
                temp_O_or_C = reading.readLine();
                successcheck1 = true;
            } catch (IOException | NumberFormatException e) {
                System.out.println("\nIncorrect format, try again.\n");
            }
            if (successcheck1 == true) {
                int O_or_C = Integer.parseInt(temp_O_or_C);
                newJson = addToJson(newJson, "Pulse", O_or_C);
            }
        }

        System.out.println("\nPlease enter what channels: '1 - 255'\n");
        while (successcheck2 == false) {
            try {
                tempchannels = reading.readLine();
                successcheck2 = true;
            } catch (IOException | NumberFormatException e) {
                System.out.println("\nIncorrect format, try again.\n");

            }
            if (successcheck2 == true) {
                int channels = Integer.parseInt(tempchannels);
                newJson = addToJson(newJson, "Channels", channels);
            }
        }

        System.out.println("\nPlease enter duration in milliseconds:\n");
        while (successcheck3 == false) {
            try {
                tempduration = reading.readLine();
                successcheck3 = true;
            } catch (IOException | NumberFormatException e) {
                System.out.println("\nIncorrect format, try again.\n");
            }
            if (successcheck3 == true) {
                int duration = Integer.parseInt(tempduration);
                newJson = addToJson(newJson, "Duration", duration);
            }
        }

        return newJson;

    }

Then it will call the “createMessage” function to get the ID and information of the message that we are going to send.

public static SystemMsg createMessage(int messagetype, byte[] messageinfo) {

        SystemMsg newMsg = new SystemMsg();
        newMsg.type = messagetype;
        newMsg.msg = messageinfo;
        System.out.println(newMsg);
        return newMsg;

    }

After this message is sent, it will be received from an edited version of the System Message Pump program and then send a certain message. The code for the edited Engine Pump file of the System Message Pump program is below.

import com.integpg.system.JANOS;
import com.integpg.system.MessagePump;
import com.integpg.system.SystemMsg;
import java.io.IOException;
import java.util.Json;
import java.util.Vector;

public class MessagePumpEngine implements Runnable {

    private static final MessagePumpEngine THIS = new MessagePumpEngine();
    private static final MessagePump MESSAGE_PUMP = new MessagePump();
    private static final Vector LISTENERS = new Vector<>();

    private static Thread _thread;

    /**
     * Singleton constructor
     */
    private MessagePumpEngine() {
    }

    /**
     * adds a listener that will be alerted of all received messages. The
     * listener will be responsible for determining if the message has meaning
     * to them
     *
     * @param listener
     */
    public static void addListener(MessagePumpListener listener) {
        synchronized (LISTENERS) {
            LISTENERS.addElement(listener);
        }
    }

    /**
     * starts our message pump engine.
     */
    static void start() {
        if (null == _thread) {
            _thread = new Thread(THIS);
            _thread.setName("message-pump-engine");
            _thread.setDaemon(true);
            _thread.start();
        }
    }
    
    //completes the message 1300 after being sent from the other application
    
    public static void completeMessage1300 (String recievedJsonInfo) {
        
        Json holdinginfo = new Json(recievedJsonInfo);
        String[] arrayofkeys = holdinginfo.keyarray();
        int togglecheck = holdinginfo.getInt(arrayofkeys[0]);
        int channelscheck = holdinginfo.getInt(arrayofkeys[1]);
        int durationcheck = holdinginfo.getInt(arrayofkeys[2]);
        System.out.println(arrayofkeys[0] + ": " + togglecheck + ", " + arrayofkeys[1] + ": " + channelscheck + ", " + arrayofkeys[2] + ": " + durationcheck);
        try {
            JANOS.setOutputPulsed(togglecheck, channelscheck, durationcheck);
        } 
        catch (IOException ex) {
            ex.printStackTrace();
        }
    }
    
    //creates the message that will be sent to the other program to complete
    
    public static SystemMsg createMessage (int messageType, byte [] messageInfo) {
        
        SystemMsg returnMsg = new SystemMsg();
        returnMsg.type = messageType;
        returnMsg.msg = messageInfo;
        
        System.out.println(returnMsg);
        return returnMsg;
        
    }
    
    //adds a value to the Json object that we be sent as part of the message to the other program
    
    public static Json addToJson(Json Jsonadd, String valuename, Object jsonvalue) {
    
        Jsonadd.put(valuename, jsonvalue);
        return Jsonadd;
        
    }
    
    @Override
    public void run() {
        System.out.println("open the message pump and start monitoring.\r\n");
        MESSAGE_PUMP.open();

        for (;;) {
            // read all messages from the message pump
            SystemMsg systemMsg = MESSAGE_PUMP.getMessage();

            // we must repost as fast as we can
            MESSAGE_PUMP.postMessage(systemMsg);

            // notify all of our listeners

            if (systemMsg.type == 1300) {

                System.out.println();
                System.out.println("Recieved command 1300, pulsing output. Sending another command.\n");
                String jsoninfo = new String(systemMsg.msg);
                completeMessage1300(jsoninfo);
                Json thirdjson = new Json();
                addToJson(thirdjson, "setclose", 1); 
                addToJson(thirdjson, "settingchannel", 1);
                addToJson(thirdjson, "timeset", 5000);
                SystemMsg returnMsg = createMessage(1301, thirdjson.toString().getBytes());
                MESSAGE_PUMP.postMessage(returnMsg);                

            }

        }

    }

    /**
     * Exposes the postMesssage method of the System MessagePump
     *
     * @param msg the message the will be sent to the system message pump
     */
    public static void postMessage(SystemMsg msg) {
        MESSAGE_PUMP.postMessage(msg);
    }

}

When this file of code replaces the engine pump file in the System Message Pump program, there is a loop that will be listening for the message sent from our previous program with the message type of 1300. After it receives that message, it will create a string from all of the message’s info, and then call the “completeMessage1301” function to pulse the correct outputs. Once the program does that, it will then use the “createMessage” function to create another message for the PostMessage program to read. There is another loop for the listeners to read that message when its sent as well.

public void run() {
        System.out.println("open the message pump and start monitoring.\r\n");
        MESSAGE_PUMP.open();

        for (;;) {
            // read all messages from the message pump
            SystemMsg systemMsg = MESSAGE_PUMP.getMessage();

            // we must repost as fast as we can
            MESSAGE_PUMP.postMessage(systemMsg);
            

            if (systemMsg.type == 1300) {

                System.out.println();
                System.out.println("Recieved command 1300, pulsing output. Sending another command.\n");
                String jsoninfo = new String(systemMsg.msg);
                completeMessage1300(jsoninfo);
                Json thirdjson = new Json();
                addToJson(thirdjson, "setclose", 1); 
                addToJson(thirdjson, "settingchannel", 1);
                addToJson(thirdjson, "timeset", 5000);
                SystemMsg returnMsg = createMessage(1301, thirdjson.toString().getBytes());
                MESSAGE_PUMP.postMessage(returnMsg);

                    }
                }

            }

        }

    }

This will have us go back to the PostMessage program to receive the message the Engine Pump file sent back. The PostMessage program calls the completeMessage1301 function to grab the values from the message and pulse the correct outputs. After that, the program will loop back to the beginning and re-prompt the user if they want to create or finish the program, and repeat this process until the user decides to finish the program.

 while (checkmsg == false) {

                SystemMsg msgRecieved = MESSAGE_PUMP.getMessage(1301);

                if (msgRecieved.type == 1301) {
                    System.out.println("Message 1301 recieved, pulsing output 1.");
                    String jsoninfo2 = new String(msgRecieved.msg);
                    completeMessage1301(jsoninfo2);
                    checkmsg = true;
                }

            }

He is an example that will make a HttpGet request and logging the response. The response text is printed to the screen and captured to a file. An human readable format of the bytes are also logged to a file

package httprequesttest;

import com.integpg.system.JANOS;
import java.io.FileOutputStream;
import java.io.IOException;

public class HttpRequestTest {

    public static void main(String[] args) {
        System.out.println("args.length: " + args.length);

        if (0 < args.length) {
            try {
                //
                // get the server hostname from the command line arguments
                String hostname = args[0];
                String serviceName = args[1];
                String paramsString = "";
                if (3 == args.length) paramsString = args[2];

                //
                // define our get job status request
                String urlString = String.format("http://%s/%s?%s", hostname, serviceName, paramsString);
                System.out.println("Testing call to " + urlString);

                //
                // define our http object and perform the request
                HttpURL url = new HttpURL(urlString);
                HttpRequest httpRequest = new HttpRequest(url);
                HttpResponse httpResponse = httpRequest.sendRequest();
                httpResponse.processResponse();
                String response = httpResponse.getData();

                //
                // print the response and toss the response in a file
                System.out.println("httpResponse: " + response);
                writeAllBytes("response.txt", response.getBytes());
                writeAllBytes("response.bin", hexDump(response.getBytes()).getBytes());
            } catch (IOException ex) {
                ex.printStackTrace();
            }

        } else {
            System.out.println("a url must be supplied");
        }
    }


/**
 * HELPER FUNCTIONS
 */
    public static void writeAllBytes(String filename, byte[] bytes) throws IOException {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(filename);
            fos.write(bytes);
            fos.flush();
        } catch (Exception ex) {
            JANOS.syslog("writeAllBytes to " + filename + " threw " + ex.getMessage(), JANOS.SYSLOG_ERROR);
        } finally {
            try {
                if (null != fos) fos.close();
            } catch (IOException ex1) {
                ex1.printStackTrace();
            }
        }
    }

    public static final char[] LowerHexArray = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };



    public static String hexDump(byte[] bytes) {
        return hexDump(bytes, 0, bytes.length);
    }



    public static String hexDump(byte[] bytes, int offset, int length) {
        StringBuilder sb = new StringBuilder((length / 16 + 1) * 83);

        char[] line = new char[81];
        line[0] = '0';
        line[1] = 'x';
        line[line.length - 2] = '\r';
        line[line.length - 1] = '\n';

        int endAddress = offset + length;
        for (int lineAddress = offset - (offset % 16); lineAddress < endAddress; lineAddress += 16) {
            int charPos = 2;
            line[charPos++] = LowerHexArray[(lineAddress >> 12 & 0x0F)];
            line[charPos++] = LowerHexArray[(lineAddress >> 8 & 0x0F)];
            line[charPos++] = LowerHexArray[(lineAddress >> 4 & 0x0F)];
            line[charPos++] = LowerHexArray[lineAddress & 0x0F];
            line[charPos++] = ' ';
            line[charPos++] = ' ';
            line[charPos++] = ' ';

            for (int linePos = 0; linePos < 16; linePos++) {
                int address = lineAddress + linePos;
                if (address < offset) {
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                } else if (address < endAddress) {
                    int c = bytes[address];
                    line[charPos++] = LowerHexArray[(c >> 4 & 0x0F)];
                    line[charPos++] = LowerHexArray[c & 0x0F];
                    line[charPos++] = ' ';
                } else {
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                }

                if (7 == linePos) {
                    // add 2 spaces between each group of 8 bytes
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                }
            }
            // add 3 spaces between the second group of 8 bytes and the ASCII output
            line[charPos++] = ' ';
            line[charPos++] = ' ';
            line[charPos++] = ' ';

            for (int linePos = 0; linePos < 16; linePos++) {
                int address = lineAddress + linePos;

                if (address < offset) line[charPos++] = ' ';
                else if (address < endAddress) {
                    char c = (char) bytes[address];
                    if (0x20 > c || 0x80 <= c) line[charPos++] = '.';
                    else line[charPos++] = c;
                } else line[charPos++] = ' ';
                if (7 == linePos) line[charPos++] = ' ';
            }

            sb.append(line, 0, line.length);
        }

        return sb.toString();
    }
}

HttpRequest.java

package httprequesttest;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.Hashtable;

public class HttpRequest {

    private final HttpURL _url;
    private Socket _socket;
    private String _type = "GET";
    private byte[] _data;
    private final Hashtable<String, String> _userHeaders = new Hashtable<String, String>();



    public HttpRequest(HttpURL url) {
        _url = url;
    }



    public String getRequestMethod() {
        return _type;
    }



    public void setRequestMethod(String type) {
        _type = type;
    }



    public void addHeader(String name, String value) {
        _userHeaders.put(name, value);
    }



    public void setData(byte[] data) {
        _data = data;
    }



    public HttpResponse sendRequest() throws IOException {
        InetAddress resolvedAddress = InetAddress.getByName(_url.getHost());
        _socket = new Socket(resolvedAddress.getHostAddress(), _url.getPort());
        _socket.setSoTimeout(15000);

        if (-1 != _url.toString().toLowerCase().indexOf("https")) {
            System.out.println("set secure");
            _socket.setSecure(true);
        }

//        System.out.print("Request:\r\n");
        sendHeaders();
        if (null != _data) {
            sendData();
        }

        return new HttpResponse(this);
    }



    private void sendHeaders() throws IOException {
        OutputStream out = getOutputStream();
        String headers
                = _type + " /" + _url.getFile() + _url.getQuery() + " HTTP/1.1\r\n"
                + "Host: " + _url.getHost() + "\r\n"
                + "Connection: close\r\n"
                + "Pragma: no-cache\r\n"
                + "Cache-Control: no-cache\r\n";

        if (null != _data) {
            headers += "Content-Length: " + _data.length + "\r\n";
        } else {
            headers += "Content-Length: 0\r\n";
        }

        if (0 < _userHeaders.size()) {
            Enumeration headerEnumeration = _userHeaders.keys();
            while (headerEnumeration.hasMoreElements()) {
                String name = (String) headerEnumeration.nextElement();
                String value = _userHeaders.get(name);
                headers += name + ": " + value + "\r\n";
            }
        }

        out.write((headers + "\r\n").getBytes());
        out.flush();
    }



    private void sendData() throws IOException {
        if (null != _data) {
            OutputStream out = getOutputStream();
            out.write(_data);
            out.flush();
        }
    }



    public OutputStream getOutputStream() throws SocketException, IOException {
        if (_socket == null) throw new SocketException("Socket not yet connected");
        return _socket.getOutputStream();
    }



    public InputStream getInputStream() throws SocketException, IOException {
        if (_socket == null) throw new SocketException("Socket not yet connected");
        return _socket.getInputStream();
    }



    public void close() throws IOException {
        if (null != _socket) {
            System.out.println("close HTTPRequest");
            if (null != getInputStream()) getInputStream().close();
            if (null != getOutputStream()) getOutputStream().close();
            _socket.close();
        }
    }

}

When we want to reboot the JNIOR from our Java application we call the command line reboot command.  To call to the command line we use the ConsoleProcess class.  Since there will not be any user to confirm the reboot we need to use the -f command to force the reboot.

        try {
ConsoleProcess consoleProcess = new ConsoleProcess("reboot -f");
consoleProcess.waitPrompt();
} catch (Exception ex) {
throw new IOException("Error rebooting").initCause(ex);
}

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 Application JAR [ Mar 27 2019, 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<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;

            }
        }

    }



    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((String)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<String, ArrayList<String>> _parameters = new Hashtable<>();
    private Enumeration<String> _enumeration;



    public ParameterGroups(String[] args) {
        // parse the arguments
        ArrayList<String> 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<String> 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));
                }
            }
        }

The JniorWebSocket Library was started as an example project.  It has not had much use here in-house and therefore the testing has been limited.

Here is a quick application that demonstrates how to instantiate multiple JNIOR WebSocket objects.  Each Object is added to a tree control so that they may be interacted with individually.  So far I have added the ability to add and remove, connect and disconnect and pulse output 1.  This application can be easily extended to manage multiple JNIORs and perform many different tasks.

Here are 2 links.  The first is a link to the Visual Studio project for the library.  The second link is a zip file containing the example described above.

  C# WebSocket Example [ Oct 30 2018, 6 MB, MD5: fe1be1e450c2772a16d090a0d9c37f99 ]

  MultipleJniors-1.zip [ Oct 31 2018, 1 MB, MD5: 26485d581f00d9c5985db3e7f481e551 ]

 

Here is a very simple example of how we can use websockets in a client browser to interact with the JNIOR.  This example will demonstrate how to register a few event callbacks, connect, and display the messages that are received from the JNIOR.  To force a monitor message to get sent we added an inversion checkbox for Digital Input 1.  When the Inversion state changes the input acts as though an electrical signal has forced the state change.

The WebSocket connection now behaves like any other bi-directional TCP Socket.  All of the functionality of the JNIOR protocol has been implemented in the WebSocket protocol.  The binary implementation has been replaced by JSON.  This not only makes it easier to code and debug but it is more extensible.

Below we have a zip file containing a demo page.  The page shows the incoming JSON messages in the lower part of the page.  The upper part of the page contains a button that will invert input 1 in software thus simulating the input receiving an electrical state change.

The javascript for our demo page is listed below

        // or comm object.  The communication will be established to the device that serves this page.
        _comm = new Comm();


        // called when the connection is successfully opened
        _comm.onopen = function () {
            logToBody('comm opened');
        };


        // called when the connection closed
        _comm.onclose = function () {
            _loggedIn = false;
            logToBody('comm closed');
        };


        _comm.onerror = function (error) {
            logToBody('comm error: ' + error);
        };


        // called when there is an error
        _comm.onmessage = function (evt) {
            var messageJson = JSON.parse(evt.data);
            logToBody('comm message: ' + JSON.stringify(messageJson));
        };


        // called when the page has been loaded
        window.onload = function () {
            logToBody('init comm');
            _comm.connect();
        }


        // a function to create a div with a timestamped message to the messages section of the page.
        // The div will be prepended so that is is at the top of the section.
        var logToBody = function (message) {
            var el = document.createElement('div');
            el.innerHTML = new Date().toLocaleString() + ': ' + message;
            el.style = 'white-space: nowrap';
            var messagesElement = document.getElementById('messages');
            messages.insertBefore(el, messages.firstChild);
        };


        // called when the user clicks the inversion checkbox
        function invert(checkbox, channelNumber) {
            var registryWriteJson = {
                "Message": "Registry Write",
                "Keys": {}
            };

            registryWriteJson.Keys["IO/Inputs/din" + channelNumber + "/Inversion"] = checkbox.checked.toString();
            _comm.sendJson(registryWriteJson);
        }

  wsdemo.zip [ Sep 19 2018, 6 KB, MD5: b3efe72c03f4b642c269311057613977 ]

To run the demo simply place the downloaded .zip file in the flash/www folder on your JNIOR.  Then open your browser and navigate to http://[IP_ADDRESS]/wsdemo.

After the connection is open, any change to I/O will send a new Monitor Packet. Clicking the “Invert Input 1” checkbox will cause a change to the I/O and send the Monitor Packet.

Using the zip file is taking advantage of another great feature of the JNIORs web server. A whole web application can be served out of a single zip file. This makes distribution and file management a lot easier.

The watchdog is a very powerful concept. Wikipedia says:

watchdog timer (sometimes called a computer operating properly or COP timer, or simply a watchdog) is an electronic timer that is used to detect and recover from computer malfunctions. During normal operation, the computer regularly resets the watchdog timer to prevent it from elapsing, or “timing out”. If, due to a hardware fault or program error, the computer fails to reset the watchdog, the timer will elapse and generate a timeout signal. The timeout signal is used to initiate corrective action or actions. The corrective actions typically include placing the computer system in a safe state and restoring normal system operation.

The sample below is very basic. You will need to adjust the duration and location of the watchdog for your application.

package com.integ.watchdogsample;

import com.integpg.system.JANOS;
import com.integpg.system.Watchdog;

public class WatchdogSampleMain {

    public static void main(String[] args) throws InterruptedException {
        JANOS.syslog("WatchdogSample started");

        // create a watchdog that will reboot the jnior if the watchdog is not fed once every 5 seconds
        Watchdog watchdog = new Watchdog("WatchdogSample");
        watchdog.setAction(Watchdog.WDT_REBOOT);
        watchdog.setDuration(5000);
        watchdog.activate();
        JANOS.syslog("WatchdogSample activated");

        // loop for 30 seconds.  we will feed the watchdog in this loop.  when the loop stops the watchdog will timeout and reboot the jnior.
        long loopExpiration = System.currentTimeMillis() + 30000;
        while (loopExpiration > System.currentTimeMillis()) {
            System.out.print(".");
            // feed the watchdog
            watchdog.refresh();
            // sleep for a second
            Thread.sleep(1000);
        }

        // now that the loop is finished.  the unit will reboot soon.
        JANOS.syslog("WatchdogSample loop finished");
    }

}

This is what we see in the log after the unit has come back up.

CODE: Select All


01/15/19 08:37:36.361, FTP/10.0.0.27:62888 uploaded /flash/WatchdogSample.jar [115.3 kbps]
01/15/19 08:37:44.081, WatchdogSample started
01/15/19 08:37:44.203, WatchdogSample activated
01/15/19 08:38:14.307, WatchdogSample loop finished
01/15/19 08:38:18.451, ** Assertion: WatchdogSample watchdog triggered reboot (Line 1278)
01/15/19 08:38:18.483, ** Terminating: System
01/15/19 08:38:20.971, ** Reboot on assertion: WatchdogSample watchdog triggered reboot (Line 1278)
01/15/19 08:38:20.997, -- JANOS 410 v1.7.1 initialized (POR: 1546)

Here are the options that are available to choose from when the watchdog expires.

 

WDT_REBOOT

This is the default action. Expiration of the watchdog timer causes the unit to reboot.

 

WDT_APPLICATION

This action indicates that a watchdog timer expiration is to be handled by the application itself. The system takes no action.

 

WDT_TERMINATE

When the watchdog timer expires the system will terminate the application.

 

WDT_BREAK

This interrupts the current application (similar to Ctrl-C interruption) when the watchdog timer expires.

 

WDT_RESTART

When the watchdog timer expires this will interrupt the current application and restart it.

 

WDT_MESSAGE

When the watchdog timer expires a WM_WATCHDOG (0x11) message will be sent through the system message pump. The content contains the text associated with the watchdog.

 

WDT_EVENT

Watchdog timer expiration will notify the first thread in the application that is waiting  for notification using waitOnWatchdogNotify().

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();
            }
        }
    }
}

We can take the Message Pump sample one step further. We can incorporate real-time web page communication. The web server supports WebSockets. WebSockets allow for real-time bidirectional communication. We can leverage this feature and have these messages put on the Message Pump so that our Java application can get them.

Our web page for the example looks like this.

In the code we have a button handler that will send the json formatted message to the web server.

            // register an onclick method so that we can post a message to get a random number from our 
            // sample java application.
            document.getElementById('random-number-button').onclick = function (e) {
                var getUpdates = {Message: "Post Message", Number: 2000, Content: "get-random-number"};
                _comm.sendJson(getUpdates);
            };

 

A full listing of the web page is as follows

<!DOCTYPE html>
<html lang="en">

    <head>
        <base href="/messagepumpsample/">

        <title>Message Pump Web Interface</title>

        <script src="comm.js" type="text/javascript"></script>
        <script src="md5.js" type="text/javascript"></script>

        <style>
            body { padding: 5px; }
        </style>

    </head>


    <body>

        <div>

            <h1>Message Pump Web Interface</h1>

            <button id="random-number-button">Get Random Number</button>
            <p style="width: 600px;">
                Clicking the button above will cause a message to get sent to the Web Server.  The Web Server 
                then places it in the System Message Pump.  Our sample Java application will then receive it and 
                for the sake of the sample will respond with a random number.  By placing the random number in a 
                message and back on to the System Message Pump with the same message type that it received, the 
                Operating System knows to send it back to the Web Server and in turn back to the sending Web 
                Socket connection.
            </p>

        </div>


        <script type="text/javascript">
            // our websocket communication object
            var _comm;
            var _loggedIn = false;

            // method called when the connection is successfully opened
            var onopen = function () {
                console.log("comm opened");
            };
            
            // method called when our websocket object receives a message from the web server
            var onmessage = function (evt) {
                var json = JSON.parse(evt.data);
                console.log(json);

                if (json.Message === "Monitor") {
                    if (!_loggedIn) {
                    }
                    _loggedIn = true;
                }

                //
                else if (json.Message === "Reply Message") {
                    if (2000 === json.Number) {
                        alert("Your Random Number is " + json.Content);
                    }
                }
            };
            
            // instantiate our websocket communications object and wire up some methods.  lastly call connect!
            _comm = new Comm();
            _comm.onopen = onopen;
            _comm.onmessage = onmessage;
            _comm.connect();


            
            // register an onclick method so that we can post a message to get a random number from our 
            // sample java application.
            document.getElementById('random-number-button').onclick = function (e) {
                var getUpdates = {Message: "Post Message", Number: 2000, Content: "get-random-number"};
                _comm.sendJson(getUpdates);
            };
        </script>
    </body>

</html>

To accomplish this we created a class to handle the get-random-number request. We could have handled this in our Main class but hey I like modularity.

package com.integpg.messagepumpsample;

import com.integpg.system.SystemMsg;
import java.util.Random;

public class RandomNumberMessageHandler implements MessagePumpListener {

    @Override
    public void messageReceived(SystemMsg systemMsg) {
        // this sample uses message type 2000
        if (2000 == systemMsg.type) {
            String content = new String(systemMsg.msg);
            System.out.println("content: " + content);
            
            Random rand = new Random();
            int randomNumber = rand.nextInt();
            
            SystemMsg responseMsg = new SystemMsg();
            responseMsg.type = 2000;
            responseMsg.msg = String.valueOf(randomNumber).getBytes();
            
            MessagePumpEngine.postMessage(responseMsg);
        }
    }

}

We also had to modify our init() method to attach our MessagePumpHandler.

package com.integpg.messagepumpsample;

import com.integpg.system.SystemMsg;
import java.util.Random;

public class RandomNumberMessageHandler implements MessagePumpListener {

    @Override
    public void messageReceived(SystemMsg systemMsg) {
        // this sample uses message type 2000
        if (2000 == systemMsg.type) {
            String content = new String(systemMsg.msg);
            System.out.println("content: " + content);
            
            // get a random number
            Random rand = new Random();
            int randomNumber = rand.nextInt();
            
            // build our response
            SystemMsg responseMsg = new SystemMsg();
            responseMsg.type = 2000;
            responseMsg.msg = String.valueOf(randomNumber).getBytes();
            MessagePumpEngine.postMessage(responseMsg);
        }
    }

}

Almost immediately upon clicking the button we get our response.

As you can see here the web page gives you an amazing ability to communicate with a Java application in real-time.

There are many times that you want to get a message from the system. You may also want to communicate between processes. To do either of these we employ the Message Pump.

In this example  we will monitor the messages that are going around the message pump inside the JNIOR.

We first need a class that will define all the message pump types

package com.integpg.messagepumpsample;

/**
 * A class that contains an enumeration of values for each message type.
 */
public class SystemMessageTypes {

    public static final int SM_SHUTDOWN = 0x01;
    public static final int SM_PROBE = 0x02;
    public static final int SM_GCRUN = 0x10;
    public static final int SM_WATCHDOG = 0x11;
    public static final int SM_SYSLOG = 0x12;
    public static final int SM_REGUPDATE = 0x40;
    public static final int SM_WEBSTARTUP = 0x60;
    public static final int SM_WEBSHUTDOWN = 0x61;
    public static final int SM_PROTCMDMSG = 0x70;
    public static final int SM_PROTCMDRESP = 0x71;
    public static final int SM_PIPEOPEN = 0x80;
    public static final int SM_PIPECLOSE = 0x81;
    public static final int SM_USER = 0x400;



    /**
     * @return a String definition based on the type parameter
     */
    public static String getMessageNameByType(int type) {
        switch (type) {
            case SM_SHUTDOWN:
                return "Shutdown";
            case SM_PROBE:
                return "Probe";
            case SM_GCRUN:
                return "GC Run";
            case SM_WATCHDOG:
                return "Watchdog";
            case SM_SYSLOG:
                return "Syslog";
            case SM_REGUPDATE:
                return "Registry Update";
            case SM_WEBSTARTUP:
                return "Web Server Startup";
            case SM_WEBSHUTDOWN:
                return "Web Server Shutdown";
            case SM_PROTCMDMSG:
                return "Protocol Command Message";
            case SM_PROTCMDRESP:
                return "Protocol Command Reesponse";
            case SM_PIPEOPEN:
                return "Pipe Opened";
            case SM_PIPECLOSE:
                return "Pipe Closed";
            default:
                if (type >= SM_USER) return "User Defined Type " + String.valueOf(type);
                else return "Unknown Type " + String.valueOf(type);
        }
    }
}

Not only does the above class define the types but it gives us a nice helper function to get a nice textual description for the message type.

We will create a class that will monitor the message pump. The biggest note to observe is that we MUST forward the message once we receive it so that other processes can see the message. The message pump message delivery is also time sensitive. If we receive the message and perform a lengthy process before re-posting it the we could cause the system to assert as the message pump timing has been violated.

package com.integpg.messagepumpsample;

import com.integpg.system.MessagePump;
import com.integpg.system.SystemMsg;
import java.util.Vector;

public class MessagePumpEngine implements Runnable {

    private static final MessagePumpEngine THIS = new MessagePumpEngine();
    private static final MessagePump MESSAGE_PUMP = new MessagePump();
    private static final Vector LISTENERS = new Vector<>();

    private static Thread _thread;



    /**
     * Singleton constructor
     */
    private MessagePumpEngine() {
    }



    /**
     * adds a listener that will be alerted of all received messages. The listener will be responsible for determining
     * if the message has meaning to them
     */
    public static void addListener(MessagePumpListener listener) {
        synchronized (LISTENERS) {
            LISTENERS.addElement(listener);
        }
    }



    /**
     * starts our message pump engine.
     */
    static void start() {
        if (null == _thread) {
            _thread = new Thread(THIS);
            _thread.setName("message-pump-engine");
            _thread.setDaemon(true);
            _thread.start();
        }
    }



    @Override
    public void run() {
        System.out.println("open the message pump and start monitoring.\r\n");
        MESSAGE_PUMP.open();

        for (;;) {
            // read all messages from the message pump
            SystemMsg systemMsg = MESSAGE_PUMP.getMessage();

            // we must repost as fast as we can
            MESSAGE_PUMP.postMessage(systemMsg);

            // notify all of our listeners
            synchronized (LISTENERS) {
                for (int i = 0; i < LISTENERS.size(); i++) {
                    MessagePumpListener listener = (MessagePumpListener) LISTENERS.elementAt(i);
                    listener.messageReceived(systemMsg);
                }
            }

        }
    }



    /**
     * Exposes the postMesssage method of the System MessagePump
     *
     * @param msg the message the will be sent to the system message pump
     */
    public static void postMessage(SystemMsg msg) {
        MESSAGE_PUMP.postMessage(msg);
    }
}

When the above class receives a message from the message pump it will want to alert any listeners.  Lets define the listener interface now.

package com.integpg.messagepumpsample;

import com.integpg.system.SystemMsg;

/**
 * An interface that outlines the method or methods that all classes that implement this interface must define.
 */
public interface MessagePumpListener {

    public void messageReceived(SystemMsg systemMsg);
}

Now that we have laid the ground work for the application lets glue the peices together.  Here is our Main class

package com.integpg.messagepumpsample;

/**
 * HexUtils will give us access to the hex dump so that we can show a meaningful representation of the data in each
 * message
 */
import com.integpg.janoslib.utils.HexUtils;
/**
 * System classes pertaining to the message pump itself and the class that will be returned from the getMessage call
 */
import com.integpg.system.SystemMsg;
/**
 * A class that will format a date string quickly
 */
import com.integpg.janoslib.text.QuickDateFormat;

/**
 * A class to demonstrate the Message Pump feature in the JNIOR.
 */
public class MessagePumpSampleMain implements MessagePumpListener {

    /**
     * the QuickDateFormat() class is part of a library of classes and are beyond the scope of this example.
     */
    private final static QuickDateFormat QUICK_DATE_FORMAT = new QuickDateFormat("MM-dd-yy HH:mm:ss.fff");



    public static void main(String[] args) throws InterruptedException {
        // instantiate our main class
        MessagePumpSampleMain msgPumpSample = new MessagePumpSampleMain();
        msgPumpSample.init();

        // the main method has nothing left to do.  let it sleep forever.  Most if not all of our other threads 
        // are daemon threads that would not keep the process from sxiting if this main thread completes.  
        // Sometimes we might do somehting in this thread but for the sake of this example we have nothing to do.
        Thread.sleep(Integer.MAX_VALUE);
    }



    public void init() {
        // the message pump engine is a singleton.  We do not have to instatiate another instance.  We can just 
        // use static methods to interface with it.  
        //
        // first we need to add in any listeners that should be alerted when a new message is received.  It 
        // will be up to those llisteners to determine if it cares about the message.
        MessagePumpEngine.addListener(this);
        MessagePumpEngine.addListener(new RegistryMessageHandler());

        // we are ready.  start listening
        MessagePumpEngine.start();
    }



    @Override
    public void messageReceived(SystemMsg systemMsg) {
        // the QUICK_DATE_FORMAT.format() call is part of a library of classes and are beyond the scope of this 
        // example.
        String dateString = QUICK_DATE_FORMAT.format(System.currentTimeMillis());
        // so we have a message.  what kind is it?
        String messageName = SystemMessageTypes.getMessageNameByType(systemMsg.type);
        // print out what we know about this message
        System.out.println(dateString + " - Process Msg: " + messageName);

        // if there is data in the message then we should dump it so that we can see what it is.
        if (0 < systemMsg.msg.length) {
            // the HexUtils.hexDump() call is part of a library of classes and are beyond the scope of this 
            // example.
            String hexDumpString = HexUtils.hexDump(systemMsg.msg);
            System.out.println(hexDumpString);
        }
    }

}

In the above lines 48 and 49 we add our listener classes.  Line 48 is the this object representing the Main class.  You can see what happens in the messageReceived() method starting at line 58.  The other listener is the RegistryMessageHandler class.  Here is the definition for that class.

package com.integpg.messagepumpsample;

import com.integpg.system.JANOS;
import com.integpg.system.SystemMsg;

public class RegistryMessageHandler implements MessagePumpListener {

    @Override
    public void messageReceived(SystemMsg systemMsg) {
        // we only care about registry key updates
        if (SystemMessageTypes.SM_REGUPDATE == systemMsg.type) {
            // get the key name from the message data
            String keyName = new String(systemMsg.msg);
            // now get teh new value
            String keyValue = JANOS.getRegistryString(keyName, "");
            System.out.println("Registry Update: " + keyName + " = " + keyValue);
        }
    }

}

Each time a message is received from the message pump each of our listeners is notified.  Each listener can check the message type to see if it cares about the message.  A sample output from the application shows three interesting events.  First, I opened the web page for the unit.  The web server is NOT always running unless the web server port is accessed.  Once the web server port was hit it caused the web server to spin up and send the Web Server Startup message.  The next two events were registry updates.  The Main class listener shows us that there was a Registry Update along with the HEX dump of the message payload.  Then the RegistryMessageHandler listener used the registry key that was updated to get the new value.  You can download the sample application JAR file below.

  msgpump-monitor.jar [ Jul 25 2018, 23 KB, MD5: 00eb2d3b97bbc67c5d1d13a9901e9fbe ]

The two files below need to be added to the project with the example above for it to run properly.

HexUtils.java

/**
 * A Utility class for dealing with Hex
 */
public class HexUtils {

    public static final char[] HexArray = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
    public static final char[] LowerHexArray = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };



    public static String bytesToHex(byte[] bytes) {
        return bytesToHex(bytes, 0, bytes.length);
    }



    public static String bytesToHex(byte[] bytes, int offset, int len) {
        if (null == bytes) return "null";

        char[] hexChars = new char[len * 2];
        int v, i = 0;
        for (int j = offset; j < offset + len; j++) {
            v = bytes[j] & 0xFF;
            hexChars[i++] = HexArray[(v >> 4 & 0x0F)];
            hexChars[i++] = HexArray[v & 0x0F];
        }
        return new String(hexChars);
    }



    public static byte[] hexToBytes(String hexString, int offset, int len) {
        if (null == hexString) return new byte[0];

        byte[] bytes = new byte[len/2];
        int i = 0;
        for (int j = offset; j < len; j += 2) {
            char c = hexString.charAt(j);
            int n = 0;
            if ('0' <= c && '9' >= c) n = c - '0';
            else if ('A' <= c && 'F' >= c) n = c - 55;
            else if ('a' <= c && 'f' >= c) n = c - 87;
            else throw new RuntimeException("error parsing hex");
//            System.out.println(String.format("%s, %d", String.valueOf(c), n));

            int val = n << 4;

            c = hexString.charAt(j + 1);
            n = 0;
            if ('0' <= c && '9' >= c) n = c - '0';
            else if ('A' <= c && 'F' >= c) n = c - 55;
            else if ('a' <= c && 'f' >= c) n = c - 87;
            else throw new RuntimeException("error parsing hex");

            val += n;
//            System.out.println(String.format("%s, %d, %d", String.valueOf(c), n, val));

            bytes[i++] = (byte) val;
        }
        return bytes;
    }



    public static String hexDump(byte[] bytes) {
        return hexDump(bytes, 0, bytes.length);
    }



    public static String hexDump(byte[] bytes, int offset, int length) {
        StringBuilder sb = new StringBuilder((length / 16 + 1) * 83);

        char[] line = new char[81];
        line[0] = '0';
        line[1] = 'x';
        line[line.length - 2] = '\r';
        line[line.length - 1] = '\n';

        int endAddress = offset + length;
        for (int lineAddress = offset - (offset % 16); lineAddress < endAddress; lineAddress += 16) {
            int charPos = 2;
            line[charPos++] = LowerHexArray[(lineAddress >> 12 & 0x0F)];
            line[charPos++] = LowerHexArray[(lineAddress >> 8 & 0x0F)];
            line[charPos++] = LowerHexArray[(lineAddress >> 4 & 0x0F)];
            line[charPos++] = LowerHexArray[lineAddress & 0x0F];
            line[charPos++] = ' ';
            line[charPos++] = ' ';
            line[charPos++] = ' ';

            for (int linePos = 0; linePos < 16; linePos++) {
                int address = lineAddress + linePos;
                if (address < offset) {
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                } else if (address < endAddress) {
                    int c = bytes[address];
                    line[charPos++] = LowerHexArray[(c >> 4 & 0x0F)];
                    line[charPos++] = LowerHexArray[c & 0x0F];
                    line[charPos++] = ' ';
                } else {
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                }

                if (7 == linePos) {
                    // add 2 spaces between each group of 8 bytes
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                }
            }
            // add 3 spaces between the second group of 8 bytes and the ASCII output
            line[charPos++] = ' ';
            line[charPos++] = ' ';
            line[charPos++] = ' ';

            for (int linePos = 0; linePos < 16; linePos++) {
                int address = lineAddress + linePos;

                if (address < offset) line[charPos++] = ' ';
                else if (address < endAddress) {
                    char c = (char) bytes[address];
                    if (0x20 > c || 0x80 <= c) line[charPos++] = '.';
                    else line[charPos++] = c;
                } else line[charPos++] = ' ';
                if (7 == linePos) line[charPos++] = ' ';
            }

            sb.append(line, 0, line.length);
        }

        return sb.toString();
    }



    public static long parseHexAsLong(String s) {
        if (s == null) return 0;
        int len = s.length();
        // get the address in two stages because some addresses can be too great for the parseLong method.
        long l = Long.parseLong(s.substring(0, len - 8), 16);
        l <<= 32;
        l += Long.parseLong(s.substring(len - 8), 16);
        return l;
    }
}

QuickDateFormat.java

import com.integpg.system.ArrayUtils;
import com.integpg.system.Timebase;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

/**
 * This class constructs a date string with a user defined format. it caches date and time locations to help construct
 * the string faster.
 */
public class QuickDateFormat {

    private static final SimpleDateFormat TIMEZON_DATE_FORMAT = new SimpleDateFormat("z");
    private static final long MILLIS_IN_HOUR = 3600000;
    private static final String[] MONTH_NAME_STRINGS = new String[]{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
    private static final byte[][] AM_PM_STRING_BYTES = new byte[][]{ "am".getBytes(), "pm".getBytes() };

    private final Calendar _calendar = Calendar.getInstance();

    private final byte[] _dateStampBytes;
    public final int DATE_STAMP_LENGTH;
    public final int DATE_STAMP_LENGTH_WITH_COMMA;
    private final String _pattern;

    int _weekOfYearPos = -1;
    int _monthPos = -1;
    int _monthNamePos = -1;
    int _dayPos = -1;
    int _2yearPos = -1;
    int _4yearPos = -1;
    int _hourPos = -1;
    int _hourOfDayPos = -1;
    int _minutePos = -1;
    int _secondPos = -1;
    int _millisPos = -1;
    int _timezonePos = -1;
    int _ampmPos = -1;

    private final byte[] _buf = new byte[8];
    private long _lastTimeRef = 0;

    private long _nextHour = System.currentTimeMillis() - System.currentTimeMillis() % MILLIS_IN_HOUR;
    private long _nextMinute = 0;



    public QuickDateFormat() {
        this("MM-dd-yyyy HH:mm:ss.fff zzz");
    }



    public QuickDateFormat(String pattern) {
        _pattern = pattern;

        _dateStampBytes = new byte[pattern.length() + 2];
        ArrayUtils.arraycopy(pattern.getBytes(), 0, _dateStampBytes, 0, pattern.length());
        _dateStampBytes[_dateStampBytes.length - 2] = ',';
        _dateStampBytes[_dateStampBytes.length - 1] = ' ';

        DATE_STAMP_LENGTH = _dateStampBytes.length - 2;
        DATE_STAMP_LENGTH_WITH_COMMA = _dateStampBytes.length;

        _weekOfYearPos = pattern.indexOf("WY");
        _monthNamePos = pattern.indexOf("MMM");
        if (-1 == _monthNamePos) {
            _monthPos = pattern.indexOf("MM");
        }
        _dayPos = pattern.indexOf("dd");
        if (-1 == _dayPos) _dayPos = pattern.indexOf("DD");
        _2yearPos = pattern.indexOf("yy");
        if (-1 == _2yearPos) _2yearPos = pattern.indexOf("YY");
        _4yearPos = pattern.indexOf("yyyy");
        if (-1 == _4yearPos) _4yearPos = pattern.indexOf("YYYY");
        _hourPos = pattern.indexOf("hh");
        _hourOfDayPos = pattern.indexOf("HH");
        _minutePos = pattern.indexOf("mm");
        _secondPos = pattern.indexOf("ss");
        _millisPos = pattern.indexOf("fff");
        if (-1 == _millisPos) _millisPos = pattern.indexOf("SSS");
        _timezonePos = pattern.indexOf("zzz");
        _ampmPos = pattern.indexOf("aa");

        updateTimezone();
    }



    public String getDateFormatString() {
        return _pattern;
    }



    private void updateTimezone() {
        if (-1 != _timezonePos) {
            String timezoneString = Timebase.getTimeZone(); //TIMEZON_DATE_FORMAT.format(new Date());
//            System.out.println("timezoneString: " + timezoneString);
//            System.out.println("_dateStampBytes.legnth: " + _dateStampBytes.length);
//            System.out.println("_timezonePos: " + _timezonePos);
            ArrayUtils.arraycopy(timezoneString.getBytes(), 0, _dateStampBytes, _timezonePos, timezoneString.length());
        }
    }



    // 2.0
    public String format(Date date) {
        return format(date.getTime());
    }



    public String format() {
        return format(System.currentTimeMillis());
    }



    public String format(long time) {
        synchronized (_dateStampBytes) {
            if (time <= 0) time = System.currentTimeMillis();

            if (_nextHour <= time) {
                _nextHour += MILLIS_IN_HOUR;
                updateTimezone();
            }

            if (_lastTimeRef > time || time >= _nextMinute) {
                initRefBytes(time);
            }

            int delta = (int) (time - _lastTimeRef);
            int rSecond = delta / 1000;

            if (-1 != _secondPos) {
                stuffInt(_buf, rSecond + 100, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _secondPos, 2);
            }

            if (-1 != _millisPos) {
                int rMillis = delta - 1000 * rSecond;
                stuffInt(_buf, rMillis + 1000, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _millisPos, 3);
            }

            return new String(_dateStampBytes, 0, DATE_STAMP_LENGTH);
        }
    }



    // 2.0
    public byte[] toByteArray() {
        return _dateStampBytes;
    }



    private void initRefBytes(long time) {
        _calendar.setTimeInMillis(time);

        int second = _calendar.get(Calendar.SECOND);
        int millis = _calendar.get(Calendar.MILLISECOND);

        _lastTimeRef = time - (1000 * second + millis);
        _nextMinute = time + 60000 - (time % 60000);

        synchronized (_dateStampBytes) {
            if (-1 != _weekOfYearPos) {
                int weekOfYear = _calendar.get(Calendar.WEEK_OF_YEAR);
//        System.out.println("woy: " + weekOfYear);
                stuffInt(_buf, weekOfYear + 100, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _weekOfYearPos, 2);
            }

            if (-1 != _monthPos) {
                int month = _calendar.get(Calendar.MONTH);
                stuffInt(_buf, month + 101, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _monthPos, 2);
            }

            if (-1 != _monthNamePos) {
                int month = _calendar.get(Calendar.MONTH);
                String monthName = MONTH_NAME_STRINGS[month];
                ArrayUtils.arraycopy(monthName.getBytes(), 0, _dateStampBytes, _monthNamePos, 3);
            }

            if (-1 != _dayPos) {
                int day = _calendar.get(Calendar.DAY_OF_MONTH);
                stuffInt(_buf, day + 100, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _dayPos, 2);
            }

            if (-1 != _2yearPos || -1 != _4yearPos) {
                int year = _calendar.get(Calendar.YEAR);
                stuffInt(_buf, year, 0, false);
                if (-1 != _2yearPos) ArrayUtils.arraycopy(_buf, 2, _dateStampBytes, _2yearPos, 2);
                if (-1 != _4yearPos) ArrayUtils.arraycopy(_buf, 0, _dateStampBytes, _4yearPos, 4);
            }

            if (-1 != _hourPos) {
                int rHour = _calendar.get(Calendar.HOUR);
                stuffInt(_buf, rHour + 100, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _hourPos, 2);
            }

            if (-1 != _hourOfDayPos) {
                int rHour = _calendar.get(Calendar.HOUR_OF_DAY);
                stuffInt(_buf, rHour + 100, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _hourOfDayPos, 2);
            }

            if (-1 != _minutePos) {
                int minute = _calendar.get(Calendar.MINUTE);
                stuffInt(_buf, minute + 100, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _minutePos, 2);
            }

            if (-1 != _ampmPos) {
                byte[] amPmBytes = AM_PM_STRING_BYTES[_calendar.get(Calendar.AM_PM)];
                ArrayUtils.arraycopy(amPmBytes, 0, _dateStampBytes, _ampmPos, 2);
            }
        }
    }



    private void stuffInt(byte[] array, int value, int dec, boolean rjustified) {
        ArrayUtils.arrayFill(array, 0, array.length, (byte) ' ');

        if (value == 0) {
            array[0] = (byte) '0';
            return;
        }

        int dig;
        int offset = 0;
        boolean leading = true;

        if (value < 0) {
            array[offset++] = (byte) '-';
            value = -value;
            rjustified = false;
        }

        for (dig = 1000000; dig >= 1; dig /= 10) {
            if (leading && value < dig) {
                if (rjustified) {
                    offset++;
                }
                continue;
            }

            array[offset] = (byte) '0';
            while (value >= dig) {
                array[offset]++;
                value -= dig;
            }
            offset++;
            leading = false;
        }

        for (dig = dec; dig > 0; dig--) {
            array[offset] = array[offset - 1];
            if (array[offset] == ' ') {
                array[offset] = '0';
            }
            offset--;

            if (dig == 1) {
                array[offset] = (byte) '.';
                if (array[offset - 1] == ' ') {
                    array[offset - 1] = '0';
                }
            }
        }
    }
}

This sample shows you how to read a barcode scanner using the AUX port. You must have the correct settings to communicate successfully with your device. The settings for the port may be set using the setSerialPortParams() method. This method can only be called after the port has been successfully opened.

package barcodescanner;

import com.integpg.comm.AUXSerialPort;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;



public class BarcodeScanner {
    private static int BAUD = 19200;
        

    public static void main(String[] args) {
        AUXSerialPort auxPort = null;


        try {
            // init the aux port
            auxPort = new AUXSerialPort();
            // open the port
            System.out.println("Open the AUX Serial Port");
            auxPort.open();
            // set the parameters
            System.out.println("Set port parameters to " + BAUD + ", 8, none, 1");
            auxPort.setSerialPortParams(BAUD, 8, 1, 0);
        } catch (Exception ex) {
            ex.printStackTrace();
            // cant open the serial port so exit
            System.exit(-1);
        }


        try {
            // create our reader
            InputStream is = auxPort.getInputStream();
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);

            // read the barcodes
            System.out.println("Ready to Scan Barcodes...");
            String line = null;
            // read the serial port in a loop to get all of the barcodes.  each barcode is terminated by a 
            // linefeed.  That means we can use readLine()
            while ((line = br.readLine()) != null) {
                System.out.println("Barcode: " + line);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

}

This sample requires that the MODBUS Server is running.

This sample is meant to show you how to gain access to the MODBUS scratch memory space. You can see in the MODBUS manual that the MODBUS addressing assigns a “scratch” address space starting at WORD 256.

Since WORDs are 2 bytes we know that the starting address is going to be 512. We can gain access to the “scratch” area by opening the Modbus00 immutable block. The Modbus00 immutable block does not exist until the MODBUS server has been started on the unit at least once in its lifetime. You can use the nv command to discover whether the Modbus00 block exists or not.

If the Modbus00 block does not exist then execute the MODBUS Server. Once the MODBUS Server has been executed the Modbus00 will exist.

The immutable example shown in the Immutable Class example uses an array of long values. We will use a byte array in this example.

package modbussample;  
  
import com.integpg.system.ArrayUtils;  
import com.integpg.system.Immutable;  
  
  
  
public class ModbusSample {  
  
    public static void main(String[] args) throws InterruptedException {  
        // open the Modbus00 immutable block  
        System.out.println("Open the MODBUS Store");  
        byte[] modbusStore = Immutable.getByteArray("Modbus00");  
  
        // if the block does not exist then alert the user and exit.  it is not our responsibility to create it  
        if (modbusStore == null) {  
            System.out.println("MODBUS store has not been created.  Please run the MODBUS Server application.");  
            System.exit(0);  
        }  
  
  
        // the modbus store must exist.  loop so that external changes by a client are seen by our application  
        while (true) {  
            // get and print the first 4 WORDs in our scratch area  
            short[] value = new short[]{  
                ArrayUtils.getShort(modbusStore, 512),  
                ArrayUtils.getShort(modbusStore, 514),  
                ArrayUtils.getShort(modbusStore, 516),  
                ArrayUtils.getShort(modbusStore, 518)  
            };  
            System.out.println("WORDs @0256: " + value[0] + ", " + value[1] + ", " + value[2] + ", " + value[3]);  
  
  
            // get and print the first 8 bytes that represent the first 4 WORDs in our scratch area  
            byte[] b = new byte[8];  
            ArrayUtils.arraycopy(modbusStore, 512, b, 0, b.length);  
            System.out.println("bytes @0512: " + hexDump(b, 0, b.length));  
  
  
            // sleep a little before checking again  
            Thread.sleep(1000);  
        }  
    }  
  
  
  
    public static String hexDump(byte[] bytes, int offset, int length) {  
        StringBuffer sb = new StringBuffer();  
        StringBuffer chars = new StringBuffer();  
  
        int i;  
        for (i = offset; i  
                < length; i++) { if (i % 16 == 0) { if (chars.length() > 0) {  
                    sb.append("  ");  
                    sb.append(chars.toString());  
                    chars  
                            = new StringBuffer();  
                    sb.append("\r\n");  
                }  
  
            } else if (i % 16 == 8) {  
                chars.append(" ");  
                sb.append("  ");  
            }  
  
            if (bytes[i] >= 32) {  
                chars.append((char) bytes[i]);  
            } else {  
                chars.append('.');  
            }  
  
            int p = sb.length();  
            sb.append(Integer.toHexString((bytes[i] & 0xff) / 16));  
            sb.append(Integer.toHexString((bytes[i] & 0xff) % 16));  
            sb.append(" ");  
        }  
  
        int mod = i % 16;  
        if (mod != 0) {  
            if (mod <= 8) { sb.append(" "); } for (i = 16 - (mod); i > 0; i--) {  
                sb.append("   ");  
            }  
  
            sb.append("  ");  
            sb.append(chars.toString());  
        }  
  
        return sb.toString();  
    }  
  
}  

Here is some sample output from our application. We used a PC MODBUS client to adjust the values of the registers in the MODBUS scratch address space. You can see the values changing during the execution of our application.

This sample shows you how to use the Immutable class. The immutable class represents persistent storage that can be accessed at the low level as an array of primitive types. This example shows the use of an array of longs. The long values in this application are date values that represent when the application was started.

package immutablesample;  
  
import com.integpg.system.Immutable;  
import java.util.Date;  
  
  
  
public class ImmutableSample {  
  
    public static void main(String[] args) {  
  
        // get an array of long values that represent the last 5 boot times  
        long[] lastFiveBootTimes = Immutable.getLongArray("BootTimes");  
  
        // if the array does not already exist then create an array of 5 long values  
        if (lastFiveBootTimes == null) lastFiveBootTimes = Immutable.createLongArray("BootTimes", 5);  
  
  
        // shift the previous 4 boot times  
        for (int i = 4; i > 0; i--) {  
            lastFiveBootTimes[i] = lastFiveBootTimes[i - 1];  
        }  
  
  
        // assign the most recent boot time  
        lastFiveBootTimes[0] = System.currentTimeMillis();  
  
  
        // loop through our last five boot times and print them in a readable format  
        for (int i = 0; i < 5; i++) {  
            long bootTime = lastFiveBootTimes[i];  
  
            if (bootTime > 0) {  
                System.out.println(new Date(bootTime));  
            }  
        }  
    }  
  
}  

Sometimes it is vital to ensure that only one copy of an application is running.  To do that JANOS has provided us the ability to register a process with the operating system.  Using the registerProcess(uid) call we can get the number of processes running with the given unique identifer.  This can become an issue if you have multiple run keys set to start the same application.

Here is the demo application.  The oneinstance application is started with the & parameter.  This tells the console session to run the application as a separate process.  The First time the application run it returns a process count of 1.  The process is allowed to proceed.

Since the application is running as a separate process from the console session we can start another instance now.  The returned count is now 2.  The code checks for a count greater than 1 and decides to exit.

Using this feature will help you be sure that there is only ever one instance of your application running at a time.  Here is the source for the sample application.

package oneinstance;

import com.integpg.system.JANOS;



public class Oneinstance {

    public static void main(String[] args) {
        // we assign a unique identifier to this application.  This can be ANY string.
        String uid = "abcdef";

        // we register the process with JANOS.  this call will return the number of processes 
        //  running with this uid.  The result should be 1 indicating that our process is now running.
        int processCount = JANOS.registerProcess(uid);
        System.out.println("# of processes running using this UID: " + processCount);
        if (processCount > 1) {
            System.out.println("There is another copy of this application already running. exit now.");
            return;
        }

        // now sleep for a while to give us time to start other instances of this application as a test
        try {
            System.out.println("We are allowed to continue as the only instance of this applicaiton");
            Thread.sleep(60000);
        } catch (InterruptedException 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);
        }
    }
}

The classic Hello World application that runs on the JNIOR!

package helloworld;
public class HelloWorld {
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        System.out.println("hello world");
    }
}