Author Archives for Kevin Cloutier

















If you are going, please stop by and visit us at booth 2615A in the Augustus Ballroom.

New for this year are some software updates as we continue to advance the JNIOR automation controller to handle more devices, more communication protocols and to be more robust than ever!  We continue to expand our capability to control LED strip lights, LED down lamps and DMX devices and provide more ways to monitor temperature and humidity.  Integrating the JNIOR with various third party systems such as Q-SYS, Barco, Dolby, Christie, etc. will be getting easier.

Bruce, Kevin  and I will be in our new booth.  If you want to set-up an appointment to meet with us at the show, please reply to this email with a date and time good for you.

Follow INTEG on Twitter for updates: @integpg

This is the first use of our new email system for staying in contact with our customers.  We will be using it to send notices about software updates, new products, etc.  If you were added by mistake or do not want to be on the list, then please reply to this email and we will remove you immediately.  If you know of anybody who would like to be added to our list, please forward this email to them so they can send me their contact information and approval.

See you in Vegas!

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


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

ControlPanel Applicaiton - 7 KB - MD5: c573b4c989ea0380729fecae5283e937

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


package com.integ;

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

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

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



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

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

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

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

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

            }
        }

    }



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

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

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

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

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

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

    }



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



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



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

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

        return bytes;
    }
}


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

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



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

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

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

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

            }
        }
        _enumeration = _parameters.keys();
    }



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



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



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



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



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

}


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

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

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

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

            }
        }

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




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

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

...


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


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

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

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

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

Got a MODBUS device that you want to publish its data to the cloud?  Use the JNIOR and the MODBUS to MQTT application.

MODBUS is a protocol that many legacy systems use.  MQTT is new and works very well to publish data to any MQTT broker whether in the cloud or hosted locally.  The JNIOR can fill the gap and get that older equipment’s data from the equipment to where you need it for analysis.

Set up the MQTT broker

There are several free brokers that you can use to test with.  You can find that list here: https://github.com/mqtt/mqtt.github.io/wiki/public_brokers

Define your slaves and the tags

If your slave is RS485 then you will need to use the model 410 and set up the serial settings to match your device.

Once your setup is complete you will the the data update on the MODBUS to MQTT page.  A message will be sent around the message pump the the MQTT application to publish the data.  The data will be sent to the topic as follows with spaces in the slave name or data tag simply removed.

This application is still a work in progress so please register to leave a comment below!

Sometimes a JNIOR gets re-purposed.  A JNIOR was performing one task and now it is going to perform a new one.  You may want to clean up the JNIOR before configuring it for the new operation.  In this case we will want to sanitize the JNIOR.  To complete this task there will be two steps that will need to be performed.

First, we will need to “erase” the unit.  To do this we will execute the following command;

reboot -eraseall

After confirmation the JNIOR will erase itself.  It will be completely blank.  Only the operating system will be present.  All other software will be removed.  The title says reset to factory but the OS will still be the currently loaded version and will not revert to the version that was shipped.  The IP Address will also remain unaffected.  All other user configuration including the hostname will be cleared.

That leads into the second step.  You will need to get the software package that you wish to install and update the unit with that code.  Use the support tool and the Update tab to do this.

What is MQTT?

This is what MQTT.org says:

MQTT is a machine-to-machine (M2M)/”Internet of Things” connectivity protocol. It was designed as an extremely lightweight publish/subscribe messaging transport. It is useful for connections with remote locations where a small code footprint is required and/or network bandwidth is at a premium.

Here is a great intro to MQTT.  NOTE:  This application is in development and screenshot may appear different when released.  You can download the latest release here.

Load_MQTT_v1.2_16jan2019.zip - 637 KB - MD5: b33ebf0277e02a3612b8795490e1f6ec

 

Use MQTT with the JNIOR

Currently the MQTT functionality is limited but we are always looking for suggestions.  Currently will publish IO status by default.  When the JNIOR boots up and successfully connects to a broker we will report the current status of the digital IO.  Digital inputs will report state, usage meter and counter values.  Relay outputs will report just state and usage meter values.

To successfully connect to a broker you will need to have selected a broker and entered its information.  Information such as the Host URL, port number and whether the connection should be encrypted.  The JNIOR implements MQTT across a TCP connection.  The default port number for MQTT is 1883 when non-secure connection while it is 8883 when using a secured connection.  There are several public test brokers out there.  Do not use any free broker for sensitive information.  Brokers that require sign up will most likely provide a user name and password.

 

Groups!

There are two types of groups, Device Groups and Relay Output Groups.  Both of these concepts are in BETA as of version 1.2 and we are looking for feedback.

Device Groups allow JNIORs to subscribe to topics that contain the Device Group name instead of the Serial Number.  A JNIOR can be part of one or many Device Groups.  By default a JNIOR will subscribe to topics that are meant for it by Serial Number, for instance, jnior/618010001/set/...  The JNIOR may want to be part of the pump stations group for example the JNIOR will subscribe to jnior/pumpstations/set/... topics.

Relay Output Groups allow the specification of more than one relay to be affected at once.  To set relay channel 1 you can publish the jnior/SERIAL_NUMBER/set/digital/outputs/1/state = true.  But maybe you want channels 1 – 3 to be affected at once.  To do this you will define a named Relay Output Group, “lights”, for example.  Then, when the JNIOR receives the jnior/SERIAL_NUMBER/set/digital/outputs/lights/state topic all defined relays will change at once.

 

Enhance the MQTT implementation!

You can enhance the MQTT implementation on the JNIOR with a custom application.  Any topic that the JNIOR receives that is not part of our native implementation will be posted to the message pump for the consumption by another application.  A custom application may also put topics with payload data to the message pump to be published by the MQTT application.  This way the MQTT application performs the protocol logic while the custom application can worry about the logic.

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 - 6 MB - MD5: fe1be1e450c2772a16d090a0d9c37f99

MultipleJniors-1.zip - 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 - 6 KB - MD5: 7f19c6f6a69e39fc795b88444fddfff2

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.

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 = 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 - 23 KB - MD5: 00eb2d3b97bbc67c5d1d13a9901e9fbe

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

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

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