When using protocols with a JNIOR, you can disable the login to not be prompted when using those protocols through the JNIOR Web Page. This is NOT RECOMMENDED because its makes the JNIOR unsecured.

To disable the login requirement for the JANOS Management Protocol or the JNIOR Protocol you will use the JNIOR Web Page. Once the web page is opened click on the Configuration tab. Then click on the Protocol section near the bottom of the column on the left. Now make your login selections using the checkboxes under the appropriate protocol.

Once you un-check that box, the login will no longer be needed for ANY connection to that port.

--- Core ---
JANOS 1.9 UPDATED

DCP 2.4 UPDATED

--- Bundled ---
FTP Client
MODBUS Server 1.7.236
Serial Control 5.0.122.1501
Serial To Ethernet 6.0.48
Slaving Service 1.5.1810.225
SNMP 2.4.1.494
Task Manager 7.0.351

All-In-One 190618 June 18, 2019

--- Core ---
JANOS 1.8
DCP 2.3

--- Bundled ---
FTP Client
MODBUS Server 1.7.236
Serial Control 5.0.122.1501
Serial To Ethernet 6.0.48
Slaving Service 1.5.1810.225
SNMP 2.4.1.494
Task Manager 7.0.351

Release Notes

We have released JANOS v1.9 which has several changes and bug fixes. Most notably with TCP packet generation. While it is not wrong to send data in multiple TCP packets, many devices incorrectly implement TCP clients and fail when this case arises. This version adjusted the release of socket data to reduce the chances that a message might be split across separate TCP packets. This had been a recent issue with some MODBUS client devices.

  • Added support for Reverse LLMNR allowing network scanners to label IP addresses with hostnames
  • Adjusted the release of socket data to reduce the chances that a message might be split across separate TCP packets
  • Corrected Sockets race condition that caused occasional reception delays and blocking
  • Fixed the incorrect signed display of large input counter values in JRMON and JMP Protocol
  • Adds a BEACON announcement in the event of an IP conflict
  • Improved NONCE management eliminating possible issues in the presence of port scanners
  • Eliminated chance of buffer overrun occurring in FTP transfer
  • Beacon REBOOT now works in all cases
  • Eliminated potential difficulty in obtaining network capture file

JANOS 1.8 Released June 17, 2019

Release Notes

We have released JANOS v1.8 which adds the JMP Protocol. The JANOS Management Protocol (JMP) is essentially the JSON message interface utilized by JANOS Websockets. The JMP Protocol has been exposed on its own TCP/IP port. This encloses the JSON messaging in a JSON array along with the JSON Object length making reception of the messages easy. The JMP Protocol will be used by the QSC Q-SYS JNIOR component.

  • Implements the JANOS Management Protocol JMP
  • Adds "Block" command to JSON interfaces
  • Web Server limits the effects of extremely slow connections.

JANOS 1.7.1 Released December 3, 2018

Release Notes

  • Watchdog no longer causes reboot from foreground execution
  • REGEX Alternation '|' has been corrected to properly work in Group
  • Corrected NULL Pointer write issue when a Group which employs alternation appears at the start of a REGEX expression
  • Resolved message pump memory issue relating to process termination
  • Expand the channel range for setOutputRelay() from 0-11 to 0-15
  • Corrected network capture filtering when an IP address is to be excluded
  • Supports networks using Jumbo Frames
  • Eliminated assertion associated with improper use of '%n' in System.out.printf.

JANOS 1.7 Released July 26, 2018

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

package mqttmessagepump;

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

public class MQTTMessagePump implements Runnable {

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

    private static Thread _thread;

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

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

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

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

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

            // notify all of our listeners

            if (systemMsg.type == 1600) {

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

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

        }

    }

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

}

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

MQT connection in MQTT Application

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

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

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

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

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

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

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

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

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

            // notify all of our listeners

            if (systemMsg.type == 1600) {

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

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

        }

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

if (systemMsg.type == 1600) {

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

            }

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

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

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

At some point you may want your JNIOR to always have an output close or pulse, and when you reboot a JNIOR, they get reset. This post will show you how to have your JNIOR automatically close outputs from startup. If you’re looking for different information on the DCP, or don’t know how to access it, you can look here.

To start, make sure you are able to access the DCP (Dynamic Configuration Page) of your JNIOR. Then you’ll want to go to the Configuration tab of the DCP. Here we will be accessing the outputs section.

Configuration tab in JNIOR Web Page

Here is where you can close or pulse outputs on startup. Simply add a zero to the Initial Action section for whichever channel you wish to close, and it will now always close that input on startup. To pulse the output on startup, simply add any positive value and it will pulse for that many milliseconds instead.

Name Version Release Date Size MD5
MQTT v3.0 Jan 21 2020 489.9 KB f6a905a5bdc100eae728d250da4c7439

New functionality has been added to the MQTT 3.0 application.

When changing settings in the MQTT application, a reboot will not be needed to get the new settings to take hold. Before when you wanted to change any setting on the MQTT application, after they were saved you would have to reboot to update those changes. Now when you click save change at the top right, the changes are applied at that moment.

Changed how information was processed so that when its retrieved, it will not be as slow when it was using the contain() method. The contain() method referenced more information in an inefficient way. Now the program is configured to get the most relevant information in a much cleaner manner, making it faster.

When a change is made to a device group, the previous name is unsubscribed from. Before this update, when changing the device group name, it would still be subscribed to the previous device group name.

When a change is made to a device host name, the previous name is unsubscribed from. Before this update, when changing the host name in the support tool, it would still be subscribed to the previous device group name.

If the MQTT application is not running on the JNIOR, the application page will be grayed out until it is started again. This is to prevent people from believing they are connected to the broker without the MQTT application running, since you can’t interact with the broker from the JNIOR without that application running.

Implemented the username and password on the MQTT application page so if it is required, it will not establish a connection until that information is entered. This allows a connection to brokers that require usernames and passwords.

Added MQTT functionality for the ten-volt module, 4-20 module, temperature probes, and environmental modules. You can now publish output percentages to the ten-volt and 4-20 modules, and subscribe to all of these listed modules.

The help command can be used in the console to show help for whatever commands you can give in the console.

To use the help command, you type help (command name). You can also type help * to see the help description for every command available, in alphabetical order.

The PS command allows you to view all application running and a process number next to it. This is useful to view if applications are running on start up, or if you have to manual start them. You can also type ps -v to get more detailed information on the processes running.

ps command in JNIOR command line

The KILL Command allows you to terminate a program running from your list of processes. To kill a process on your JNIOR, you’ll type kill (process number) or kill(process name). This is useful to stop program from running on your JNIOR, without rebooting. The example below stops analog presets from running because its process number is 3.

kill command in JNIOR command line

What is the EXTERN Command?

The extern Command is used to help you see and/or remove modules that you have setup to use with your JNIOR.

How to use the EXTERN Command

Typing extern into the Console on the JNIOR Web Page will display all currently configured modules on your JNIOR, as shown below.

You can also use the extern -r command to remove whatever modules you currently have connected to your JNIOR, as shown below. The first line returned from the extern command is what you get when a module is plugged in. The second line from the extern command is what you see when its not plugged in, notice that the configuration has not been removed even though the module has been unplugged. The last line after the extern -r command removes the configuration that was still there, letting you replug the same module or a different one.

If you receive this error after establishing a serial connection, its because the operating system detected an error in the flash file system on boot. The flash was not mounted to protect the file system from any further damage.

To resolve this issue we need to reformat the flash. You can follow the steps below to reformat the flash and reload the unit. If you need to save any files out of flash we can temporarily mount it. If you don’t then you can skip to step 3.

1. jrflash -z

This will force the operating system to mount the flash temporarily so that you can recover any needed files. The macro and devices files should also be in RAM at the root of the filesystem.

2. FTP

You can use FTP to recover any files from the flash file system. Applications will be loaded using the update packages from the website. Files may or may not be available depending on where the corruption is in the filesystem structure.

3. jrflash -f

This will reformat the flash filesystem. Everything will be lost.

4. Run the latest All-in-one update project from our website.

This will reload all of the files that are loaded when we ship the unit.

5. Run any additional update projects to get the applications running that you depend on.

6. FTP

Use FTP to restore any files that you saved in step 2.

The procedure above will get the unit running again. If you need any additional help or questions, just let us know!

Analog Presets is an application that lets you control and automate expansion modules such as 4-20ma Module, the 10v Module, or the 3 channel LED module. To get started, you’ll want to download the Analog Presets application, which you can download below.

Name Version Release Date Size MD5
Analog Presets v2.6 Dec 14 2021 334.1 KB d9eeeded688fe9c875402948f562f9b1

After updating your JNIOR with the application, you’ll want to go to the URL containing the IP of your JNIOR with /analogpresets after it to access the application on your JNIOR. Here you can add levels containing different commands you want to create for your modules. You can name the command, what you want it to do, how long it should last, and if you want other commands to start once it starts or finishes. You can also use these as macros by defining a port number and termination string on the general tab.

To send a macro to the Analog Presets application, first we’ll need to configure it. Here is an example using the 3 Channel LED Dimmer. We’ll start by adding a new level at the bottom middle of the first tab in Analog Presets, and giving it a name. (For this example we named ours MacroTest) Once that is added, we’ll want to go to setting level and add the brightness of each output we want set. For this example, since the 3 Channel LED Dimmer has 3 outputs, we’ll set each of them to 100 by typing in the field 100,100,100. We’ll also want to go the 3 Channel LED Output Channel field and type 1,2,3 to represent the channels we want to set the brightness of. If in the setting level field we set the brightness to 50,75,100, and the 3 Channel LED Output Channel was still 1,2,3, then channel 1’s brightness would 50, 2’s would be 75, and 3’s would be 100. For this example though, we’ll set them all to 100. If you have the 3 Channel LED Dimmer plugged into your JNIOR and the 3 Channel LED Output Channel field is still grayed out, then reboot your JNIOR and it then should be configurable.

We’ll then click on the General tab of the Analog Presets application. Here we can set the TCP port that the macro will send to. (In this example we have 9700) Make sure the Termination string is \r\n.

After that you’ll need the cinema application. You’ll download the Cinema.jar application and using the update tab in the support tool load it onto your JNIOR. Once its on your JNIOR, we’ll want to go to the support tool and create a device in the device tab. We need to do this so that the macro will know what information to send. At the bottom of the Device tab, click add Device. From there, we’ll want to name our device (You can name it anything), set the device type to RAW ETHERNET, set the IP Address to the JNIOR you are using, and the port number to the one you set above in the Analog Presets application. (Mine for this example is 9700).

Once you have set this up, you’ll want to click “publish to JNIOR” and it will prompt you to save the device configuration as a file. Once this is done, select the JNIOR you are using as the one to publish to. Once its published, you’ll then go to the Macro tab. The first thing we’ll do here is click “link devices”, and select the device file we previously created. After this we will go to the Action view and add a new action. Here you’ll name your action (It can have any name), the device we’ll set the to the one we previously created, the action should be send, and the data should be formatted as follows:

Trigger “Name of level from analog presets application”\r\n.

Once this is done, we will then add a Macro in the Macro view. First we name the macro and then we can add the action to this macro, by selecting the Macro we created, the action we created, and then clicking <- button.

Once you have done this, you’ll want to click “publish to JNIOR” like we did previously for the device tab, and create a macro file. Once that is created select the JNIOR you are using as the one to publish to. Now to test this, we’ll want to go to the tools tab of the support tool and click command line.

Once the command line is open, we need to configure the command line to create the right connection, at the bottom of the command line we’ll set the values to our IP and then the TcpPort value in our JNIOR’s Registry. You’d find this by opening the JNIOR’s web page in beacon, and then after going to the registry tab look for the path: AppData/Cinema/CinemaServerClient/TcpPort

Make sure that the value is not -1 like it is above. If it is, change that registry key. (An example value to change it to is 5000). Once you find this value, set it as the port number next to the IP address at the bottom your command line. The IP address should be the one your JNIOR has.

Also, go the options drop down list and check each option.

Now all you should have to do is click the connect button at the bottom right and type:

run “Name of macro from support tool”\r\n

This will run the macro you wanted to send to the 3 Channel LED Dimmer!

To monitor the environment with the JNIOR you will need 3 things. First, either the Environmental Sensor or the Rugged External Temperature Sensor. Second, the Tasker application that will read that sensor and log the values. Third, the Grapher application to graph the data logged from Tasker.

INTEG resells an environmental sensor from Embedded Data Systems. The sensor itself wont work directly with the JNIOR until it is wired. They have several different models that provide different environmental metrics. Temperature and humidity have been the consistent requests. INTEG also sells a rugged external sensor that tracks just temperature.

NOTE: Only the Environmental Sensor records humidity.

Set Up

The first thing to check when setting up is the sensor module you connect to the JNIOR. In the Console Tab of the JNIOR Web Tool, commands you can use to see what sensor is connected are the extern command to tell you what modules you have connected, and the extern -r command to remove a module on your device that is no longer connected. To tell which sensor module you have connected, look at the ID of the device from the extern command. If it ends with 7E its the Humidity Sensor, if its 28 its the Rugged External Temperature sensor.

extern command for JNIOR command line

After that, you’ll want to make sure that you have the applications to graph the data logged.

Name Version Release Date Size MD5
Tasker v12.0 Jun 20 2023 2.9 MB edfd2578eccdf8595b4f3d35f1ca4bf8
Grapher v4.1 Jun 18 2020 788.5 KB 75e992513636e0c45c7aa7f71d8c1303

You’ll want to do an update project one at a time, publishing these to the JNIOR you have the sensor connected to. After that, to get to the Grapher application, you can either type (JNIOR’s IP)/Grapher into the URL of a web browser or you can select it under the tools tab of Tasker to take you to the application. There you can create the graph to monitor your Temperature. 

Once on the Grapher application page, you’ll want to go to the Grapher Configuration page to enter settings for creating a graph. You can access this by going to the top of the Grapher application page and at the top click Tools/Grapher Configuration. To start, you can go down to the files section and add the files that give the values to be put on a graph.

At the top, you can define the directory path of the file you want monitored, but keep in mind whatever path and file name is set here should match what you set in the Tasker application. The column value field is where you enter what you want the names of the values from the log file in Tasker to be called. One of the column values needs to be Timestamp since the graph needs that information to know where to plot it the value being pulled. The Date format can be set to MM-DD-YY HH:mm:ss.SSS. The file count is specified to how many files you want added. After setting this, you can configure the Graph section described below that will be monitored for the graph.

Here you can create the graph name, and then set the range of the graph and also the time range being charted. Below that you can create the lines that go into the graph that are tracked. You can add their names, what the units they are measured in are called, and the color of the line. Once this is all set you’ll now want to open the Tasker application by typing in the URL (JNIOR’s IP)/tasker, or by clicking the link for it from the JNIOR Web Page in the applications section of the Configuration tab.

In the Tasker application, the first thing we’ll want to do is create a workspace. After that we’ll create a Logger using the Logger Tab. Once on the Logger Tab of Tasker, clicking the “add Logger” button will create an empty Logger in the Logger Tab, which you can name Log_Temp for this example. This is where the file will be created that Grapher pulls the data from to graph.

Like in the picture above, you’ll want to set the log file path. The one in the example is called temps-{{date(YYMMDD)}}.csv just like the one named in the file section in Grapher of this example, the only difference being that the YYMMDD has “” around it while the file section in Grapher should not. (The date.format part of the file name auto inputs the date when creating the file). The Timestamp Format specifies the Timestamp of when the value are logged. This is needed to know where the files need to be plotted in Grapher, which is also why we included the Timestamp value in Grapher eariler. Entering nothing in the Timestamp Format field will default it to MM/dd/yy HH:mm:ss zzz, so you don’t need to enter anything in this field unless you want to change formatting of the default timestamp. The Schema field will contain separate values that act as the referenced schema values we defined in the graph section of Grapher.

For how the schema values work, they simply refer to values on the JNIOR such as the I/O or analog values. The double squiggly brackets and comma between each value are needed to separate each value. The picture above uses Signals from the signal tab, so you’ll need to go to the Signal tab after this and create two signals for Celsius and Fahrenheit for the humidity temperature sensor. Make sure the names in brackets in the schema under logger and the signal names in the signal tab match each other. More information on how to reference values on the JNIOR in a Logger, go to the help drop down at the top of the Tasker page and select the help link.

After defining the values for the Logger in Tasker, we’ll now want to create a Task in the Task Tab of Tasker. Once in the Task Tab, we’ll select the “add Task” button. This will add an empty Task to Tasker that you can name. Once we do that, we’ll want to select the “add Action” button to add an action to the Task to run the Logger we just created. In the action submenu we’ll want to select the Force Log Profile action. Once its added, we’ll enter the Logger name we created earlier to the action so it’ll run that Logger every time the Task runs.

Once that is done, we’ll now add a schedule that runs every two minutes so the graph is constantly getting more data every two minutes.

Going to the schedule Tab, we’ll click the “add Schedule” button to add an empty schedule to Tasker. We’ll then select the “add Rule” button to open the rule submenu. Here we’ll set the Schedule to run every two minutes, every day, from Midnight to Midnight like shown in the picture below. We’ll also set the schedule to run the Task that activates Logger to every two minutes.

Once this is all set up, going back to the Grapher application should now give you a graph of the logged temperatures!

Grapher Main Page

Sometimes an application wont run on a JNIOR Series 3 unit.  This issue may be due to “insufficient heap“.

This error message means that there was not enough contiguous space in “heap” or the SRAM memory for the application.  The application must find contiguous memory of at least the file size of the application.  Once the application finds that space it will stay in that same space until the application is updated.  The memory can become fragmented over time since the SRAM is used for application executables, application memory and the root file system.

Most likely this error condition will occur after updating to a new version of an application.  If the new application is larger than the old application it cannot occupy the same contiguous block in memory.  It must find a new contiguous block.  That space may not be available depending on the fragmentation of the memory. Even if there is a contiguous block of memory large enough when the file is updated, it still might give you an “insufficient heap” error. This might happen because until its run as an executable, the file doesn’t know it needs to be stored as contiguous space and fragment its memory anyways, which would then cause the error.

The fragmentation is caused by file blocks that have become scattered throughout memory over time.  To clear up the fragmentation we could delete a file at a time until a contiguous block has become available.  This would be a very slow approach.  You would not know which file gives you the best chance of finding a contiguous block.  Since the root of the file system contains mostly log files we can just clear the heap on a reboot.  In order to that we execute the reboot -a command.

What about my cinema devices and macros files?

The cinema files are automatically backed up to flash/cinema_backup.  This location is not affected by the reboot -a.  The cinema files will be restored next time cinema runs.

If you experience issues with your Dolby IMS2000 Cinema Server connecting to or executing commands on the JNIOR you may need to follow one of the procedures below. There are two issues that I have seen.

Dolby has resolved the issue for the IMS3000.

A Dolby may not even try to connect to a JNIOR when it should.

Make sure you have rebooted the Dolby IMS after adding the JNIOR as a device.

To determine if this is the issue:

  1. Go to the DCP for the JNIOR
  2. Click on the Console tab
  3. Click ‘Start Session’
  4. Log in when prompted
  5. enter the netstat command
  6. There should be a connection to 9200 with the Dolby Server IP Address for the Remote IP

The Dolby is connected but nothing happens on the JNIOR when the Dolby sends the command.

When Dolby tries to connect to the JNIOR it may not be able to log in to the JNIOR due to a bad password. The highlighted bytes below are corrupted.

The bytes should represent the password like this…

The previous images are screenshots for the WireShark application when analyzing a network capture. This capture is included in a JNIOR Support Tool Snapshot. I have been told by Dolby that this is likely due to the browser auto-fill.

To determine if this is the issue you should check the protocol.log on the JNIOR

To do that:

  1. Go to the DCP for the JNIOR
  2. Click on the Console tab
  3. Click ‘Start Session’
  4. Log in when prompted
  5. Enter the cat protocol.log command

If you don’t see a successful login or do see a failed login then do the following:

  1. Go to the JNIOR Device Configuration on the Dolby IMS
  2. Remove the JNIOR device
  3. Reboot the Dolby IMS
  4. Add the JNIOR as a device but DO NOT enter the password
  5. Reboot the Dolby IMS

You can now re-test the JNIOR connection to see if this is resolved.

If it is not resolved then make sure your browser is not using auto-fill for the site. The procedure differs per browser. Follow one of the links below for your browser.

If it is not resolved you can try to analyze the network capture or contact INTEG for additional support.

I hope this article helped you out. Please contact us to let us know!

The reboot command is used to restart the JNIOR. When a reboot is called on a JNIOR it kills all the applications its running, and then if any have a run key, those applications boot back up once its restarted. It will also disconnect and reconnect all connections on a JNIOR.The reboot command has multiple options along with it.

The first one is -A which cleans system and heap memory on reboot.

-F will not prompt a confirmation and immediately do a reboot of the JNIOR.

There is also a another command for a reboot that will not appear in the help command of reboot. If you type -eraseall with reboot, it will factory restart a JNIOR deleting any information that wasn’t already on them when they were first received.

A normal reboot usually fixes a lot of temporary problems the JNIOR is confronting. The -eraseall command should be used with lots of caution, as you lose everything you’ve done on the JNIOR since its been received. The reboot -eraseall is only for series 4 JNIORs.

The following information describes how to use the JNIOR to capture data on the Ethernet network and the JNIOR Serial ports. Capturing the data can be a great troubleshooting technique for communicating with various devices.

The Netstat command on the JNIOR Series 4 has multiple functions and some of them are very helpful when trying to troubleshoot a network connection via the Ethernet network.

The standard Netstat command will list all the TCP ports that are listening on the JNIOR and indicate any connections made.

Typing help netstat will show all the different options you can use with netstat.

The -C, -R, and -F options are great for Capturing, Resetting, and Filtering a file of the packets being sent on the TCP ports. This file can opened in Wireshark so you can better view the information being sent back and forth on each port. There is also a command option for using a network sniffer with the netstat -s command.

There is a chance you will see the following PHP error. For this to be seen you must have a Series 4 JNIOR that had JANOS version 1.6 or older and you just updated to a new JANOS version

Why did this happen?

The older JNIORs had a different web page than we have now. The main web page used to be served out of the flash/www folder. Now the DCP, the newer web page, is served out of the flash/www.zip file. If the flash/www/index.php file is still found then the web server will process it and serve it instead of the index.php in the www.zip file.

How do we fix it?

To fix this we need to remove the old flash/www/index.php file. This will allow JANOS to look in the flash/www.zip file. This should be a step in the All-In-One update project. You can do it manually with a Telnet connection like this…

  Serial Control Source Code [ Oct 06 2020, 6.78 KB, MD5: bb35cb94fc72426bc2e81b8be58ecb2f ]

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

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

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

To start we are looking at the SerialConrtolMain.java file. After listing the imports, there is a version listed. This is from the Assembly.java file. The versioning of the project would usually be kept there, but we aren’t going over the Assembly.java file because it extends outside the scope of this post.

After this the Class of the file begins. There are three global variables listed there. The validconfiguration variable is to determine if the application should be running or not. This could be determined by weather or not the application is already running on the device. After that is the Input and Output log monitors, which are used to watch the I/O of the JNIORs.

Next comes the main function. Here an object of the application is created, and two function are called. The Init function, which grabs the values needed to establish the connection for the serial control application and the run function which, allows the connection to stay until it needs to end.

The Init function calls the config.init to format the information between connections so the information is readable. Then setUpSerialPort.init setTCPServer.init and setIoLogMonitor are called. The setUpSerialPort.init funciton sets up the port to communicate on, the setTCPServer.init sets the TCP port to communicate on, and the setIoLogMonitor begins to grab alerts about the I/O that are being activated.

For the run function, it confirms if there is a valid configuration. If so the run function doesn’t stop the program, but otherwise the program terminates.

import com.integ.common.iolog.*;
import com.integ.common.logging.AppLog;
import com.integ.common.logging.Logger;
import com.integ.common.logging.RollingFileLog;
import com.integ.common.net.*;
import com.integ.common.system.Application;
import com.integpg.comm.AUXSerialPort;
import com.integpg.comm.COMSerialPort;
import com.integpg.comm.SerialPort;
import com.integpg.system.JANOS;
import java.io.IOException;
import java.net.Socket;

/**
 * The Serial Control application allows people to connect to the JNIOR via Serial or Ethernet.
 * I/O monitoring and Relay control is available with simple ASCII commands.
 */
public class SerialControlMain
        implements IoChannelLogListener {

    /* a flag indicating whether there was a valid configuration.  If there is not a 
       valid configuration then we will let the application exit */
    private boolean _validConfiguration = false;

    /* IO log monitors for both inputs and outputs.  we use these to send unsolicited io alerts */
    private DigitalInputsIoLogMonitor _digitalInputsIoLogMonitor;
    private RelayOutputsIoLogMonitor _relayOutputsIoLogMonitor;



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

        SerialControlMain serialControlMain = new SerialControlMain();
        serialControlMain.init(args);
        serialControlMain.run();

    }



    public void init(String[] args) throws Exception {
        //
        // Read the configuration.  Configuration is only consumed on application start-up 
        // since we are bringing up tcp listeners and taking over serial ports
        Config.init();

        //
        // set up various features
        setUpSerialPort();
        setTcpServer();
        
        setIoLogMonitors();
    }



    private void setUpSerialPort() {
        try {
            SerialPort serialPort = null;

            //
            // get the serial port configuration and open the port if the name is valid.  we will 
            // use whatever serial settings are in the registry in AUXSerial or COMSerial
            String serialPortName = Config.getSerialPortName();

            if ("aux".equalsIgnoreCase(serialPortName)) serialPort = new AUXSerialPort();
            else if ("rs232".equalsIgnoreCase(serialPortName)
                    || "com".equalsIgnoreCase(serialPortName)) serialPort = new COMSerialPort();

            //
            // if a valid serial port was assigned then open it and start the client
            if (null != serialPort) {
                AppLog.info(String.format("opening %s serial port", serialPortName));
                serialPort.open();

                //
                // get a logger for the serial connection
                Logger serialLog = RollingFileLog.getLogger(
                        String.format("%s_Serial.log", Application.getAppName()));

                //
                // create an serial control client four our serial port
                SerialControlClient serialControlClient
                        = new SerialControlClient(serialPortName + " port",
                                serialPort.getInputStream(), serialPort.getOutputStream());
                serialControlClient.setLog(serialLog);

                //
                /// mark that there is a valid configuration
                _validConfiguration = true;
            } else {
                AppLog.info("serial port not configured to be in use");
            }
        } catch (Exception ex) {
            AppLog.error("error opening serial port", ex);
        }
    }



    private void setTcpServer() {
        try {
            //
            // get the serial port configuration and open the port if the name is valid.  we will 
            // use whatever serial settings are in the registry in AUXSerial or COMSerial
            int tcpServerPortNumber = Config.getTcpServerPortNumber();

            if (-1 != tcpServerPortNumber) {
                TcpServer tcpServer = new TcpServer("SerialControl-TcpServer", tcpServerPortNumber);
                tcpServer.setLog(AppLog.getLog());
                tcpServer.start();

                //
                // get a logger for the serial connection
                Logger tcpServerLog = RollingFileLog.getLogger(
                        String.format("%s_TcpServer.log", Application.getAppName()));

                tcpServer.setTcpServerListener(new TcpServerListener() {
                    @Override
                    public void clientConnected(TcpServerEvent evt) {
                        Socket socket = evt.getSocket();

                        String clientInfo = String.format("%s:%d",
                                socket.getInetAddress().getHostAddress(), socket.getPort());
                        tcpServerLog.info(String.format("%s is connected", clientInfo));

                        try {
                            //
                            // create an serial control client four our serial port
                            SerialControlClient serialControlClient
                                    = new SerialControlClient(clientInfo,
                                            socket.getInputStream(), socket.getOutputStream());
                            serialControlClient.setLog(tcpServerLog);
                        } catch (IOException ex) {
                            tcpServerLog.error(String.format("error setting up ascii command client for %s", clientInfo), ex);
                        }
                    }
                });

                //
                /// mark that there is a valid configuration
                _validConfiguration = true;
            } else {
                AppLog.info("tcp server not configured to be in use");
            }
        } catch (Exception ex) {
            AppLog.error("error setting up tcp server", ex);
        }
    }



    private void setIoLogMonitors() {
        _digitalInputsIoLogMonitor = new DigitalInputsIoLogMonitor();
        _digitalInputsIoLogMonitor.addIoChannelLogEventListener(this);
        _digitalInputsIoLogMonitor.start();

        _relayOutputsIoLogMonitor = new RelayOutputsIoLogMonitor();
        _relayOutputsIoLogMonitor.addIoChannelLogEventListener(this);
        _relayOutputsIoLogMonitor.start();
    }



    @Override
    public void onIoChannelEvent(IoChannelEvent ioEvent) {
        //
        // see if unsolicited io alerts is enabled in the configuration 
        boolean sendUnsolicitedIoAlerts = Config.getSendUnsolicitedIoAlerts();
        if (sendUnsolicitedIoAlerts) {
            //
            // build the string to send
            String outputString = String.format("%s%d=%d",
                    ioEvent.AbbrTypeString, ioEvent.Channel, (ioEvent.State ? 1 : 0));

             if (ioEvent instanceof DigitalInputChannelEvent 
                     && Config.getSendCounts()) {
                int counter = JANOS.getInputCounter(ioEvent.Channel - 1);
                outputString = String.format("%s,%d", outputString, counter);
            }
            
            //
            // broadcast to all connected clients
            SerialControlClient.broadcast(outputString, ioEvent.TransitionTime);
        }
    }



    public void run() throws Exception {
        //
        // see if there is any valid configuration.  if there isnt, then we can let this 
        // application exit as a reboot is needed
        AppLog.info(String.format("Version %s", VERSION));
        if (_validConfiguration) {

            //
            // nothing to do here since the client handlers are being performed in a separate thread.
            Thread.sleep(Integer.MAX_VALUE);
        } else {
            AppLog.warn("there was either not valid configuration or errors occured.  nothing to do, exiting.");
        }
    }

}

Next is the SerialControlClient.java. To start, this file has a few global variables that it uses. It has a QuickDatFormat variable for the date, 3 pattern variables for Inputs, Ouputs, and both together. Then there is an array for clients connected. Lastly is a logger, followed by a string, outputstream, and bytearray for sending data.

The first function of the file is the SerialControlClient function. This function declares an AsciiCommandClient variable which begins listening for clients to connect to. There is a setLog function which sets a logger to record what the AsciiCommandClient object does.

The broadcast function sends a message out to all listening clients. The send function, which after determining which clients returned datestamps back to the JNIOR, sends out to the clients who responded. The bytesRecieved function grabs data that was sent to the JNIOR to be processed and read. The clientStarted function adds a client to the client list. The clientFinished function removes a client from the client list.

Lastly, the processMessage function takes the data received and determines what kind of I/O change occurred to report it.

import com.integ.common.logging.AppLog;
import com.integ.common.logging.Logger;
import com.integ.common.logging.SystemOutLog;
import com.integ.common.net.AsciiCommandClient;
import com.integ.common.net.BytesReceivedEvent;
import com.integ.common.net.ClientListener;
import com.integpg.system.JANOS;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.QuickDateFormat;
import java.util.ArrayList;
import java.util.EventObject;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SerialControlClient
        implements ClientListener {

    private static final QuickDateFormat QUICK_DATE_FORMAT = new QuickDateFormat("MM/dd/yy HH:mm:ss.fff");

    private static final Pattern IO_CONTROL_PATTERN = Pattern.compile("([copt\\+\\d\\*]+(=\\d+)?)$", Pattern.CASE_INSENSITIVE);
    private static final Pattern DIN_QUERY_PATTERN = Pattern.compile("din(\\d+)\\?$", Pattern.CASE_INSENSITIVE);
    private static final Pattern ROUT_QUERY_PATTERN = Pattern.compile("rout(\\d+)\\?$", Pattern.CASE_INSENSITIVE);

    private static final ArrayList<SerialControlClient> CLIENTS = new ArrayList<>();

    // initially we will log to the system.out stream unless setLog is called
    private Logger _log = SystemOutLog.getLogger();

    private final String _clientNameString;
    private final OutputStream _outputStream;
    private final ByteArrayOutputStream _byteArrayOutputStream = new ByteArrayOutputStream();

    private final AsciiCommandClient _asciiCommandClient;



    public SerialControlClient(String clientNameString, InputStream inputStream, OutputStream outputStream) {
        _clientNameString = clientNameString;
        _outputStream = outputStream;

        //
        // create an ascii command client four our serial port
        _asciiCommandClient = new AsciiCommandClient(clientNameString, inputStream);
        _asciiCommandClient.setTerminationBytes(Config.getIncomingTerminationString());
        _asciiCommandClient.setClientListener(this);
        _asciiCommandClient.start();
    }



    public void setLog(Logger log) {
        _log = log;
        _asciiCommandClient.setLog(_log);
    }



    public static void broadcast(String s, long timestamp) {
        synchronized (CLIENTS) {
            for (SerialControlClient serialControlClient : CLIENTS) {
                try {
                    // send the output string with the acutal transition time from the iolog
                    serialControlClient.send(s, timestamp);
                } catch (Exception ex) {
                    // do nothing
                }
            }
        }
    }



    public synchronized void send(String s) {
        send(s, System.currentTimeMillis());
    }



    public synchronized void send(String s, long timestamp) {
        try {
            // reset the byte output array
            _byteArrayOutputStream.reset();

            // if client should respond with datestamp
            if (Config.getSendDateStamp()) {
                // first send the datestamp
                _byteArrayOutputStream.write(QUICK_DATE_FORMAT.format(timestamp).getBytes());
                _byteArrayOutputStream.write(' ');
            }

            // append text
            _byteArrayOutputStream.write(s.getBytes());

            // append the termination characters
            _byteArrayOutputStream.write(Config.getOutgoingTerminationString().getBytes());

            if (_outputStream != null) {
                _outputStream.write(_byteArrayOutputStream.toByteArray());
                // flush the buffer to make sure it went
                _outputStream.flush();
                // log what was sent. since we are using the timestamp obtained at
                // the beginning of this method we could potentially log out of order
                // with the recieve client.  we can synchronize on a log lock to make
                // sure this doesnt happen or we can let it happen and let it be known
                // that it can happen
                _log.info(_clientNameString + " sent: " + s);
            }
        } catch (IOException ex) {
            _log.error(ex);
            AppLog.error(ex);
        }

    }



    @Override
    public void bytesReceived(BytesReceivedEvent evt) {
        byte[] bytes = evt.getBytes();
        String message = new String(bytes);

        _log.info(String.format("%s received: %s", _clientNameString, message));

        try {
            processMessage(message);
        } catch (IOException ex) {
            _log.error(ex);
            AppLog.error(ex);
        }
    }



    @Override
    public void clientStarted(EventObject evt) {
        _log.info(_clientNameString + " client started");

        //
        // add this serial client to the clients list
        synchronized (CLIENTS) {
            CLIENTS.add(this);
        }
    }



    @Override
    public void clientFinished(EventObject evt) {
        _log.info(_clientNameString + " client finished");

        synchronized (CLIENTS) {
            if (CLIENTS.contains(this)) {
                CLIENTS.remove(this);
            }
        }
    }



    private void processMessage(String message) throws IOException {
        Matcher matcher;
        if ((matcher = IO_CONTROL_PATTERN.matcher(message)).find()) {
            System.out.println("IO_CONTROL_PATTERN.groupCount(): " + matcher.groupCount());
            for (int i = 0; i < matcher.groupCount(); i++) {
                System.out.println("  IO_CONTROL_PATTERN.group[" + i + "]: " + matcher.group(i));
            }

            if (1 < matcher.groupCount()) {
                Jrmon.parseCommand(matcher.group(1));
            }

        } //
        // is it a query for input status?
        else if ((matcher = DIN_QUERY_PATTERN.matcher(message)).find()) {
            System.out.println("DIN_QUERY_PATTERN.groupCount(): " + matcher.groupCount());
            for (int i = 0; i < matcher.groupCount(); i++) {
                System.out.println("  DIN_QUERY_PATTERN.group[" + i + "]: " + matcher.group(i));
            }

            int channelNumber = Integer.parseInt(matcher.group(1));
            int state = ((JANOS.getInputStates() >> (channelNumber - 1)) & 1);
            String response = String.format("din%d=%d", channelNumber, state);
            if (Config.getSendCounts()) {
                int counter = JANOS.getInputCounter(channelNumber - 1);
                response = String.format("%s,%d", response, counter);
            }
            send(response);

        } //
        // is it a query for output status?
        else if ((matcher = ROUT_QUERY_PATTERN.matcher(message)).find()) {
            System.out.println("ROUT_QUERY_PATTERN.groupCount(): " + matcher.groupCount());
            for (int i = 0; i < matcher.groupCount(); i++) {
                System.out.println("  ROUT_QUERY_PATTERN.group[" + i + "]: " + matcher.group(i));
            }

            int channelNumber = Integer.parseInt(matcher.group(1));
            int state = ((JANOS.getOutputStates() >> (channelNumber - 1)) & 1);
            String response = String.format("rout%d=%d", channelNumber, state);
            send(response);


        } else {
            send("unknown command: '" + message + "'");

        }
    }

}

The next file is the Config file, which sets all the information so the port connection properly gets established. The only global variable is the one that gets the applications name form the registry.

The first function in the config file is the init function. It calls all the other functions in the file when its called.

All the functions called in the Init function grab information from the Registry for the serial port name, number, incoming termination string, outgoing termination string, if the JNIOR sends unsolicited I/O Alerts, the Date, and its send counts.

The only function not grabbing from the registry is the getTerminationBytes string which gets called from the getIncomingTerminationString and getOutgoingTerminationString functions. It looks at the message data and determines the Termination value there.

import com.integ.common.system.Application;
import com.integ.common.utils.RegistryUtils;

public class Config {

    private static final String APPDATA_ROOT = "AppData/" + Application.getAppName();



    static void init() {
        getSerialPortName();
        getTcpServerPortNumber();

        getIncomingTerminationString();
        getOutgoingTerminationString();
        getSendUnsolicitedIoAlerts();
        getSendDateStamp();
        getSendCounts();
    }



    public static String getSerialPortName() {
        return RegistryUtils.getRegistryKey(String.format("%s/SerialPort", APPDATA_ROOT), "none");
    }



    public static String getIncomingTerminationString() {
        String incomingTerminationString = RegistryUtils.getRegistryKey(
                String.format("%s/IncomingTerminationString", APPDATA_ROOT), "\\n");
        return new String(getTerminationBytes(incomingTerminationString));
    }



    public static String getOutgoingTerminationString() {
        String outgoingTerminationString = RegistryUtils.getRegistryKey(
                String.format("%s/OutgoingTerminationString", APPDATA_ROOT), "\\n");
        return new String(getTerminationBytes(outgoingTerminationString));
    }



    public static int getTcpServerPortNumber() {
        return RegistryUtils.getRegistryKey(
                String.format("%s/TcpServerPortNumber", APPDATA_ROOT), -1);
    }



    private static byte[] getTerminationBytes(String terminationString) {
        String newTermString = "";
        boolean backslashFound = false;
        for (int i = 0; i < terminationString.length(); i++) {
            if (terminationString.charAt(i) == '\\' && !backslashFound) {
                backslashFound = true;
            } else {

                if (backslashFound) {
                    switch (terminationString.charAt(i)) {
                        case 'r':
                            newTermString += '\r';
                            break;
                        case 'n':
                            newTermString += '\n';
                            break;
                        case 't':
                            newTermString += '\r';
                            break;
                        case 'f':
                            newTermString += '\f';
                            break;
                        case 'b':
                            newTermString += '\b';
                            break;
                        case '0':
                            newTermString += '\0';
                            break;
                    }
                } else {
                    newTermString += terminationString.charAt(i);
                }
                backslashFound = false;
            }
        }
        return newTermString.getBytes();
    }



    public static boolean getSendUnsolicitedIoAlerts() {
        return RegistryUtils.getRegistryKey(
                String.format("%s/SendUnsolicitedIoAlerts", APPDATA_ROOT), true);
    }



    public static boolean getSendDateStamp() {
        return RegistryUtils.getRegistryKey(
                String.format("%s/SendDateStamp", APPDATA_ROOT), true);
    }



    public static boolean getSendCounts() {
        return RegistryUtils.getRegistryKey(
                String.format("%s/SendCounts", APPDATA_ROOT), false);
    }

}

The last file included is the Jrmon file. This file contains only one type of function, the parseCommand function. Depending on the parameters given to the parseCommand function, it will perform differently. The first function grabs a string sent from another device to find the beginning and end of that string. The second instance of parseCommand interprets the string coming in and performs the instructions the string says to do.

import com.integpg.system.JANOS;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;

public class Jrmon {

    public static byte[] parseCommand(String lowerCommand) throws IOException {
        if (0 <= lowerCommand.indexOf(",")) {
            int begin = 0;
            while (begin >= 0) {
                int end = lowerCommand.indexOf(",", begin + 1);
                parseCommand(lowerCommand, begin, end);
                if (end >= 0) {
                    end++;
                }
                begin = end;
            }
        } else {
            return parseCommand(lowerCommand, 0, lowerCommand.length());
        }

        return null;
    }



    public static byte[] parseCommand(String lowerCommand, int beginIndex, int endIndex) throws IOException {
        if (endIndex == -1) {
            endIndex = lowerCommand.length();
        }
        lowerCommand = lowerCommand.substring(beginIndex, endIndex);

        // used for caching the action
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(baos);

        int i, n;
        int states = 0;
        int mask = 0;
        int parameter = 1000;
        boolean close = false;
        boolean open = false;
        boolean toggle = false;
        boolean reset = false;
        boolean setcounters = false;
        boolean dosetcounters = false;
        boolean pulse = false;
        boolean value = false;
        boolean stop = false;
        int shift = 0;

        int len = lowerCommand.length();
        char[] chars = new char[len];
        lowerCommand.getChars(0, len, chars, 0);

        int currentStates = (int) JANOS.getOutputStates();
//        System.out.println("Current States: " + Integer.toBinaryString(currentStates));
        states = currentStates;

        for (i = 0; i < len && !stop; i++) {
            /* each character in the comand */
            switch (chars[i]) {
                case 'c':   // [C]lose sets relay state to 1
                    close = true;
                    open = false;
                    toggle = false;
                    reset = false;
                    setcounters = false;
                    break;  // next character

                case 'o':   // [O]pen sets relay state to 0
                    open = true;
                    close = false;
                    toggle = false;
                    reset = false;
                    setcounters = false;
                    break;  // next character

                case 't':
                    open = false;
                    close = false;
                    toggle = true;
                    reset = false;
                    setcounters = false;
                    break;  // next character

                case 'p':   // [P]ulse indicates that changes are pulsed
                    pulse = true;
                    break;  // next character

                case '=':   // acquire parameter
                    // extract just the content after the '='

                    StringBuffer sb = new StringBuffer();
                    for (n = i + 1; n < lowerCommand.length(); n++) {
                        char c = lowerCommand.charAt(n);
                        if (!Character.isDigit(c)) {
                            break;
                        }
                        sb.append(c);
                        i++;
                    }

                    String cmd = sb.toString(); //command.toString().substring(i+1).trim();
                    // check that only digits are here

                    for (n = 0; n < cmd.length(); n++) {
                        if (!Character.isDigit(cmd.charAt(n))) {
                            return null;
                            // obtain the integer value
                        }
                    }
                    parameter = new Integer(cmd).intValue();
                    value = true;
//                    stop = true;   // ends command interpretation
                    break;

                case 's':   // sets selected counters
                    dosetcounters = true;
                    setcounters = true;
                    open = false;
                    close = false;
                    toggle = false;
                    reset = false;
                    break;

                case '1':   // digit sets up state and mask

                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                    if (!open && !close && !toggle && !reset && !setcounters) {
                        return null;
                    }
                    n = (int) (lowerCommand.charAt(i) - '1');
                    if (shift == 0) {
                        mask |= (1 << n);
                        if (close) {
                            states |= (1 << n);
                        } else if (open) {
                            states &= ~(1 << n);
                        } else if (toggle) {
                            states ^= (1 << n);
                        }

                    } else {
                        mask |= (1 << (n + 8 * shift));
                        if (close) {
                            states |= (1 << n << (8 * shift));
                        } else if (open) {
                            states &= ~(1 << n << (8 * shift));
                        } else if (toggle) {
                            states ^= (1 << n << (8 * shift));
//                            System.out.println("Toggle States: " + Integer.toBinaryString(states));
                        }
                    }
                    shift = 0;
                    break;  // next character

                case '+':
                    shift++;
                    break;

                case '*':    // means all

                    if (!open && !close && !reset && !setcounters) {
                        return null;
                    }
                    mask |= 0xffffffffL;
                    if (close) {
                        states = 0xffffffff;
                    } else if (open) {
                        states = 0;
                    }

                    break;  // next character

                case ' ':   // white space ignored

                    break;  // next character

                default:    // error in command
            }
        }
        /* each character in the command */

        // extra parameter is an error
        if (value && !pulse && !dosetcounters) {
            return null;
            // parameter conflict - can't use both [S]et and [P]ulse in the same command line.
        }
        if (value && pulse && dosetcounters) {
            return null;
            // [S]et Counters command requires a parameter
        }
        if (dosetcounters && !value) {
            return null;
            // if mask is nonzero then we have changes to make on the way out
        }
        if (mask != 0) {
            if (pulse) {
                /* this action is pulsed */
                if (dos != null) {
                    dos.writeByte(1);
                    dos.writeShort((short) mask);
                    dos.writeShort((short) states);
                    dos.writeInt(parameter);
                    JANOS.setOutputPulsed(states, mask, parameter);
                }

            } /* this action is pulsed */ else if (!toggle) {
                if (dos != null) {
                    dos.writeByte(2);
                    dos.writeShort((short) mask);
                    dos.writeShort((short) states);
                }
                JANOS.setOutputStates(states, mask);
            } else if (toggle) {
                baos = new ByteArrayOutputStream();
                dos = new DataOutputStream(baos);
                JANOS.setOutputStates(states, mask);
            }
        }

        return baos.toByteArray();
    }
}

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

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

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

public class CommunicationPost2 {

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

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

        while (running == true) {

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

            switch (choice) {

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

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

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

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

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

    }

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

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

    }

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

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

    public static Json getInfoToJson() throws IOException {

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

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

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

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

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

        return newJson;

    }

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

        MessagePump MESSAGE_PUMP = new MessagePump();

        MESSAGE_PUMP.open();

        while (true) {

            boolean checkmsg = false;

            int fixedtype = getresponse(MESSAGE_PUMP);

            if (fixedtype == 1300) {

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

            while (checkmsg == false) {

                SystemMsg msgRecieved = MESSAGE_PUMP.getMessage(1301);

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

            }

        }

    }

}

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

    public static int getresponse(MessagePump closesPump) throws IOException {

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

        while (running == true) {

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

            switch (choice) {

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

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

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

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

public static Json getInfoToJson() throws IOException {

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

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

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

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

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

        return newJson;

    }

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

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

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

    }

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

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

public class MessagePumpEngine implements Runnable {

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

    private static Thread _thread;

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

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

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

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

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

            // notify all of our listeners

            if (systemMsg.type == 1300) {

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

            }

        }

    }

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

}

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

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

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

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

            if (systemMsg.type == 1300) {

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

                    }
                }

            }

        }

    }

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

 while (checkmsg == false) {

                SystemMsg msgRecieved = MESSAGE_PUMP.getMessage(1301);

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

            }

There has been an update to the Update Project for OS 4.8.

Name Version Release Date Size MD5
Series 3 All-In-One v4.8 Nov 11 2019 251.7 KB ef6a2613188b70725b155c9c026064b3

Browsers no longer support Java Applets.  Therefore, the web files have been removed from the JNIOR in this update. This fixes flash space issues. The applets can still be loaded from within the JNIOR Support Tool.  To load the Applets, right click on the JNIOR  in the Beacon Tab.  Then go to Tools -> Open Classic Monitor, Configure, Control Application.