Uncategorized

At some point you may want your JNIOR to always have an input/output toggled, and when you reboot a JNIOR, they get reset. This post will show you how to have your JNIOR automatically toggle inputs/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.

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

To close inputs rather then outputs, we’ll still want to be on the configuration tab, but we’ll move to the inputs section.

Name Version Release Date Size MD5
MQTT v3.0 Jan 21 2020 490 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.

Analog Presets is an application that lets 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 from the all downloads page on integpg.com. 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 monitor the environment you will need 3 things. First, a sensor that provides the environmental condition you are looking to monitor. Second, an application that will read that sensor and log the values. Third, a way to view that data.

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.

Set Up

To start, you’ll want to connect the Temp & Humidity Sensor to the JNIOR through the sensor port. After that, you’ll use the grapher application to monitor the sensor. You’ll also want the SNAP application. Here is a link to both:

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’ll type (JNIOR’s IP)/Grapher into the URL and it will 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 enter settings to create a graph. To start, you can go down to the Graphs section and add a graph.

Here at the top you can create the chart name, and then set the range of the chart 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. After that you can add a file that will be monitored to create the graph.

Here you can define the directory path of the file you want monitored. The columns are the lines that you defined in the Graphs section. The Date format can be set to MM-DD-YY HH:mm:ss.SSS. The file count is specified to how many files you have added. Once this is all set you’ll now want to open the snap application by typing in the URL (JNIOR’s IP)/snap. This is to pull information for the graph you want to crate.

Like in the picture above, you’ll want to pull a block function into the block area, and name it. The one in the example is called temp_logs. Then the filename is defined as the one you named in the file section in Grapher. (The date.format part of the file name auto inputs the date when creating the file). The Text is defined as separate values that will act as the lines we created in the graph section of Grapher. So you will only have text values equal to the amount of different lines in the graph your creating.

The values need to be labeled as this in order from top to bottom from Grapher, {{env_[1].fahrenheit}}, {{env_[1].humidity}}. These text values represent one module getting humidity and Fahrenheit. How the text values work is, the double squiggly brackets and comma between each value are needed to separate each value. The env_ is what you have to preface each value with in the text. The “.fahrenheit” has to be replaced with “.celcius”, “.humidity”, or each words first letter like “.f”, “.c”, or “.h” depending on what type of value you are trying to monitor. The number surrounded by brackets represents which module is getting that value so if you only have on module you’d label every bracket with a 1.

As for commands, you can use the extern command to tell you what modules you have connected, and the extern -r command to remove a module on your device to switch to another.

After you define those text values, you’ll want to pull a loop block into the block area. You’ll change the name to the name of the function we just created, and then you can set every time it adds information to the chart. Then you’ll go back to the functions tab and add the function we created into the loop block. Once that is done you should be able to go back to the main Grapher page and see your graph updating with information. Keep in mind that if you ever want more then one graph to be able to display, when creating multiple files in the Grapher configuration page, make sure you also create the blocks for them in snap.

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.

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.

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…

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

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

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

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

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

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

public class CommunicationPost2 {

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

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

        while (running == true) {

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

            switch (choice) {

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

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

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

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

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

    }

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

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

    }

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

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

    public static Json getInfoToJson() throws IOException {

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

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

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

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

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

        return newJson;

    }

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

        MessagePump MESSAGE_PUMP = new MessagePump();

        MESSAGE_PUMP.open();

        while (true) {

            boolean checkmsg = false;

            int fixedtype = getresponse(MESSAGE_PUMP);

            if (fixedtype == 1300) {

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

            while (checkmsg == false) {

                SystemMsg msgRecieved = MESSAGE_PUMP.getMessage(1301);

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

            }

        }

    }

}

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

    public static int getresponse(MessagePump closesPump) throws IOException {

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

        while (running == true) {

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

            switch (choice) {

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

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

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

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

public static Json getInfoToJson() throws IOException {

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

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

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

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

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

        return newJson;

    }

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

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

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

    }

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

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

public class MessagePumpEngine implements Runnable {

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

    private static Thread _thread;

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

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

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

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

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

            // notify all of our listeners

            if (systemMsg.type == 1300) {

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

            }

        }

    }

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

}

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

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

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

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

            if (systemMsg.type == 1300) {

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

                    }
                }

            }

        }

    }

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

 while (checkmsg == false) {

                SystemMsg msgRecieved = MESSAGE_PUMP.getMessage(1301);

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

            }

The JNIOR Support Tool will allow foreign characters in the filenames for macro and device files. Here is a quick video showing how to delete them.

1. Create a console session.
2. login
3. type rm mac
4. Then press tab

The JNIOR will fill in the name of the macro file.  In your case, press tab until you get the file with the offending name.

5. Then use the left arrow key to go back and insert a quote at the beginning of the file name

6. Press enter

You can then go back to the folders tab to see that the file is gone.

Repeat the same steps for the “macro file not found.log” file.

A series 3 JNIOR has a limited amount of flash memory. The update can fail because the the flash is close to being full. There are many files that are loaded on the JNIOR that are not being used.

To get this unit to succeed we will remove files from the flash filesystem. Since the JAVA Applets can no longer be loaded in a browser. This article, https://www.theverge.com/2016/1/28/10858250/oracle-java-plugin-deprecation-jdk-9, explains why browsers stopped supporting the JAVA Applet technology.

Since the Applets can no longer be loaded in a browser there is no reason to keep them in flash so that the web server has access to them. You can safely remove the flash/www contents. You can use the latest Series 3 All-In-One to accomplish this.

Once files have been removed, reboot the JNIOR. Once the JNIOR has booted, try the update project again. If the flash filesystem space was the issue then the update should now work!

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

package httprequesttest;

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

public class HttpRequestTest {

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

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

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

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

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

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


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

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



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



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

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

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

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

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

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

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

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

        return sb.toString();
    }
}

HttpRequest.java

package httprequesttest;

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

public class HttpRequest {

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



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



    public String getRequestMethod() {
        return _type;
    }



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



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



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



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

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

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

        return new HttpResponse(this);
    }



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

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

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

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



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



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



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



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

}

We can command the Nagra myCinema player to start playing its content. To do this we will add a myCinema device to the Devices file and a “Play” action to the Macro file. We then associate the “Play” Action with a macro. It’s that easy!

To create the myCinema device in the devices file go to the Devices tab in the JNIOR Support Tool. Then in the lower left click ‘Add’. Then fill out the information as follows. This device can be added to an existing devices file or a new one. We are going to use a RAW ETHERNET device so that we can send any command we want. The command is a simple text string.

Then click over to the Macro tab and click on Link Devices. This will link the devices file so that the configured devices are available to create macro actions. Now click ‘Add’ below the Macro Action View. You can rename the action. Click in the Device column for the new Action and scroll to the bottom to select your linked device. Select the myCinema device. Now click on Action and select Send. In the data field enter “play”. It should look like this…

You can now add the action to any macro by selecting both the macro you wish to add it to and the new myCinema action. Then use the <- button between the two views like this…

There are many reasons to upgrade a series 3 JNIOR to a series 4. While we believe the experience and reliability are much better with the newer hardware and software we understand that in most cases the “if it isn’t broke, don’t fix it” rule applies.

When you are ready, use the following steps to help the transition go smoothly.

Can I just perform a backup from the series 3 and restore onto the series 4?

You can perform a backup and restore. BUT, this will copy over the cinema.jnior application. This file will not run on the series 4. There is a specific cinema file, cinema.JAR, that will run on the series 4.

If the cinema.jnior file gets copied over there will only be one small issue. A restore will set that file to run on boot. Every time the JNIOR boots it will throw an error regarding the cinema.jnior file being there and being the incorrect format.

The procedure

1. Update the Series 4 Operating System

Make sure the new series 4 JNIOR’s Operating System and Cinema application are up to date. You can find those applications on the All Downloads page.

The Cinema update will ensure that the Cinema application is present that will run on the Series 4. The version of the application that is loaded on the Series 3 will NOT run on the Series 4.

2. Take a Snapshot of the series 3 JNIOR to wish to replace

3. Extract the Snapshot

Open the Snapshot zip file and extract the macro file, devices file and flash/jnior.ini file.

4. Upload the macro and devices files to the destination series 4 JNIOR

5. Upload the configuration

The configuration is stored in the flash/jnior.ini file. Before you upload it we will need to remove two sections from the file. We will remove the [ipConfig] section so that the IP Address remains and the [run] section so that cinema.jnior does not try to start on boot.

6. Enable MODBUS Server (If you are using a GDC Cinema Server)

The MODBUS server ran by default in the series 3 operating system. It is a separate application on the Series 4 that must be enabled to provide MODBUS connectivity.

You can enable MODBUS via the DCP. http://JNIOR_IP_ADDRESS in your browser.

6. Reboot the JNIOR again.

You new Series 4 should be running the Cinema application with the configuration and settings from your previous series 3 JNIOR.

There is a known issue in Support Tool 7.8 for new installs. Click below to download 7.9. If you have trouble launching Support Tool 7.8 it is likely because the INTEG directory was not created at the root level of your drive. If you wish to stay with 7.8 you can create a C:\INTEG\JNIOR Support Tool directory and try to launch the Support Tool again. Sorry for the inconvenience.

Check the Support Tool News Section to get the latest update!

Pronounced “jump”, the JMP protocol is the JANOS Management Protocol. It will be available in JANOS 1.8.

The JMP protocol shares the messaging JSON structure that is used by the DCP (Dynamic Configuration Page) and the web-socket protocol but on port 9220, by default.

To make processing easier we wrapped the JSON message in a two element JSON Array.

[ number, object ]

Where number is the size in bytes/characters of the JSON object that follows.

Doing this relieves us of needing to count braces to see when an entire JSON message was received. To parse the structure we can do the following:

  1. Look for the opening ‘[‘.
  2. Then process numeric values until a ‘,’ is found. White-space is ignored.
  3. White-space is again ignored and N bytes are read.
  4. Finally remaining white-space is ignored and the trailing ‘]’ confirmed.

If the numeric value is invalid, the comma missing or the trailing ‘]’ bracket not found, the entire message is to be ignored. The extracted JSON Object can then be validated as well.


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


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

  ControlPanel Application JAR [ Mar 27 2019, 7 KB, MD5: c573b4c989ea0380729fecae5283e937 ]

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

package com.integ;

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

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

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



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

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

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

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

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

            }
        }

    }



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

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

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

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

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

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

    }



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



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



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

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

        return bytes;
    }
}


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

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



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

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

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

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

            }
        }
        _enumeration = _parameters.keys();
    }



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



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



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



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



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

}


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

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

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

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

            }
        }

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




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

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

...


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


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

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

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

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

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

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

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

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

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