Depending on the application, something going wrong and stopping it from running could lead to big complications. Thankfully, a watchdog can monitor your application and react how you want it to using the Watchdog class. The watchdog can stop, restart, and report on applications not responding to it and more. Watchdogs have configurable timeframes, and when they aren’t updated before the time frame expires they trigger.

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.

Usage

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

View on GitHub

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

01/15/19 08:37:36.361, FTP/10.0.0.27:62888 uploaded /flash/WatchdogExample.jar [115.3 kbps] 
01/15/19 08:37:44.081, WatchdogExample started 
01/15/19 08:37:44.203, WatchdogExample activated 
01/15/19 08:38:14.307, WatchdogExample loop finished 
01/15/19 08:38:18.451, ** Assertion: WatchdogExample watchdog triggered reboot (Line 1278) 
01/15/19 08:38:18.483, ** Terminating: System 
01/15/19 08:38:20.971, ** Reboot on assertion: WatchdogExample 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.  You see that we assign the action in the setAction() method above.  watchdog.setAction(Watchdog.WDT_REBOOT);

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().

Release Notes

  • Added ability to load single CA Certificate to be supplied on TLS connection
  • Support TLS Client Certificate Verification on outgoing connections
  • Added legacy PKCS1 support for externally generated certificates
  • Corrected array issue with processing of deferred email transmissions
  • Corrected memory issue with TAB use on the Command Line

JANOS 1.6.5 Released May 22, 2018

  • Corrected FTP listing issue created by the v1.6.4 release
  • Corrected getRegistryList method memory leak
  • Corrected 412DMX light Flickering
  • Corrected 412DMX NAND Flash processing issue
  • Corrected FTP transfer restart issue

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

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

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

This post talks about the additional use of the message pump. You should have an understanding of how the message pump works before continuing. Please make sure you read the Message Pump Overview first.

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 put these messages on the Message Pump so our Java application can get them. This facilitates full-stack applications on the JNIOR.

The procedure looks like:

  1. Web Page sends a “Post Message” message with a Number, or APP_ID, that the Java application defines as well as some content.
  2. JANOS places that content on the message pump along with the APP_ID
  3. Java application consumes the message and can respond by placing a new message on the message pump with the same APP_ID.
  4. JANOS takes the message for the APP_ID and sends a “Reply Message” to any connected Websocket that has previously sent a “Post Message” to the APP_ID. For a web application to get unsolicited messages from a Java application, it must have previously sent a “Post Message” to that application by using the associated APP_ID.

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 the 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.38 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.

Modbus Registry Bit Address Ranges

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 information will withstand reboots and application terminations. You can view Immutable data by typing the ‘nv’ command in the command line of a JNIOR. If using only one data type, you can express to create an immutable array of that data type, but if you want your array to have mixed data types inside it, you have to use a byte array. 

Using Immutable Blocks

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. The long array is part of an Immutable block, therefore the application start times don’t go away until overwritten or manual removed. This application holds up to the last five application start times.

View on GitHub

I put the built jar file of this example application into the JNIOR’s flash folder and ran it from the Web UI’s console tab. After it has successfully run, I run the application multiple times, and it shows each time it runs a new application start time added to its immutable array.

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 identifier.  This can become an issue if you have multiple run keys set to start the same application.

Here is the demo application.  The one instance 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();
        }
    }

}

The JNIOR is a very flexible and powerful controller. Utilize our bundled or add-on software applications. If those don’t meet your needs, let INTEG quickly develop an application for you.

The JNIOR offers superb functionality with its included and available software. However, if you require a custom application to run on your JNIOR, INTEG can develop it for you, or you can develop it yourself using the JNIOR Software Development Kit (SDK).INTEG has already developed a number of custom applications for a variety of customers. Some of these applications have become our ‘add-on’ applications because they have met the needs of a large group of customers.

Other times the applications have been focused and developed to meet the needs of a specific customer.  After the user requirements are gathered it doesn’t take long for INTEG to deliver something for the customer to test.  Often times we dont get devices sent to the office that the customer wants to interface with.  This makes it tough for INTEG to complete full testing in the office.  Sometimes we write test applications to mimic the communications between the JNIOR and the end device.

If you have an application that you have in mind and want to talk to INTEG about the JNIOR please call the office, 724-933-9350, or email support@integpg.com.  You can also fill out the contact form.

Thank you for your interest in the JNIOR from INTEG.

JANOS supplies a Message Pump wherein messages of various types may circulate between processes.

 

JANOS supplies a Message Pump wherein messages of various types may be circulated between processes or application. These messages may be user defined.
Message numbers below 1024 (0x400) are RESERVED by the system.
The following are the system defined message types.

SM_SHUTDOWN (0x01)

This message is generated by the system prior to shutdown. When received applications MUST forward the message by returning it to the pump and exit in an expeditious fashion. The JNIOR is about to reboot.

SM_PROBE (0x02)

This message is generated by the system periodically. When receive applications MUST forward the message by returning it to the pump. This is used to detect listeners that are no longer responding or that are not properly forwarding messages. The system expects to see this message return to it in a prompt fashion.

SM_GCRUN (0x10)

This message indicates that the Garbage Collection (GC) has completed. When received applications MUST forward the message by returning it to the pump.

SM_WATCHDOG (0x11)

This message is generated by a application watchdog configured to send then message on timer expiration.

SM_SYSLOGMSG (0x12)

System log messages can be sent to an external Syslog Server. This message also passes the log information to listening applications.

SM_PWRLOST (0x20)

When Ride-Thru Power support is available this indicates the lost of external power.

SM_PWRGOOD (0x21)

When Ride-Thru Power support is available this indicates that external power has been restored.

SM_PWRREADY (0x22)

When Ride-Thru Power support is available this indicates that the supply is fully charged and ready to provide maximum holding capacity.

SM_REGUPDATE (0x40)

This message is generated whenever a registry entry is updated or removed. When received application MUST forward the message by returning it to the pump.

SM_WEBSTARTUP (0x60)

Message sent when the Web Server process is activated.

SM_WEBSHUTDOWN (0x61)

Message sent when the Web Server process is terminated.

SM_PROTCMDMSG (0x70)

This message is generated when the JNIOR Protocol receives a custom command message. When received an application MUST either forward the message or provide a SM_PROTCMDRESP response.

SM_PROTCMDRESP (0x71)

This message is generated by an application in response to a SM_PROTCMDMSG command message. It is intended for the JNIOR Protocol server. When received applications MUST forward the message by returning it to the pump.

SM_PIPEOPEN (0x80)

This message is sent by the Web Server when a piped websocket connection has been established. The message contains the client IP Address and Port as well as the target message number.

SM_PIPECLOSE (0x81)

This message is sent by the Web Server when a piped websocket connection has terminated. The message contains the client IP Address and Port as well as the original targeted message number.

SM_USER (0x400)

Lowest allowed user defined message number. Applications that intend to exchange messages SHOULD attempt to define globally unique message identifiers. These must be values from 1024 and up. Message numbers below SM_USER are RESERVED by the system.

The JNIOR Model 410 looks very much like your familiar model 310 but just how similar is it? This insiders’ guide takes you on a tour of the JANOS Operating System highlighting what’s new and what’s different. It’s a technical peek into the next generation of JNIOR.
This Guide is for those who are very familiar with the JNIOR Model 310, 312 or 314. While the new series of JNIOR provides all of the capabilities that you have come to enjoy there are many (sometimes subtle) improvements. It is here that we will outline those for you. Our goal is to remove any mystery, to make you comfortable with the new generation, and to help you efficiently assimilate these devices into your installations.

1 Appearance

First of all the Model 410 looks different but not all that different from the Model 310. It is important that you be able to continue to use the JNIOR 410 in every application where previously you would the 310. For this reason the product housing has not changed; There has been no change to the connections; And, the power supply requirements remain the same.
At the same time it is important that you be able to quickly identify a 410 limiting any possible confusion. To this aim the color scheme used in the labeling has changed. The 410 provides a sleek dark look with its predominantly black front label. In addition the GREEN power LED is no longer green. The model 410 uses a BLUE LED to indicate power which can be easily identified from a distance. Other than this color difference the LEDs serve an identical purpose.

2 Connections

All of the connections to the Model 410 are 100% compatible with the Model 310. This is so you are guaranteed to be able to remove a 310 from its application and directly replace it with the new 410. There is one very subtle change which you would likely be hard-pressed to identify unless we point it out. The tab on the Sensor Port jack is now located upwards. We flipped this connection over because with the JNIOR mounted to the wall and depending on the hardware used to mount it getting your finger under the RJ-l2 plug to press the tab was sometimes difficult. This is certainly one of the least significant differences in the product if not THE least significant but we did promise to outline ALL of the differences.

3 Under the Hood

With the Model 310 there was no real need to remove the cover to access the circuit board inside. For the most part this is true for the 410 as well. Here there are two differences under the hood that should be mentioned.

3.1 Battery

All JNIORs use a 3V non-rechargeable lithium battery cell. This battery is used to maintain the content of the non-flash memory holding that portion of the file system outside of the /flash folder. The battery has a life expectancy of several years. This is especially true when the JNIOR remains in use and powered during most of that period. With the Model 310 this battery is NOT user replaceable.
The Model 410 uses a standard 3V CR2032 coin cell which is readily available wherever batteries are sold. While we have not experienced any significant issue with the life of the batteries used in the 310 they are quite expensive and do not provide the advantages of a removable cell. If your battery has failed you would notice a lack of any entries in the jniorsys.log file prior to the removal of power. The loss of file data may or may not impact your application. With the 410 at least you can easily replace the battery by simply sliding the old one out of the holder and slipping a new one in. The CR2032 coin cell actually has more capacity than the permanent batteries that we have used in the 31X models.

3.2 Configurable Relays

As you know our relay outputs are normally open (N.O.) dry contact. Those familiar with the Model 312, which has 12 relay outputs and 4 isolated digital inputs, may know that there are two relays which can be configured by internal jumper to alternatively function as normally closed (N.C.). We have carried this feature into the Model 410 where Relay Output 1 and Relay Output 2 can be configured to be either N.O. or N.C. simply by moving the associated jumper. All units come factory configured for N.O. operation.

4 Performance

By far the majority of differences in the new Model 410, those beyond what have been mentioned already, become apparent when power is applied. The most significant of which is product performance. The Model 410 is considerably faster and much more responsive than its predecessor.
This key difference becomes immediately obvious when power is applied. You are likely aware that the YELLOW/ORANGE Status LED to the right of the Power LED on JNIORs remains on a during product boot. This is true also for the Model 410 however try not to be distracted by the BLUE Power LED as you may miss the boot indication entirely. The Model 410 operating system (OS) literally boots in a second. The status LED correspondingly merely flashes.
After a second any configured and enabled applications have been lunched and have begun to initialize. The network connection becomes active after about 5 seconds as the hardware completes the normal auto-negotiation with the interconnected hub. This means that after only 5 seconds the Model 410 JNIOR is ready to accept browser connections, process FTP transfers or otherwise handle all network traffic. Even the most complicated applications are generally up and running in no more than 15 seconds.

4.1 Processing Core

The Model 410’s leap in performance comes from a significant upgrade in processor. The internal system clock, which governs the pace of machine instructions, is 3 times faster than the Model 310. Furthermore the new processor uses leading-edge technology to combine a variable length instruction set with an advanced instruction pipeline to achieve a one instruction per clock cycle execution rate. On average the Model 310 requires about 3 clock cycles per instruction. On this basis alone we would expect a 10X speed improvement with the Model 410.
The new processor is 32-bit where the Model 310’s processor was only 8-bit. Numbers are generally represented by programming languages as 4-byte (32-bit) integers. The simple act of adding two integers in an 8-bit system requires dozens of machine instructions whereas with a 32-bit processor it takes just one. In our case this difference is perhaps on average a factor of 20. This would imply that the Model 410 might be as much as 200 times faster than the 310.
In addition, on the Model 410, the operating system code is run out of flash memory internal to the processor which has a 10 nano-second access time. This is extremely fast for flash especially when compared to the external flash used by the Model 310 where access requires 70 nanoseconds. In order for the prior 310 to avoid having to wait for memory, programs are copied to RAM and executed there. The impact on performance in having to copy code and the increased load on memory management cannot be directly quantified.
As a further advantage the new processor has built-in functionality to perform multiplications, divisions and floating point calculations. The benefit of all of this is also difficult to quantify. The bottom line is that on the basis of hardware alone the Model 410 should be some 250 times faster than the 310 if not much more.

4.2 JANOS Operating System

As with any product the operating system brings life to the hardware. This is complex programming that is tightly coupled with the hardware design. In order for INTEG to move the JNIOR line to a new processor platform a new operating system had to be developed. For this new operating system to retain functionality closely matching (and indeed indistinguishable from) the existing 31X line we had to create it completely in-house. Therefore with the new Model 410 we introduce the JNIOR Automation Network Operating System of “JANOS” for short.
In ancient Roman religion and myth, Janus is the god of beginnings and transitions, and also of gates, doors, passages, endings and time. With this in mind the acronym JANOS is appropriate for an I/O system controlling inputs and outputs.
Unlike its predecessor the JniorOS (built upon the Dallas/Maxim TINI OS) which was written in Java, JANOS is written in C language directly generating efficient machine instructions which are optimized for the new processor core. Where previously the Java code interpreted sequences of bytecodes pacing OS performance, the new operating system performs dramatically faster and significantly more efficiently executing directly in machine language.
As a result the new Model 410 out-performs the Model 310 by orders of magnitude opening up possibilities for new and exciting applications.

5 File Storage

Beyond the improvement in processing speed the Model 410 also has the capability to store more file data. By default the area preserved for the /flash folder is 32MB which is over 40 times that provided by the Model 310. This providing ample space to store a fully featured website inclusive of graphics or for the storage of data covering long periods of time in data logging applications. The internal flash memory may even be expanded by special order to as much as 128MB and perhaps beyond.

6 JANOS Command Line

As with its predecessor the Model 410’s command line is accessed through a serial connection to the RS-232 port or via the network through a Telnet connection or application support such a connection. The prompt should be very familiar and all of the commands that you have used to manage your JNIORs are there. There are some differences which will be high-lighted in the next section.

6.1 Editing

The entry of commands has been improved. In addition to the UP/DOWN ARROW command history that JNIOR provides you may now use a number of other editing keys. The RIGHT/LEFT ARROW keys together with the HOME, END, BACKSPACE, DEL and INS keys can be used to flexibly edit and enter commands. This is welcome relief and a definite improvement over the previously limited editing available in the Model 31X series.

6.2 Auto-fill (TAB)

The TAB key is used to auto-fill file names. While entering any command you may type one or more characters representing the beginning of a file or folder name and then use the TAB keystroke to to toggle through potential files and folders matching that criteria. This is very useful to avoid having to enter lengthy file names in their entirety. The TAB key can even be used to construct paths to files located deep in the file system. For instance the sequence
cat f[TAB]/j[TAB][RETURN]
will likely execute the following as if entered directly.
cat flash/jnior.ini[RETURN]
The TAB key also enhances the registry command discussed in the next section.

6.3 Prompt Abbreviation (DEL)

The prompt itself displays the current Hostname together with the current working folder (directory). If you have used the cd command to move about folders in the file system and depending on the length of the Hostname defined, your prompts can be quite lengthy. You might be starting to enter commands 1/3rd of the way across a line and depending on the application used for access may have to contend with command line wrap.
If this becomes an issue, you may now use the DEL key immediately at a new prompt to squelch the display of the Hostname. If you do so, all subsequent command line prompts will omit the Hostname. Once another character is type after the prompt the DEL key will perform its editing function as expected.

6.4 Command History

As with its predecessor the Model 410 provides a command history wherein the UP/DOWN ARROW keys may be used to access and repeat a previously entered command. This is very useful.
The 410 however remembers up to 8 unique command entries sorted so that the UP ARROW supplies the most recently entered commands first. By doubling the number of remembered lines, eliminating duplicates, and sorting by age the command history becomes a more effective time saver. Add this to other editing and auto-fill functionality and you find that the command line is much easier to use.

6.5 Custom Command Creation

The java command is used to execute application programs. As will be covered later these are stored in .JAR files. The Model 410 allows you to execute a program using simply its name. So these two command lines equivalently execute the target application. Note also that file names are case-independent.
java MyApp.jar -debug
myapp -debug
This in effect allows you to create a custom command.

7 Commands

All of the commands available with the Model 310 are also provided in the new Model 410. In many cases the output may be formatted slightly differently, some additional information might be provided, or there may be additional functionality. We will review each command here focusing on significant differences.

7.1 The arp Command (New!)

7.2 The bye Command (New!)

The bye command terminates the current Command session. It is equivalent to the exit command.

7.3 The cat Command

Ctrl-C can be used to interrupt the listing of a lengthy file. If you cat a lengthy file and decide that you do not need to see the whole thing, hit the Control-C key combination to stop the listing.
The cat command can be used with the -h option to dump the content of a file in hexadecimal. This allows you to view the binary content of a file. For example:

JANOS_Rev04 /> cat -h jniorboot.log
00000000  30 38 2f 31 32 2f 31 33  20 31 39 3a 33 36 3a 31  08/12/13 .19:36:1
00000010  31 2e 35 39 38 2c 20 4d  6f 64 65 6c 20 34 31 30  1.598,.M odel.410
00000020  20 2d 20 4a 41 4e 4f 53  20 76 30 2e 38 2e 35 2d  .-.JANOS .v0.8.5-
00000030  72 63 34 2e 31 0d 0a 30  38 2f 31 32 2f 31 33 20  rc4.1..0 8/12/13.
00000040  31 39 3a 33 36 3a 31 31  2e 35 39 39 2c 20 43 6f  19:36:11 .599,.Co
00000050  70 79 72 69 67 68 74 20  28 63 29 20 32 30 31 32  pyright. (c).2012
00000060  2d 32 30 31 33 20 49 4e  54 45 47 20 50 72 6f 63  -2013.IN TEG.Proc
     .
     .
     .

This is useful in debugging applications that may store information in a binary form.

7.4 The date Command

The date command has 3 new options. The -s option disables the use of Daylight Savings Time for the current Timezone; Correspondingly the -d option enables Daylight Savings Time; And, the -v (verbose) option provides additional detail regarding the current time. For example.

JANOS_Rev04 /> date -v
 utc: 1376337540
 Mon Aug 12 15:59:00 EDT 2013
 Current Timezone is EST for the America/New_York area.
 Abbrieviated EDT when Daylist Savings is in effect.
 Daylight Savings Time begins at 02:00 on the Sun on or after Mar 8th.
 Daylight Savings Time ends at 02:00 on the Sun on or after Nov 1st.
 When in effect Daylight Savings Time sets clocks ahead by 1 hour.
 Daylight Savings Time is currently in effect.
JANOS_Rev04 />

7.5 The extern Command

The extern command manages external devices. In JANOS it remembers and displays the addressing and type for each module used with the unit.

JANOS_Rev04 /> extern
  TypeFB_1 = CD111090708109FB  present
  TypeFB_2 = BE111120220410FB  not present
  TypeFB_3 = 79111130517082FB  not present
  TypeFA_1 = C7100511100083FA  not present
  TypeFE_1 = 23111130619007FE  not present
  TypeFD_1 = 4B111110510241FD  not present
JANOS_Rev04 />

Note that the first two TypeFB (4ROUT) devices (TypeFB_1 and TypeFB_2) extend the Relay Output functionality of the unit. On the 410 the first represents Relay Outputs 9 through 12 and the second 13 through 16. JANOS like its predecessor works to maintain the proper association between the Relay Output and the physical module. This is done through this addressing.
Unlike its predecessor, the Model 410 does not automatically forget modules should they be removed and the unit rebooted. This greatly reduces the risk that the order of 4ROUTs and their associated Relay Outputs be confused and improperly assigned. If you do need to reset this addressing, use the -r option as follows:
extern -r
This will ‘remove’ any modules that are no longer present. The 4ROUTs will be reassigned in the order that they are detected.
The Model 410 also scans for new modules and verifies existing modules every 5 seconds. A reboot is no longer required (or use of the extern command) to detect newly connected devices.
Procedure for Assigning 4ROUT Modules
1) Remove all modules.
2) Issue the extern -r command.
3) Connect the 4ROUT module to be associated with Relay Output channels 6 through 12.
4) Wait 5 – 10 seconds (or use extern command until you see that the module is assigned).
5) Connect the 4ROUT for channels 13 through 16.
The 4ROUT and Power 4ROUT modules are TypeFB and are interchangeable as far as channel assignments are concerned. All other modules are addressed directly by their address in all protocols.

7.6 The iolog Command (New!)

7.7 The jar Command (New!)

7.8 The jrupdate Command (New!)

7.9 The manifest Command (New!)

7.10 The mode Command (New!)

7.11 The nv Command (New!)

7.12 The reg Command (New!)

The reg command is an alias (abbreviation) for the registry command.

7.13 The registry Command

Listing Registry Content
The registry command can now be used with wildcards to list matching Registry entries. Wildcards adhere to the DOS standards using ‘?’ and ‘*’ wilds. For example:

JANOS_Rev04 /> reg Ip*
    IpConfig/Hostname = JANOS_Rev04
    IpConfig/DHCP = disabled
    IpConfig/IPAddress = 10.0.0.71
    IpConfig/SubnetMask = 255.255.255.0
    IpConfig/GatewayIP = 10.0.0.1
    IpConfig/PrimaryDNS = 10.0.0.4
JANOS_Rev04 />

Auto-fill Registry Keys
The TAB key can be used to auto-fill Registry Key names. For example:
reg Ip[TAB]/[TAB][TAB][TAB] =
This results in the following command line wherein you may complete it by adding the IP address.
reg IpConfig/IPAddress =
Note the use of repeated TAB keys to toggle through the various subkeys to end up with the one you want. The keys appear in alphabetical order and if you continue through all available the original command line re-displays. You may then proceed through the list again. This only works for existing defined keys. To define a new key you will need to type it out.
Recall Current Value
You may use the TAB key immediately following the ‘=’ to recall the current value of an existing Registry key. For example:
reg IpConfig/IpAddress =[TAB]
This results in the following line which may then be edited if desired.
reg IpConfig/IPAddress = 10.0.0.71
Specifying Files
The TAB key functions as in any other command line when used following the the ‘=’ sign but not immediately after. For example:
reg Run/TaskManager = f[TAB]/T[TAB][RETURN]
This results in the following entry and can be used to easily define the key to start TaskManager.
reg Run/TaskManager = flash/TaskManager.jar
Combined with the newly available LEFT/RIGHT ARROW, BACKSPACE, DEL and INS editing this can greatly improve the command line experience.

7.14 The touch Command (New!)

7.15 The usermod Command (New!)

7.16 The users Command (New!)

8 User Management

9 Registry

10 Web Server

The JANOS Web Server introduces 2 new enhancements in addition to improvements in performance. The JANOS Web Server supports built-in websocket functionality as well as a form of server-side scripting consistent with the Hypertext PreProcessor (PHP).

10.1 Websockets

JANOS allows a web connection to be promoted to the websocket protocol through the HTML port (default 80). In this case JSON formatted messages can be exchanged over a single persistent connection with the client browser providing AJAX type services in support of dynamic web pages. This offers an alternative to the older Java Applets and implements the approach which has become the norm for many websites.

10.2 Server-side Scripting

The JANOS Web Server implements a small subset of the well-known scripting language known as PHP. Documented separately this server-side scripting function complements websocket and dynamic HTML by providing the ability to generate context specific web content on demand.

11 JAVA Applications Programming

12 I/O Logging

13 External Modules

14 Firmware Updates

A key advantage with the new Model 410 is the ability to update 100% of the operating firmware. With its predecessor, the Model 31X series, a percentage of the operating firmware is supplied by a third party in binary form. It was not possible to field update that portion of the JNIOR Model 310 code. We in fact had not changed that portion of the operating system through the entire life of the Model 31X product. This has forced us to work-around some (permanent) deficiencies that had been discovered over the years.
JANOS on the other hand was completely developed by INTEG and this includes every single byte of operating code. Correspondingly we are able to update the system in its entirety. We are able to service an issue that may arise and better yet, we are able to extend the function of the operating system in any way conceivable.
Each Model 410 may be updated manually or through programs like the JNIOR Support Tool. The firmware update is supplied in a .UPD file which must first be copied into the JNIOR file system using FTP. This file is typically between 600KB and 700KB and so it is recommended that it be transferred to the /flash folder where the 410 provides ample space. Unlike its predecessor the Model 410 does not automatically detect the .UPD file on boot nor does it automatically remove the file after updating. The jrupdate command is used to install the update. For example:
jrupdate -u flash/filename.upd
This initiates the firmware update. Note that a reboot will be required to complete the OS replacement.

14.1 Java System Library

The Java library is stored in the /etc/JanosClasses.jar file. This is the system built-in library residing in the Read-Only /etc folder. This contains all of the base classes required to build Java applications to run on the JNIOR Model 410. A .UPD file my optionally carry new content for the /etc folder. Note that the folder is replaced immediately upon execution of the jrupdate command in the form shown above.
Since Java applications cache referenced classes, the library .JAR can be swapped while Java applications are running. An application may throw an unexpected exception if it should attempt to load a new class during the update. If this is a concern you might want to stop any running application before performing the update.

14.2 OS Update and Rollback

The Model 31X series maintained two JniorOS images, the field update and the original factory installed OS. These, of course, are images of that part of the OS that can be field updated. It is possible with the 310 to rollback to the factory installed OS. We have found that doing so is rarely desirable given that the factory installation can quickly become outdated. It is likely that JNIOR applications would fail to run under the original OS. The rollback in this case has not been recommended and practically never used.
The Model 410 on the other hand also supports two JANOS images one of which is the currently executing operating system. The other image is a copy of the previously installed version. The rollback then becomes seriously useful in that it will restore any previous version of OS which can be assumed to have been recently operational unlike a potentially aged original factory version. The jrupdate -r command performs the rollback.
In actuality the jrupdate -r command schedules a swap of the OS images on reboot. In this case on reboot the “Saved OS” becomes the executing OS and the previously running OS is “saved”. One can use the jrupdate -r command to toggle between an updated version of JANOS and a prior version. The stats command displays the current and saved versions of the operating system.
The jrupdate -u command as described above merely overwrites the “Saved OS” image with the supplied update and schedules the OS swap on the next reboot. The jrupdate -c command can be used to cancel any scheduled swap.
The Model 410 then performs any scheduled OS swap on boot bringing up the desired copy of JANOS in its entirety. While we recommend that the reboot be caused using the reboot command it is not strictly required with the new JNIOR. The Model 410 has been designed to perform properly in the face of the practice of pulling power to force a reboot. This is not the case with the 31X series where issues (namely the loss of configuration changes) may result if the reboot command is not used. We continue to recommend that the reboot command be used with with all Model 310, 312 and 314 JNIORs.

14.3 Removing Power During Firmware Update

You might be familiar with the warning: “Updating firmware DO NOT remove power.” if you, like everyone else, use new products that support network firmware updates. The assumption is that if you remove power and their update process has not completed you will be left with a product with half an operating system which is potentially no longer operational. This is not a concern with the Model 410 JNIOR.
On reboot the Model 410 handles a scheduled OS swap. The JANOS images are indeed physically swapped in program flash memory. If a traditional memory copy operation were employed then we would indeed need to warn you. But the Model 410 uses an algorithm for the swap that insures success even if you flip power off and on as fast as you can possibly do so during the procedure. Try it!
The YELLOW/ORANGE status LED flashes slowly during the swap which typically takes only a few seconds. The updated JANOS will subsequently boot in just another second no matter the stability of power during the swap. This was a critical concern because otherwise if power is pulled to effect the reboot and not restored decisively a lazy update process would risk failure. This risk is unacceptable and unlike other products we address the issue head-on with a fault-tolerant update procedure so you can update with confidence.

15 Safe Mode

The Model 410 may be started in SAFE MODE. In this mode applications that are programmed to start automatically through Registry “Run” keys are not started.
In order to access SAFE MODE a jumper must be inserted onto the pins accessible through the small opening between the Ethernet connector and the RS-232 Command Port. The unit is then rebooted or powered up. When the command line mode is subsequently accessed either through the serial connection or via the network, “SAFE MODE” will be indicated below the welcome banner. This is the only indication that the mode has been enabled. The jumper must be removed and the unit must be rebooted in order to exit SAFE MODE.
Note that you may ‘borrow’ a jumper from the N.O./N.C. relay jumpers if you remove the unit’s cover. Do so only if disconnecting that relay will not adversely affect any system connected to it. Use a unused channel if available. Once you are done with SAFE MODE be sure to return the jumper to the original position. Jumpers placed close to the relay output connector are set for Normally Open (N.O.) operation (default).
There are two situations in which SAFE MODE is useful.

15.1 Application Generated Boot Loops

If an application that is programmed to automatically start upon boot misbehaves and immediately causes a reboot, a boot loop will result. In this case the JNIOR will rapidly reboot and it will be impossible to regain control of the unit through normal means. The solution is to insert the SAFE MODE jumper. On the next reboot the application will not be restarted and you will be able to log into command line mode. Once at the command line you can proceed with debugging. The SAFE MODE jumper should be removed and you might want to also remove the application’s “Run” key until you are certain the issue is resolved.

15.2 Forgotten Administrator Username or Password

If you have lost and forgotten the administrator’s password (‘jnior’ user for instance), you will need to contact INTEG to obtain the “backdoor” password for your unit. This password will allow you to log into all accounts (even disabled accounts) but only in SAFE MODE. Once you have logged into the administrator’s account you should use the passwd command to change the administrator’s password.
In case you have removed the default administrator’s accounts (‘jnior’ and ‘admin’) and replaced them with your own account name which now you may have forgotten, SAFE MODE will restore a DISABLED account for ‘jnior’ with the standard default password. You may log into disabled accounts in SAFE MODE using the backdoor password. Use the ‘jnior’ account to manage your user accounts.
A unit should not be left in SAFE MODE as this enables the backdoor password wherever a password is requested. That means that it would be valid for web page login, FTP, etc. as well. This would represent a serious security concern. Remember to remove the SAFE MODE jumper once you are done and reboot!

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

Write Outputs

Sometimes you want to control the outputs. The outputs may be wired to things like lights, sirens, valves and maybe fans. (Note: You may need our Power 4 Relay Output Module for high loads).  This example will show you how to set the output states programmatically.

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

The JNIOR is a very flexible and powerful controller. Utilize our bundled or add-on software applications. If those don’t meet your needs, let INTEG quickly develop an application for you.

The JNIOR offers superb functionality with its included and available software. However, if you require a custom application to run on your JNIOR, INTEG can develop it for you, or you can develop it yourself using the JNIOR Software Development Kit (SDK).INTEG has already developed a number of custom applications for a variety of customers. Some of these applications have become our ‘add-on’ applications because they have met the needs of a large group of customers.

Other times the applications have been focused and developed to meet the needs of a specific customer.  After the user requirements are gathered it doesn’t take long for INTEG to deliver something for the customer to test.  Often times we dont get devices sent to the office that the customer wants to interface with.  This makes it tough for INTEG to complete full testing in the office.  Sometimes we write test applications to mimic the communications between the JNIOR and the end device.

If you have an application that you have in mind and want to talk to INTEG about the JNIOR please call the office, 724-933-9350, or email support@integpg.com.  You can also fill out the contact form.

Thank you for your interest in the JNIOR from INTEG.

NOTE: Before creating the setup described in this post on your JNIOR 410, please download the update project below and install it on your 410 using the JNIOR Support Tool. This is required to get the JNIOR to create a DMX connection.

Name Version Release Date Size MD5
DmxPort for enabling DMX on 410 Mar 17 2021 3.5 KB 299c66717c03a9c9b702716d9d56d095

This article is the first of two addressing the issues encountered in, and a means to simplify, the processing of the DMX Universe data stream using a standard UART serial receiver. The follow-up article is entitled JNIOR as a DMX Fixture Revisited.

The Model 412DMX generates a DMX512 Universe and allows the JNIOR to control DMX fixtures like those used in stage lighting. What if you needed a fixture with relays that can be controlled by DMX? Perhaps you need to output channels over a 4-20ma loops. Maybe you need a 10 VDC output signal to control LED house lighting. Can the JNIOR receive DMX? Can the JNIOR be a DMX Fixture?

We showed you how you could control DMX fixtures with a standard Model 410 in a White Paper available here:

  AN01 DMX512 Implementation [ Jul 20 2017, 101.27 KB, MD5: e1b0203f177d1866e56cfbfdd0e221d4 ]

Now we have the 412DMX JNIOR designed for that purpose. Can the Model 410 also serve as a DMX fixture? Yes, it can. I’ll show you how here and we’ll see how we manage to accommodate some of the unique aspects of the DMX512 format with the JNIOR.

Cabling

We can use the JNIOR Model 410 because the AUX port is compatible with RS-485. In the white paper explaining how the 410 can be used to control DMX fixtures we described an adapter cable taking the DB9 output from the JNIOR and presenting the proper female XLR connector for DMX. Now since a DMX fixture always has both a male and female 5-pin XLR connectors, our cabling has to be slightly different. Note that you can do this with the 3-pin XLR (as I have) if that is appropriate for your situation.

Here is an example of one that we put together.

modified Series 4 DMX AUX port cable

This can be constructed by splicing into a standard DMX extension cable. A number of DB9 adapters with screw terminals like the one pictured can be found on Amazon. Note that you will want one with large screws compatible with larger wire sizes. DMX wiring is typically of a larger diameter and you will need to successfully clamp two wires in each of three positions on the adapter.

Here is the pin numbering. Note that wire colors vary.

        Signal           XLR      DB-9 Male
--------------------  ---------  -----------
Signal Ground (GND)       1          5
Data (D-)                 2          2
Data (D+)                 3          8
Not Used (NC)            4,5     1,3,4,6,7,9

This cable allows the JNIOR to be a DMX FIXTURE.

THE RESULTING DMX CONNECTION IS NOT ISOLATED. We recommend using an isolated power supply for the JNIOR and not sharing that voltage with other circuits. Take great care in making ground connections. Note that the JNIOR relay outputs are naturally isolated.

Serial Connection

Connect the adapter to the Model 410 AUX serial port as I have in this photo and connect this to the DMX network. Note that the 412 and 414 are not RS-485 compatible and cannot be used for this purpose.

modified Series 4 DMX AUX port cable

The serial port parameters should be set as follows. This is done through the Dynamic Configuration Pages (DCP) that should come up when accessing the JNIOR using your browser. You enable the RS-485 mode here so the AUX port output doesn’t disrupt the DMX communications before you have a chance to run the DMXFIXTURE application that I will describe. That application will also configure the AUX port just to make sure that all is well.

JNIOR Aux port settings

If you encounter “Applets” instead of the DCP then your Series 4 needs to be updated or you have a Series 3. The latter also cannot be used for this application. You will need to update your JNIOR to JANOS v1.6.6 or later for the functionality to be described here.

Data

With the JNIOR Model 410 wired to the DMX network and the AUX serial port properly configured the unit should be receiving data. There is a simple way to check that. You can see data without any application running just by using the IOLOG command. Here we enter the Console (or Command Line Interface) and use this command.

InfoComm_LED /> help iolog
IOLOG

Options:
 -T             Indicate transitions
 -R             Reset logs
 -A             AUX Serial log
 -S             Sensor Port log
 -O             Output to stdout

Generates jniorio.log file from available logs.

InfoComm_LED /> iolog -ao
--  07/02/18 15:42:46.098
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--80--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--FF--00--FF--80--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--83--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--80--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--FF--00--FF--80--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--83--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--80-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--FF--00--FF--80-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--83--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-80--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--FF--00--FF-    ................
-80--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--83--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-    ................
-00--00--00--00--00--00--00--00--00--00--00--00--00--00--00-        ...............

InfoComm_LED />

If you scan down in the above output and look through the data you will see that there are a couple of channels at 100% (0xFF) and a couple near half (0x80’ish). There are two pretty major issues in trying to read these bytes with standard library read() functions.

  1. How do we know how to find Channel 1? There are many more than 512 bytes shown here. If you read 512 bytes what you get could start anywhere.
  2. The data rate at 250 Kbaud supplies 44 complete channel sets per second! That absolutely will overrun the buffer before you can process any of it. The overrun would likely further obfuscate the data.

The fact is that the standard serial communications routines that you may be used to are just not usable here. JANOS will come to the rescue. But first let’s take a look at the data stream so we understand how this is to be resolved.

DMX Format

The DMX data on the RS-485 lines conforms to standard asynchronous serial data with 8 data bits, 2 stop bits and no parity. The bits are marched out from least significant (LSB) D0 to the most significant (MSB) D7. Each byte is called a “Slot”. The standard implementation transfers a START CODE and 512 channels in a total of 513 slots. The START CODE for normal DMX data is 0x00.

The beginning of the sequence is signaled with a Break Condition. This BREAK can be detected by the fixtures which allows them to synchronize with the stream. After the BREAK comes the START CODE (0x00 – NULL START) followed by the value for Channel 1 on through to Channel 512. Not all 512 channels need to be part of the transmission. The number of channels may vary by DMX controller. The complete implementation provides all 512.

On the oscilloscope the BREAK looks like this. Here all of the channels are 0X00 and so you see only the STOP BITS. That long low pulse is the BREAK.

The issue is that the BREAK is difficult to handle with the standard serial port. It results in a FRAMING ERROR. During the break the signal is held at a low level. When the receiving serial UART expects the STOP BITS and they aren’t there it throws a FRAMING ERROR. While that can be detected and your application can be notified there still is no way to insure that the next bytes read from the port are those that follow the break. They may have been buffered some time before. They may be overrun by oncoming data.

In order to handle this and properly capture a reliable channel set, there must be a special function for that purpose in the AUX port class (AUXSerialPort). Of course, being the author of JANOS, I have implemented exactly what we need. And, those details are next…

Packet Capture

To read the DMX Packet (START CODE plus up to 512 Slots/Channels) we need to detect the Break Condition and then reliably collect as many as 513 serial bytes that immediately follow. Under many other RTOS implementations we would need to write an interrupt driven routine both to detect the Break condition and then also to collect the data. The JNIOR executes application programs written in a managed language (Java) and one does not have low level access to write things like serial interrupt routines. That is actually a good thing as the user generally does not have the programming experience. Such low level user programming often leads to unstable/unpredictable operation.

Here we rely on JANOS to maintain reliable operation. Low level interrupt routines have already been implemented to buffer incoming serial data and otherwise issue a notification of errors. Recall that the Break Condition manifests itself and one or more FRAMING ERRORS. But we have already established that reading buffered serial data and receiving asynchronous notifications is not going to be sufficient for capturing a DMX packet. This is where we benefit from having developed JANOS in-house and having authored 100% of it. Here we identify a need and are able to promptly and correctly implement a solution.

AUXSerialPort.readAfterBreak(byte[] buffer)

I have added the readAfterBreak() method to the AUXSerialPort class in the JANOSClasses.jar library. From the naming its use is self-explanatory. Here you create a buffer as a byte array and pass it to JANOS. The operating system enables the capture and then blocks the thread until the data collection completes. At the low-level JANOS sets up the buffer with a pointer and goes into a kind of ‘armed’ state. The interrupt routine that detects FRAMING errors has a tiny bit of code that checks for an armed capture and ‘triggers’ the collection of data. The interrupt routine that collects and buffers serial bytes from the port has code to set each byte aside into the buffer that you have provided. Once triggered the capture passes into the a ‘collection’ mode. When the buffer is full (or when another Break Condition is detected) the capture is ‘complete’ and the application program can proceed now with a byte array containing the DMX data.

Now to benefit from this new feature, you will need to update your Series 4 to run JANOS v1.6.6 or later. At the moment this is Beta code. We would make it available if you were to want to try this before its release. All you need do is ask.

Next we need to try it out…

DMX Capture Test

Here we create a project in Netbeans (making the few settings needed to target it for JANOS) and create the following test program. This merely takes control of, and fully configures, the AUX port in case it has not been configured through the DCP. Lines 23 and 24 test our new method. The rest merely dumps the byte array content for review.

package dmxfixture;
 
import com.integpg.comm.AUXSerialPort;
import com.integpg.comm.SerialPort;
 
public class Dmxfixture {
 
    public static void main(String[] args) throws Throwable {
 
        // AUX port access and configuration.  We need to open the port to gain exclusive access and
        //  set the proper baud rate and format.  We enable RS-485 mode and make sure that the receivers
        //  are enabled.  With normal RS-485 you would disable the transmit drivers.  Our adapter doesn't
        //  bridge the transmit and receive lines anyway and the DCP configuration automatically disables
        //  the drivers.  It is here for clarity.
        AUXSerialPort aux = new AUXSerialPort();
        aux.open();
        aux.setSerialPortParams(250000, 8, 1, SerialPort.PARITY_NONE);
        aux.setRS485(true);
        aux.enableReceivers(true);
        aux.enableDrivers(false);
        
        // capture a complete frame using our new method
        byte[] data = new byte[513];
        aux.readAfterBreak(data);
        
        // The remainder here is a fancy dump (skipping the START CODE).  Note how JANOS implements the
        //  printf formatting for us.
        for (int i = 1; i < data.length; i++) { 
            if (i % 10 == 1)
                System.out.printf("%04d  ", i);
            System.out.printf("%4d ", data[ i ] & 0xff);
            if (i % 10 == 0)
                System.out.println("");
        }
        System.out.println("");
    }
    
}

To run this we first build it in Netbeans. Then using the DCP we open the Folders tab and select the /flash folder. We then drag the dmxfixture.jar file from the project to the /flash folder (it can be executed from the root too). Then under the Console tab we log in and execute the application. The following is the result.

InfoComm_LED /> dmxfixture
0001     0    0    0    0    0    0    0    0    0    0 
0011     0    0    0    0    0    0    0    0    0    0 
0021     0    0    0    0    0    0    0    0    0    0 
0031     0    0    0    0    0    0    0    0    0    0 
0041     0    0    0    0    0    0    0    0    0    0 
0051     0    0    0    0    0    0    0    0    0    0 
0061     0    0    0    0    0    0    0    0    0    0 
0071     0    0    0    0    0    0    0    0    0    0 
0081     0    0    0    0    0    0    0    0    0    0 
0091     0    0  255    0  255  128    0    0    0    0 
0101     0    0    0    0    0    0    0    0    0    0 
0111     0    0    0    0    0    0    0    0    0    0 
0121   131    0    0    0    0    0    0    0    0    0 
0131     0    0    0    0    0    0    0    0    0    0 
0141     0    0    0    0    0    0    0    0    0    0 
0151     0    0    0    0    0    0    0    0    0    0 
0161     0    0    0    0    0    0    0    0    0    0 
0171     0    0    0    0    0    0    0    0    0    0 
0181     0    0    0    0    0    0    0    0    0    0 
0191     0    0    0    0    0    0    0    0    0    0 
0201     0    0    0    0    0    0    0    0    0    0 
0211     0    0    0    0    0    0    0    0    0    0 
0221     0    0    0    0    0    0    0    0    0    0 
0231     0    0    0    0    0    0    0    0    0    0 
0241     0    0    0    0    0    0    0    0    0    0 
0251     0    0    0    0    0    0    0    0    0    0 
0261     0    0    0    0    0    0    0    0    0    0 
0271     0    0    0    0    0    0    0    0    0    0 
0281     0    0    0    0    0    0    0    0    0    0 
0291     0    0    0    0    0    0    0    0    0    0 
0301     0    0    0    0    0    0    0    0    0    0 
0311     0    0    0    0    0    0    0    0    0    0 
0321     0    0    0    0    0    0    0    0    0    0 
0331     0    0    0    0    0    0    0    0    0    0 
0341     0    0    0    0    0    0    0    0    0    0 
0351     0    0    0    0    0    0    0    0    0    0 
0361     0    0    0    0    0    0    0    0    0    0 
0371     0    0    0    0    0    0    0    0    0    0 
0381     0    0    0    0    0    0    0    0    0    0 
0391     0    0    0    0    0    0    0    0    0    0 
0401     0    0    0    0    0    0    0    0    0    0 
0411     0    0    0    0    0    0    0    0    0    0 
0421     0    0    0    0    0    0    0    0    0    0 
0431     0    0    0    0    0    0    0    0    0    0 
0441     0    0    0    0    0    0    0    0    0    0 
0451     0    0    0    0    0    0    0    0    0    0 
0461     0    0    0    0    0    0    0    0    0    0 
0471     0    0    0    0    0    0    0    0    0    0 
0481     0    0    0    0    0    0    0    0    0    0 
0491     0    0    0    0    0    0    0    0    0    0 
0501     0    0    0    0    0    0    0    0    0    0 
0511     0    0 

InfoComm_LED />  

We note that channels are correct. Here we go over to the 412DMX controlling this DMX network and check Kevin’s DMX panel page for comparison.

DMX control panel

Putting it to Work

Now we can receive a DMX frame and read the individual channels what can we do with it? I mean other than dump it?

Well Kevin has defined an eight channel fixture starting at DMX channel 121. The idea being that each channel would correspond to a JNIOR Relay Output. Channel settings from 0-127 would result in an open/off relay and values in the range 128-255 would close the relay. You can imagine any use that you would want given the flexibility that you now have in JNIOR programming. Let’s implement this particular fixture.

The approach will be to sample a DMX packet periodically and set the relays appropriately. There is no need to catch every DMX packet and in fact we are not likely going to be able to do that. We are also going to be considerate of the JNIOR CPU and anything else that the unit might want to be doing. We will sample say every 1/4 second and sleep in between.

Here is the program. This uses an infinite loop to sample the DMX stream about 4 times a second. The starting address must be defined in the Registry. This could be cached. With this implementation you can change the starting address without rebooting or restarting the DMXFIXTURE program. It is presume that you would start the DMXFIXTURE program automatically at boot with a Registry Run key.

package dmxfixture;
 
import com.integpg.comm.AUXSerialPort;
import com.integpg.comm.SerialPort;
import com.integpg.system.JANOS;
 
public class Dmxfixture {
 
    public static void main(String[] args) throws Throwable {
 
        // AUX port access and configuration.  We need to open the port to gain exclusive access and
        //  set the proper baud rate and format.  We enable RS-485 mode and make sure that the receivers
        //  are enabled.  With normal RS-485 you would disable the transmit drivers.  Our adapter doesn't
        //  bridge the transmit and receive lines anyway and the DCP configuration automatically disables
        //  the drivers.  It is here for clarity.
        AUXSerialPort aux = new AUXSerialPort();
        aux.open();
        aux.setSerialPortParams(250000, 8, 1, SerialPort.PARITY_NONE);
        aux.setRS485(true);
        aux.enableReceivers(true);
        aux.enableDrivers(false);
 
        // here we create an infinite loop to continuously process the DMX data
        byte[] data = new byte[513];
        for (;;) {
            
            // capture a complete frame
            aux.readAfterBreak(data);
            
            // Obtain the starting address.  If it is invalid or not defined no action is taken.
            int addr = JANOS.getRegistryInt("DMX/Address", 0);
            if (addr > 0 && addr < 505) {
                
                // Although we don't have to we are going to collect all of the relay states
                //  and set them simultaneously.  This will also take advantage of signed values
                //  in Java.  Values in the range 128-255 will appear to be negative if we don't
                //  mask them with 0xff.
                int bits = 0;
                for (int i = 0; i < 8; i++) {
                    if (data[addr++] < 0)
                        bits += (1 << i);
                }
                JANOS.setOutputStates(bits, 0xff);
            }
            
            // sleep for a quarter second
            System.sleep(250);
        }        
    }
    
}

This program should be pretty easy to follow. Let’s test it.

Demonstration

A video can best demonstrate the operation of this program. Here we have a DMX application running on a 412DMX (10.0.0.242) allowing us to vary the channels that we associate with our 410 fixture. A separate Model 410 running our DMXFIXTURE (10.0.0.250) program can be monitored remotely through its DCP page. Here we overlap the two browser entities and we can see how modifying the channel fader results in the relay status change out across the DMX network.

Reliability

Let’s look into potential error conditions and the reliability of this approach. The DMX format typically supplies nearly 44 frames per second. If there is a communications error, due to electrical noise for instance, one and possibly up to a few frames might be in error. For a light fixture this might cause a minute flicker or some small flinch in pointing. But, given the frame rate it is quickly corrected and might not be even noticeable. If we are interpreting a frame with our program we need to be extra careful not to trigger a chain of events based upon an error packet.

Typically in data protocols we would have some form of checksum or CRC which we can use to identify an erroneous transmission so it can be ignored. There is no such thing in the DMX512 protocol. So what steps can we take?

Well to start we should verify that the START CODE is the expected NULL START 0x00 and ignore any frame with a different code. The controller might actually be inserting those and we must ignore them. I will adjust the program to check this.

Well… The START CODE is returning 128 (0x80) and the channels appear to be properly registered (e.g. in the right place). Now to look into this.

Synchronization After Break

The DMX512 specification defines the width of the Break Condition as something greater than 92 microseconds. It is important to note that it is something greater than twice that of a single slot time (the time to receive a single byte) of 44 microseconds (11 bit times – start bit, 8 data bits and 2 stop bits). It is not a precise multiple of slot times or even bit times. This forces the receiver to synchronize with each and every packet.

Given this I could make the argument that the Mark After Break should be at least one slot time of 44 microseconds in order to insure that the leading start bit of the first slot is successfully interpreted. The DMX512 specification however specifies the minimum Mark after Break of 12 microseconds. This puts us at the mercy of the UART design and its ability to synchronize following a Break Condition of arbitrary length. There are a number of possible outcomes that depend on what the UART decides is the first STOP BIT once the Break Condition passes.

  • For example, if the beginning of the Mark After Break is seen as a valid STOP BIT then a 0x00 byte is received AHEAD OF the normal NULL START code 0x00. This extra 0x00 can be interpreted as a valid START CODE but all of the channel slots are off by 1. Channel 2 would have the value for Channel 1. This is an ERROR!
  • If the Mark condition just slightly into to Mark After Break is interpreted as a valid start bit then an extra 0x80 is received AHEAD of the START CODE. This might be seen as a bad packet if the START CODE is verified. Channels are also shifted if values are used. This is a ERROR!
  • The above continues with each bit time advance into the Mark After Break generating an initial extra byte of 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE and 0xFF depending on the length of the Mark After Break. In each case the START CODE would then be considered the Channel 1 value. ERRORs result!
  • With a short Mark After Break the UART might look at a low bit value in the START CODE as a missing STOP BIT and generate yet another FRAMING ERROR. Again depending on the timing the START CODE might be returned as 0x80 with the first STOP BIT actually being interpreted as the MSB. In this case the Channel data is properly positioned. This is actually the most common mode I am seeing in the current set up. It is timing sensitive. This is also an ERROR!

If you follow this logic you might see that it is possible that it may take a couple of regular slot times before the UART grabs something it is happy about. It is all about the synchronization aspect of the hardware design.

The question is how to know when you are receiving valid data and properly aligned slots? Is there a solution to this?

A UART that requires a Marking Condition before attempting to detect a START BIT (falling edge) would function properly. Apparently they don’t work this way. At least not all of them.

UART Issue

The problem that we run into is an ancient design flaw in serial ports.

A Framing Error results when the UART (RX SCI) expects a Stop Bit and none is detected. A Stop Bit is a high (1 Marking) and during a Break Condition the signal is held low (0 Space) so a Framing Error is quickly encountered. Now most descriptions of UART logic suggest that after a Break the UART locates the next Start Bit (0 Marking) and that this is detected by a high to low transition of signal (1 -> 0). Logically it is done that way for asynchronous reception as the UART clock needs to synchronize and then sample the middle of each bit period.

In reality after a Framing Error the UART seems to see the next low (0 Space) as a Start Bit and continues to read bit data. As a result Framing Errors are repeated throughout the break period. A bogus byte value might appear to be properly read if the tail end of the Break Condition aligns with the UART in a way to make the high (1 Marking) after the Break look like a Stop Bit.

The likelihood of this bogus data byte and its content can vary depending on the length of the Break and the length of the Marking after the Break and before actual data is present. Since bytes are serialized LSB first these extra bytes look like one of 0xFF, 0xFE, 0xFC, 0xF8, 0xF0, 0xE0, 0xC0, 0x80 and even 0x00.

If the Marking after Break is brief (only a few bit times) and the alignment falls such that the UART looks at bits in the first byte of data for that magic Stop Bit, you will receive an incorrect value for the fist byte. It is conceivable that the UART might take several bytes before synchronizing and providing real data.

If the UART simply fell into a mode whereby it actually did search for the next Start Bit by looking for a valid high to low transition (1 -> 0), you would get a single Framing Error followed by the proper collection of data. But no… after 50+ years we have not addressed this issue. I half recall struggling with this exact thing maybe 30 or so years back now. The fact that it is still an problem is not impressive.

I guess I shouldn’t be surprised in that these hi-tech MCU processors all still include the Real Time Clock (RTC) circuit first designed for the very first digital watches in the late 1960’s. This forces us to parse time into Day, Month , Year, Hour, Minute and Seconds as if setting a watch on your wrist. In fact Seconds can only be reset to 00 and not directly set. On boot we have to read the time and reassemble it into Linux or Internet time as a tally of milliseconds since some epoch. Lots of work that causes loss of precision. And the ideal would be a non-volatile battery-backed 64-bit millisecond counter. Sometimes silicon space is limited and this counter would save lots of that. But no… these integrated circuit companies aren’t as swift as we would like to think.

Since DMX512 signals can have different lengths of Break and Marking after Break and these can vary depending on source, and since the protocol has no leading header that can be used in identifying valid frames, we are NOT ABLE to reliably receive data. Note that if the DMX512 Standard had forced the Mark After Break to be at least one data Slot long (> 44 microseconds) then UARTs would likely properly synchronize and reliably present the first byte of data. But the spec does not and the problem is that changing the standard now does not correct all of the DMX controllers already in use all over the world. So it is what it is.

So for us to insure that we read a valid frame, we need to resort to some trickery, filtering and indeed AI. While that can be fun, it’s unfortunate.

  • Corrected FTP listing issue created by the v1.6.4 release
  • Corrected getRegistryList method memory leak
  • Corrected 412DMX light Flickering
  • Corrected 412DMX NAND Flash processing issue
  • Corrected FTP transfer restart issue

Beginning with JANOS v1.6.4 you will be able to adjust the Time-To-Live (TTL) parameter used by the network stack.

The IpConfig/TTL Registry key defines the lifespan of a network packet. The time-to-live value is a kind of upper bound on the time that an IP datagram can exist in the Internet system. The value is reduced with the passage through a router. If it reaches 0 the packet is discarded. The default value has been increased to 128 from the value of 64 used prior to JANOS v1.6.4.

The TTL setting can be considered to limit the maximum radius (in terms of hops) of the network within reach of the JNIOR. The default setting should allow packets to reach the far end of the globe. A low setting would limit access to the unit as only those in the local vicinity could communicate. In this respect the TTL setting can be used to improve device security.

A very low setting of 1 or 2 would constrain the JNIOR to the local network. One must consider the need to reach Doman Name Servers (DNS) and Network Time Servers (NTP). There may also be the requirement for email transfers wherein the JNIOR needs to reach out to a SMTP Server. To help determine the minimum setting you may be able to use your PC’s TRACERT command to detect the hop count involved in reaching those destinations. The JNIOR does not support a route tracing function.

Real World Test

Luckily we have a neat way to test the effect of reducing TTL. We have a JNIOR we call HoneyPot sitting on the open Internet. Naturally it comes under a constant level of attack. For instance there is a fairly constant level of random login attempts on the Telnet port. On the JNIOR the Telnet port provides access to the JANOS command line interface. We log failed login attempts to a @/access.log@ file.

Log files on the JNIOR rollover to BAK files when they reach 64 KB in size. We keep only one BAK file for each log. Typically an application would archive BAK files when longer term logging is desired. A syslog server can be used for the system log @/jniorsys.log@ for longer term logging.

On HoneyPot we have an application that takes the access.log when it rolls over and analyzes the hosts attempting to log into the unit. IP addresses are added to a database (JSON based) covering data from the past 24 hours. The application uses a locating service to identify the geographical location of the host. A simple web page http://honeypot.integpg.com/map.php receives the database and uses the Google Maps API to plot these locations.

By default JANOS uses a TTL of 128. The map typically appears as follows:

If we reduce the TTL to 16 the map changes. Note that this seems to thin out the number of hosts able to communication with the unit. It does not seem to create a geographical radius.

The thinning effect is useful but one gets the feeling that systems within our own country may no longer be able to communicate with the unit.

The further reduction of TTL to 12 begins to suggest a geographical radius. Note in the following how the unit now seems to be invisible in China. This might suggest that our friends in far away places might actually be using shortcuts in the network to gain access to systems in the United States.

Of course, for a controller the most important aspect of this kind of security is whether or not YOU can access your own unit. In that case you might also use the IP filtering functionality of the device and limit access to only YOU.

One note. With the TTL limited to 16 the HoneyPot unit had trouble reaching some of the @pool.ntp.org@ NTP servers for synchronizing the clock. By limiting the radius of the network you may limit the useful services such as DNS and NTP.

So this test fails in that the service that is used to determine a location for an IP address is about 12 hops away. Here we see it is 13 from inside INTEG.

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. All rights reserved. 
C:\Windows\system32>tracert ip-api.com 
Tracing route to ip-api.com [69.195.146.130]
over a maximum of 30 hops: 

1 <1 ms <1 ms <1 ms 10.0.0.1
2 1 ms 1 ms 1 ms 50-197-34-78-static.hfc.comcastbusiness.net [50.197.34.78] 
3 12 ms 9 ms 9 ms 96.120.62.245 
4 10 ms 9 ms 8 ms te-0-1-1-1-sur01.westdeer.pa.pitt.comcast.net [68.86.146.225] 
5 32 ms 15 ms 15 ms be-62-ar01.mckeesport.pa.pitt.comcast.net [69.139.195.37] 
6 28 ms 21 ms 37 ms be-7016-cr02.ashburn.va.ibone.comcast.net [68.86.91.25] 
7 21 ms 20 ms 20 ms be-10130-pe04.ashburn.va.ibone.comcast.net [68.86.82.214] 
8 20 ms 20 ms 19 ms 23.30.206.206 
9 61 ms 61 ms 72 ms xe-0-2-1.cr2-kan1.ip4.gtt.net [213.254.215.121] 
10 62 ms 52 ms 51 ms ip4.gtt.net [69.174.12.26] 
11 52 ms 53 ms 60 ms 10.0.1.137 
12 * * * Request timed out. 
13 52 ms 51 ms 51 ms us-mo-1.free.ip-api.com [69.195.146.130] 

Trace complete. 
C:\Windows\system32>

And as a result with TTL restricted to 10 I get a lot of these errors.

04/05/18 08:29:12.949
** Uncaught java/io/IOException thrown: "Unable to connect to remote host"
  in java/io/IOException.<init>:(Ljava/lang/String;)V 
  in java/net/PlainSocketImpl.connect:(Ljava/net/InetAddress;I)V 
  in java/net/Socket.<init>:(Ljava/net/InetAddress;ILjava/net/InetAddress;IZ)V 
  in java/net/Socket.<init>:(Ljava/lang/String;I)V 
  in jaccess/JAccess.main:([Ljava/lang/String;)V at line 71

Just a note that I generally create application programs that are not destined for customer deployment with a throws Throwable clause. This insures that every exception is logged to the errors.log file and I don’t need to busy the code with try-catch structures. The application uses the com.integpg.system.Watchdog class which restarts the application after a timeout. You can see this in the system log up until I removed the TTL restriction.

In summary…

Reducing the TTL reduces the “radius” of the the accessible Internet but that does not precisely correspond to a geographic radius. Sites in Russia appear to have access to our Internet vicinity through less hops than some citizens in this country. Still it is a good defense in limiting access to the JNIOR so long as the resources your application uses can still be reached.

I had been thinking about this.

In testing by running with a low TTL we ran into problems where the JNIOR had difficulty reaching services it requires (like NTP) while locations perhaps even in Russia could still reach us. It seems to me that the standard large TTL should still be used for all outgoing communications. But a reduced TTL applied only to incoming connections. Specifically to UDP replies and TCP/IP SYN ACK responses. This would prevent distant (Internet radius wise) hosts from initiating connections or soliciting UDP replies.

The issue with UDP is that the original source TTL is unknown. So we cannot filter on it. The UDP would be received and would be processed. That packet would represent a vulnerability. All we can do is prevent any response from making it back to the malicious host.

The JNIOR Model 410, 412 and 412 each have two available serial ports. Each port providing at least a 3-wire RS-232 interface. A 3-wire connection contains only the Transmit (Tx), Receive (Rx) and Signal Ground (GND) circuits. This is the bare minimum for Duplex communication or interfaces utilizing software handshakes. The Rx line may be omitted if only sending data. Similarly the Tx might be omitted if only receiving data.

In addition to the 3-wire signals the AUX port supports optional hardware handshaking using the Request To send (RTS) and Clear To Send (CTS) signals. The Model 410 AUX port also provides a configuration for RS-422 and RS-485 communications.

While there are a number of parameters that must be properly configured in order to achieve functional and reliable communication, the biggest issue is (and has always been) proper cabling. If an RS-232 connection is not working and it is the first time the connection has been made, the connections are probably not correct.

Originally the RS-232 standard was created to support the connection of a modem. Before networking the modem was used to extend communications over standard telephone lines. Typically a computer (an IBM 360 for instance) would connect to a modem. At home a user would connect their terminal to another modem and establish a remote connection via dial-up. There are two types of equipment in this scenario: the computer stuff and the communications stuff (modems). The RS-232 standard defines two acronyms for this: DTE and DCE. These are used extensively to define connector types and signal definitions.

This is where the confusion begins. The acronym DTE refers to Data Terminal Equipment and in our example above this includes both the Computer and the Terminal (CRT or Teletype). That would be the stuff that you would be trying to connect together had you not needed the modems. The term DCE is often confused and is meant to refer to Data Circuit-Terminating Equipment or Data Communications Equipment. That being the modem in the above example. It does not stand for Data Computing Equipment which implies the computer. These terms are often confused and, perhaps, never really understood. As a result even the engineers who design the equipment (including myself) often employed the incorrect connectors, signal terminology and pin assignments. So let’s not use these designations.

JNIOR Serial Ports

The JNIOR has a COM port (labelled RS-232) and an AUX port (labelled AUX Serial). Both are DB-9F Female 9-pin D-sub connectors. The AUX port has 4 active signals and the COM port 2. The pin assignments are as follows:

2 >> RS232 TX / RS485 TX-
3 << RS232 RX / RS485 RX-
5    GND
7 << RS232 RTS / RS485 RX+
8 >> RS232 CTS / RS485 TX+

Here is how it shows on the schematic. Note that even the pin numbering on the the connector itself can be confused. The (>>) indicates an output. The JNIOR generates a voltage on this pin and it must be connected to an input at the other end. The (<<) indicates an input. This should be connected to an output at the other end. We will cover RS485 in a little bit.

You can see that we do not use DCD, DSR, DTR and RI. These are unconnected. The COM port follows the same assignments but ONLY pins 2, 3, and 5 are used.

Here is the source of additional confusion. The JNIOR transmits data on Pin 3 and therefore from the JNIOR’s point of view THAT is Transmit Data (TX or TxD). But when that signal reaches the other end (say your PC) it is incoming data or Receive Data (RX or RxD). That is because from the point of view at the PC it is data that would be received. So you connect RXD to TXD and visa versa.

Not everyone labels it that way. You will find an input pin labelled TxD. The thinking is that you would connect TxD to TxD. After all you do connect CTS to CTS as the signal is Clear To Send regardless as to who generated it and who is listening to it. The same goes for Request To Send (RTS).

It is not surprising that we sometimes have to grab a voltmeter to see if a pin is generating an RS232 voltage level (an output) or not (an input). Even that can be misleading when pull-up resistors are used. I used to have a couple of really sweet RS-232 break-out boxes. Those have gotten lost but were life savers back in the day. You know, nice colored LEDs showing outputs and jumper wires that you could use to test various cabling solutions before soldering the final cable.

JNIOR to PC Connection

Well today if you want to connect the JNIOR to your PC you will need a USB-To-Serial adapter. You would likely want to do that to gain access to the JANOS Console (command line interface) available over the COM port (115.2Kbaud, 8 data bits, 1 stop bit, no parity). The adapter will present you with a DB-9M Male connector identical to what you would have found on an older PC as a COM or AUX port connection. The connector (DTE) can be directly plugged into the JNIOR COM (or AUX) port (DCE).

Some USB-To-Serial adapters provide a length of cable and others are relatively short. If you need a longer cable then you either use a USB extension or an Male-To-Female Straight-Thru Serial Extension cable. The latter would need only be 3-wire unless your application optionally employed the hardware handshake. I will cover that a little later.

You would use this same approach to connect the JNIOR’s AUX port to a PC-based media server or other system that uses the standard PC serial ports. An application on the JNIOR can then send and receive data or commands to the remote server.

Connecting a Device to the JNIOR

If you plan to connect a barcode scanner or other device to the JNIOR then you might need a little help. You may need a 9-pin Gender Changer. There are two kinds: F-F and M-M. You may need the Male-To-Male (m-M) Gender Changer. This has pins on both sides and when plugged into the JNIOR it changes the connector from a Female DB-9F to the equivalent of a Male DB-9M. Unfortunately this does not alter the pin assignments and if the device was designed to be plugged into a PC then you will need a cross-over adapter or cable. The cross-over exchanges pins 2 and 3 (as well as 7 and 8). Remember that you want to always connect an output to an input. Sometimes this is called a Null Modem adapter, the name coming from the need to interconnect two DTE devices without modems.

Perhaps in hindsight it would seem that the JNIOR AUX port should have been DTE. In fact in the beginning we did not use a DB9 connector at all and provided screw terminals for the 5 signals since we would be required to connect to either DTE or DCE. The reality is that in Cinema (which was an early and big market for JNIORs) we connected often to media servers (which are essentially PCs) and the current DCE arrangement worked best for those customers. That stuck.

So as a result you end up with stuff like this.

Of course if you are handy with the soldering iron and get some solder-cup DB9 connectors and hoods from Digi-Key, you can clean this up nicely. They had hoped to solve all of this with USB but that has created other issues.

It didn’t help RS-232 that from the beginning no one fully understood how to document it. Some of us might remember the detailed signal diagrams explaining plus and minus 12V states, start and stop bits, and little endian order in the back of manuals. That level of detail was just adding to the confusion.

Here is a modern day failure. This is from a product received in 2017. At first glance you would think this is good documentation.

Here only the boldface signals are available or can be used. Perhaps only those should be shown. But beyond that picky item the important piece that is missing is any indication or what is an output and what is an input. You can naturally make your own assumptions. You might correctly assume that Received Data (RxD) is information generated (or output from) the remove connection and therefore an input at this connector. The TxD would then be an output. I mean you only have two choices here and chances of being correct are 50/50. If you are working a soldering iron though you won’t appreciate making the wrong guess.

It is not so obvious as to whether the CTS or RTS connection is an input or output. These signals are shown here but are they used? Are they required? Is there an option setting some place of which you should be aware?

So if you have the diagram for the other piece of equipment that you are connecting should you wire straight thru? Do you wire TxD to RxD and vice versa? If that ends up crossing over from pin 3 to pin 2 and vice versa should you also cross over RTS and CTS? Who knows. RS-232 failure.

My point though is that this nice little picture doesn’t eliminate the chance that your cabling or the cable you make might not work. And, if it doesn’t work you don’t have enough information to decide what to change. Come on man! You can do better.