Name Version Release Date Size MD5
Tasker v12.0 Jun 20 2023 2.9 MB edfd2578eccdf8595b4f3d35f1ca4bf8
SNMP v3.0 May 14 2020 116.0 KB 2f6f4b7686a9df9a119048a87ce174a4

Tasker is an application that runs on the JNIOR allowing Tasks to be executed manually, triggered, scheduled, or via remote connection.  Tasks are a series of actions.  Those actions can be I/O related, logging, email or network based.

Tasker is not loaded on the JNIOR when it is manufactured. It is an add-on application. Please follow the Installing Tasker guide to get Tasker installed and running on your JNIOR.

No Code, No Programming Necessary. Just configure it and go!

Writing JAVA code on the JNIOR is great and the perfect way to achieve the functionality you want. But, sometime you either don’t have the knowledge, expertise or time and want something out-of-the-box that you can just configure to get the job done. Tasker is the application for you!

Tasker is an update to the Task Manager application that was first created for the JNIOR Series 3 in 2007. It was ported to the JNIOR Series 4 but the core code was kept the same. A rewrite allows us to implement many new features.  A comparison page between Tasker and Task Manager highlights many of those changes.

Here is a high level list of the features in Tasker

  • Workspace – Workspaces are different configurations of Tasker users can create that can be loaded, edited, and deleted.
  • Tasks – Tasks, or series of actions, can be created that can be executed in a variety of ways. Tasks can now be executed manually, triggered, scheduled, or via remote connection.
  • Devices – Some Actions used in Tasks may depend on Devices. Devices are names given to connections. The names are then used to make configuration easier.
  • Signals – A name given to an I/O point or sensor to make using it in an Action, Trigger or Logger easier.
  • Triggers – A set of conditions to watch for and when met a Task is executed. The signals that are configured will be used.
  • Schedule – Scheduling a Task to execute One Time, Daily, Weekly or by event has never been easier. Events that can cause a Task to be executed are On Boot, Sunrise and Sunset.
  • Loggers – Configuration to make logging easier to use in Task Actions. Loggers ask you to provide a file path, the information to be logged to the file and the retention count. The retention count is the number of files for that specific logger that will be kept.

Upgrading from Task Manager or previous version of Tasker

This version of Tasker uses a different configuration file layout. It is NOT compatible with Task Manager or the previous version of Tasker.

We are here to help. we can help you configure the new version of Tasker to work with your older configuration.

If you have any suggestions or a need for Tasker please Contact Us!

Leaving any device set with a factory Username and Password is a big security risk. The JNIOR comes with two factory Administrator accounts. The usernames jnior and admin both have administrative privileges.

It is recommended that the users be modified on the JNIORs to secure them. This is vital for units that are on the open network and have public access. Public access does not mean that they are supposed to be open to the public but rather that they are accessible from anywhere.

List the Users

To see the current users and their privileges, you will use the users command. This command will show you the usernames, their assigned user id and the privileges that they possess.

JNIOR user levels

Change the password for the current user

The simplest change that you can make to secure your unit is to change the password of one of the factory administrator accounts. To do this you will log in to the one you want to change and use the passwd command. You will be prompted for the current password and then twice for the new password. The password requirement is very relaxed but you are encouraged to create a strong password.

JNIOR change password

Create a new user

We need to create a new administrator account so that we can disable the factory users. Using the useradd command with the -a option will create a new user with administrator privileges. You will be prompted for the new password twice. The password requirement is very relaxed but you are encouraged to create a strong password. After the user is created you can execute the users command again to see the results.

JNIOR new user

Disable the Admin account

To disable an account you will use the usermod command. This command modifies the privileges for the given user. To disable the admin account you will type usermod +d admin. Then you can execute the users command again to see the result.

JNIOR disable admin

Change the current user

After creating the new user you will want to switch to it so that modifications can be made to the user account that was logged in while the new user was created. You will do this by closing the command line connection. This will log you out from the current user. Then reconnect using the new user and the new credentials. You can see what the current user account is by using the whoami command. Next, you can then modify the privileges of the user that was used to create the new account.

JNIOR change user

Things to watch out for

You MUST have at least one admin account on the JNIOR. If you try to do so, you will simply be told that you cannot remove that user.

You also cannot remove admin privileges from the user that you are logged in as while performing that operation. If you try to do that you will be told that you cannot alter the current account.

What to do if the admin credentials are forgotten

If you lose the credentials then the only option is SAFE MODE. SAFE MODE will restore the default “jnior” username and password while in SAFE MODE. The accounts will not be altered after exiting SAFE MODE.  The requires physical access to the JNIOR.

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

Some Series 3 are not up to date enough to appear in beacon, which can make it very confusing to update to a new version in the Support Tool. Here is how to update a JNIOR that isn’t showing in the Support Tool.

The first thing you’ll want to do is make sure that you have the support tool downloaded from INTEG’s website. Here is the link to download the support tool. You’ll also want the newest all-in-one update project for the Series 3, which will include the newest version of the OS inside of it. The Series 3 all-in-one is located here.

Once you have both of those downloaded, you’ll want to open the support tool and go to the update tab.

Update tab for the JNIOR Support Tool

Once you are at the update tab, you want’ll to click the open project button and select the series 3 all-in-one update project you downloaded earlier and open it in the update tab.

All-in-one Update Project

Now that the all-in-one is uploaded to the update tab, we want to publish it to the JNIOR. Click the Publish button and a dialog will appear to select JNIOR’s from Beacon to update. The issue here would be that the JNIOR you need to update won’t appear since its version doesn’t have beacon. What to do instead is to type the IP of the JNIOR in the space provided.

Custom IP field for JNIOR update selection

Once you have typed the JNIOR’s IP into the IP space provided, click OK and the JNIOR should begin to update. Afterwards, it should now appear in beacon.

Either when using a certain custom application, or creating one on your own, there may be a want to set an application when a JNIOR turns on. Many applications people use have this functionality, like Cinema. Here is the process for setting an application to run on boot.

NOTE: Before you can set the run key, you have to make sure that the application is installed on your JNIOR. If you are installing a custom application not in an update project, when adding the application to the JNIOR you should place the file in the flash folder since not only is that where we reference the program in this example, but its where applications on the JNIOR are kept when they are loaded on through update projects. So if you are installing an update project, the file will automatically be placed in the flash folder.

The way to make a project start on boot is by creating a registry key in the run registry of the JNIOR you want the application to be on. To access the registry of a JNIOR, you need to either open the support tool and right click a JNIOR to open a web page, or type the JNIOR’s IP address into a web browser URL.

Once you are on the JNIOR’s webpage, you’ll want to click onto the Registry tab. Going through the registry, you’ll want to find the run registry, and there you will create a new registry key for your application.

Once you are in the run registry folder, you’ll click new at the top left which will display the following dialog box.

In the key section you’ll want to type the name of your application so it will look like run/(application name). In the content section, you’ll want to enter the line flash/(application name).jar. This will make it so that when you boot your JNIOR, application will appear in your flash folder along with other applications.

For example, if we had an application called ExampleProgram that we wanted to run on boot we would enter in the key field ExampleProgram so it would look like run/ExampleProgram. Then in the content field, you’d enter flash/exampleprogram.jar, since that is where we placed the application previously.

The application won’t start running after that registry key is set, you’ll have to reboot after to get it to start. Once that registry key is created though, anytime the JNIOR reboots the application will launch when the JNIOR loads back up!

To update JANOS, the JNIOR Series 4 Operating System, you will need to do two things.

  1. Get the UPD file on the JNIOR in the /temp directory.
  2. Execute the jupdate command. To force the update to happen immediately upon entering this command we will type jrupdate -fup /temp/janos_filename.upd

The easiest way to update the unit remotely is using the DCP The DCP is most likely to be available on a remote JNIOR because it uses a single port for all of the features. Without the DCP you might need port 23 for telnet, port 21 for FTP commands, and many other ports for FTP data.

If the DCP is available follow this procedure.

  1. Once the DCP is open, navigate to the Console tab.
  2. Login to the command console.
  3. Drag and drop the JANOS .upd file into the console window. This automatically stores the file in the /temp directory.
  1. Issue the jrupdate command. Like above, to force the update to happen immediately upon entering this command we will type jrupdate -fup /temp/janos_filename.upd
Name Version Release Date Size MD5
DMX Application v4.0 Feb 14 2024 659.7 KB 337a19fd86b00ce205d1da58d99bd4b8
DMX Manual Sep 14 2020 922.7 KB 28dcb24bf9c052319429011661d4e027
DmxPort for enabling DMX on 410 Mar 17 2021 3.5 KB 299c66717c03a9c9b702716d9d56d095

A Bug Error Executing DMX Script from Client Connection was identified in version 3.4.  Please update to version 3.5.

The DMX application has multiple tabs: fixture types, physical fixtures, scripts, triggers, tools, help.

Fixture Types tab for DMX application on JNIOR

Each tab helps the DMX application create a script for fixtures on certain channels:

  • In the fixture type tab, you create fixture types that you can select from the physical fixtures, which defines for them the amount of channels that fixture uses.
  • In the physical fixtures tab you set the initial channel and fixture type of that physical fixture.
  • The scripts tab let you create a script that sets the addresses of DMX channels.
  • The triggers tab activates scripts based off if inputs or outputs are toggled.

Scripts are what is used to control the functionality of you fixtures through the DMX application.

When creating a script, you’ll have you’ll be given a dialog box to type your script, along with a bunch of help options to the right of the script to show you what is possible in the DMX script. You can also click the link on the right to automatically add that feature into your script! An example script below changes all the channels to zero, and then after waiting for .5 seconds sets channels 1,3,and 5 to 125.

Scripts for DMX Application

You can set triggers to activate your scripts.

When setting the triggers, each line corresponds with a number for the inputs and outputs located on the JNIOR. You can set any script you created to run from an input/output being activated, running either one, multiple, or an infinite number of times (until an abort is called). The picture below is for a 412 JNIOR so it will only have 4 inputs triggers and 12 outputs triggers.

Triggers for DMX application

Clicking the drop down of the tools bar lets you access either the DCP web page, or the DMX panel which displays all 512 DMX channels that lets you view in real time all the channels in their current state and while running through a script.

Control Panel for DMX application

This post goes over an application that reads inputs being activated as a pattern using their state mask. However many times inputs are triggered within a certain amount of milliseconds between them, those inputs state masks are added to the pattern already there. This continues until there is an input not within the set amount of milliseconds. Then it prints the pattern and begins creating a new one.

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

After setting up the project, it will require two files. This first one we create is called MultiplexSample, which calls the run function we will create in the second file.

//function runs the application
public class MultiplexSample {

    public static void main(String[] args) {
        
        InputMultiplexer inputMultiplex = new InputMultiplexer();
        inputMultiplex.run();
        
    }
    
}

After that we begin creating the second file called InputMultiplexer. The first thing we include in the project is the import statements. The two imports we use are for IoEvents and the Iolog. We will use these to get the input’s information and process them as patterns.

We then declare the global variables. The first one we declare is the _iolog object. The Iolog keeps track of all IoEvents, including inputs. We will use the IoEvents from the Iolog to grab the information of the inputs when they are activated.

After this is _eventCaptureTime which grabs the start of when the IoEvents are grabbed at the start of each loop through the application.

We then need a long value called _startOfPatternTime. This is the current time of each new input that comes in. We need this because the program loops every 500 seconds, and if 1 input happens right after the loop ends we’ll miss it. The _startOfPatternTime is subtracted from the _eventCaptureTime which helps us check if that input should keep the pattern going.

The _refreshTimestamp value helps us only grab new IoEvents from the Iolog by only grabbing events past its value.

Next is the IoEvent array _ioEvents which contains the inputs we are processing from the Iolog for the current pattern.

We then have the pattern value, which is the pattern we are constantly building from the IoEvents state mask.

Lastly is PATTERN_WINDOW_DURATION which is the value in milliseconds we are looking to see pass between inputs to determine if they are close enough to combine into a new pattern or process as separate ones.

import com.integpg.system.IoEvent;
import com.integpg.system.Iolog;

public class InputMultiplexer implements Runnable {

    //grabs the Iolog to get IoEvents
    private Iolog _iolog = new Iolog();
    //time of the first input to find the start of the input pattern
    private long _startOfPatternTime = 0;
    //time where Iolog is refreshed as to not look at old IoEvents
    private long _refreshTimestamp = _startOfPatternTime;
    //array of IoEvents to evaluate from the Iolog
    private IoEvent[] _ioEvents;
    //value for checking pattern has more IoEvents to process
    private long _eventCaptureTime = 0;
    //input pattern to log
    private int _pattern = 0;
    //duration between inputs required to continue a pattern
    private static final int PATTERN_WINDOW_DURATION = 100;

Next is the run function, which sets the _eventCaptureTime value, calls the other functions we are about to create, and permanently loops the program every 500 seconds. This makes it so the application is constantly checking the Iolog for new IoEvents.

    //function calls other functions to log input pattern in a infinite loop
    @Override
    public void run() {

        while (true) {

            _eventCaptureTime = System.currentTimeMillis();
            getIoEvents();

            lookForIoPattern();

            try {
                Thread.sleep(500);
            } catch (InterruptedException ex) {

            }

        }

    }

Next is the lookForIoPattern function. This function starts with a for loop that will go through all recent IoEvents from the Iolog and processes their states to begin creating a new pattern.

Next the IoEvent states and mask values have an & operation done to get which states have turned high. Next the _startOfPatternTime was set if this was the first input in the pattern. After that _startOfPatternTime would be compared with either the current IoEvent’s timestamp or the _eventCaptureTime value to determine if more inputs need to be added to the pattern. _startOfPatternTime is compared to currentIoEvent’s timestamp to check if timestamps of inputs were within PATTERN_WINDOW_DURATION milliseconds of each other, which in this example is 100 milliseconds. If the 500 milliseconds that the program is looping in ends as another input comes in, then its compared to the _eventCaptureTime value to check if another input came in within 100 milliseconds of the last input, even if the loop ended. This would prevent patterns from getting cut of from one another because of the 500 millisecond loop.

//function checks the IoEvents of the Iolog and evaluates an input pattern
    private void lookForIoPattern() {

        //grabs the IoEvents of the Iolog
        for (int index = _ioEvents.length - 1; index >= 0; index--) {

            IoEvent currentIoEvent = _ioEvents[index];

            System.out.println(currentIoEvent.timestamp);

            //This value shows the state total for the IoEvent
            System.out.println("This is the current I/O state: " + currentIoEvent.states);
            //This value shows which input values were activated for the IoEvent
            int statesTurnedHigh = currentIoEvent.states & currentIoEvent.mask;
            System.out.println("This is the value of statesTurnedHigh: " + statesTurnedHigh + "\n");

            //grabs the start of the input pattern if its hasn't been assigned yet and if enough time passes between IoEvents it evaluates the pattern,
            //otherwise the pattern has an or statement performed on it with the current IO event
            if (statesTurnedHigh != 0) {

                if (_startOfPatternTime == 0) {
                    _startOfPatternTime = currentIoEvent.timestamp;
                    System.out.println("Start of pattern found.");
                }
                if (currentIoEvent.timestamp - _startOfPatternTime > PATTERN_WINDOW_DURATION) {
                    evaluatePattern(_pattern);
                    _startOfPatternTime = currentIoEvent.timestamp;
                    _pattern = currentIoEvent.states;

                } else {
                    _pattern = currentIoEvent.states | _pattern;
                }

            }

        }

        //checks if the pattern is done or if the loop needs to grab more IoEvents
        if (_startOfPatternTime != 0) {

            if (_eventCaptureTime - _startOfPatternTime > PATTERN_WINDOW_DURATION) {

                evaluatePattern(_pattern);
                _startOfPatternTime = 0;
                _pattern = 0;

            }
        }
    }

The last two functions are the evaluatePattern function and the getIoEvents function. The evaluatePattern function simply grabs the pattern created from the lookForIoPattern function and prints it. The getIoEvents function is what is called to grab the most recent IoEvents from the IoLog.

 //prints out the Input Pattern
    private void evaluatePattern(int createdPattern) {

        System.out.println("Pattern is " + createdPattern);

    }



    //grabs IoEvents and puts them into a class level array
    private void getIoEvents() {

        _iolog.refresh(_refreshTimestamp);
        _ioEvents = _iolog.getInputEvents();

    }

}

After adding those last functions, you should now have program that grabs all the inputs activating on the JNIOR, and creates a state pattern depending on how close each input’s timestamp is. Below is the code for the full application. Make sure you set the MultiplexSample file as the main function of your project.

//function runs the application
public class MultiplexSample {

    public static void main(String[] args) {
        
        InputMultiplexer inputMultiplex = new InputMultiplexer();
        inputMultiplex.run();
        
    }
    
}
import com.integpg.system.IoEvent;
import com.integpg.system.Iolog;

public class InputMultiplexer implements Runnable {

    //grabs the Iolog to get IoEvents
    private Iolog _iolog = new Iolog();
    //time of the first input to find the start of the input pattern
    private long _startOfPatternTime = 0;
    //time where Iolog is refreshed as to not look at old IoEvents
    private long _refreshTimestamp = _startOfPatternTime;
    //array of IoEvents to evaluate from the Iolog
    private IoEvent[] _ioEvents;
    //value for checking pattern has more IoEvents to process
    private long _eventCaptureTime = 0;
    //input pattern to log
    private int _pattern = 0;
    //duration between inputs required to continue a pattern
    private static final int PATTERN_WINDOW_DURATION = 100;



    //function calls other functions to log input pattern in a infinite loop
    @Override
    public void run() {

        while (true) {

            _eventCaptureTime = System.currentTimeMillis();
            getIoEvents();

            lookForIoPattern();

            try {
                Thread.sleep(500);
            } catch (InterruptedException ex) {

            }

        }

    }



    //function checks the IoEvents of the Iolog and evaluates an input pattern
    private void lookForIoPattern() {

        //grabs the IoEvents of the Iolog
        for (int index = _ioEvents.length - 1; index >= 0; index--) {

            IoEvent currentIoEvent = _ioEvents[index];

            System.out.println(currentIoEvent.timestamp);

            //This value shows the state total for the IoEvent
            System.out.println("This is the current I/O state: " + currentIoEvent.states);
            //This value shows which input values were activated for the IoEvent
            int statesTurnedHigh = currentIoEvent.states & currentIoEvent.mask;
            System.out.println("This is the value of statesTurnedHigh: " + statesTurnedHigh + "\n");

            //grabs the start of the input pattern if its hasn't been assigned yet and if enough time passes between IoEvents it evaluates the pattern,
            //otherwise the pattern has an or statement performed on it with the current IO event
            if (statesTurnedHigh != 0) {

                if (_startOfPatternTime == 0) {
                    _startOfPatternTime = currentIoEvent.timestamp;
                    System.out.println("Start of pattern found.");
                }
                if (currentIoEvent.timestamp - _startOfPatternTime > PATTERN_WINDOW_DURATION) {
                    evaluatePattern(_pattern);
                    _startOfPatternTime = currentIoEvent.timestamp;
                    _pattern = currentIoEvent.states;

                } else {
                    _pattern = currentIoEvent.states | _pattern;
                }

            }

        }

        //checks if the pattern is done or if the loop needs to grab more IoEvents
        if (_startOfPatternTime != 0) {

            if (_eventCaptureTime - _startOfPatternTime > PATTERN_WINDOW_DURATION) {

                evaluatePattern(_pattern);
                _startOfPatternTime = 0;
                _pattern = 0;

            }
        }
    }



    //prints out the Input Pattern
    private void evaluatePattern(int createdPattern) {

        System.out.println("Pattern is " + createdPattern);

    }



    //grabs IoEvents and puts them into a class level array
    private void getIoEvents() {

        _iolog.refresh(_refreshTimestamp);
        _ioEvents = _iolog.getInputEvents();

    }

}

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.