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

Dolby has resolved the issue for the IMS3000.

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

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

To determine if this is the issue:

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

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

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

The bytes should represent the password like this…

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

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

To do that:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Why did this happen?

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

How do we fix it?

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

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

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

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

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

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

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

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

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

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

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

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

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

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



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

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

    }



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

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



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

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

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

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

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

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

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



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

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

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

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

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

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

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



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

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



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

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



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

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

}

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

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

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

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

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

public class SerialControlClient
        implements ClientListener {

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

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

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

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

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

    private final AsciiCommandClient _asciiCommandClient;



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

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



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



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



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



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

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

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

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

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

    }



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

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

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



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

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



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

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



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

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

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

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

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

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


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

        }
    }

}

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

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

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

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

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

public class Config {

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



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

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



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



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



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



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



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

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



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



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



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

}

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

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

public class Jrmon {

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

        return null;
    }



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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                case '+':
                    shift++;
                    break;

                case '*':    // means all

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

                    break;  // next character

                case ' ':   // white space ignored

                    break;  // next character

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

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

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

        return baos.toByteArray();
    }
}

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

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

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

public class CommunicationPost2 {

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

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

        while (running == true) {

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

            switch (choice) {

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

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

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

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

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

    }

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

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

    }

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

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

    public static Json getInfoToJson() throws IOException {

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

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

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

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

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

        return newJson;

    }

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

        MessagePump MESSAGE_PUMP = new MessagePump();

        MESSAGE_PUMP.open();

        while (true) {

            boolean checkmsg = false;

            int fixedtype = getresponse(MESSAGE_PUMP);

            if (fixedtype == 1300) {

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

            while (checkmsg == false) {

                SystemMsg msgRecieved = MESSAGE_PUMP.getMessage(1301);

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

            }

        }

    }

}

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

    public static int getresponse(MessagePump closesPump) throws IOException {

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

        while (running == true) {

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

            switch (choice) {

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

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

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

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

public static Json getInfoToJson() throws IOException {

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

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

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

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

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

        return newJson;

    }

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

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

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

    }

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

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

public class MessagePumpEngine implements Runnable {

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

    private static Thread _thread;

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

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

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

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

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

            // notify all of our listeners

            if (systemMsg.type == 1300) {

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

            }

        }

    }

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

}

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

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

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

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

            if (systemMsg.type == 1300) {

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

                    }
                }

            }

        }

    }

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

 while (checkmsg == false) {

                SystemMsg msgRecieved = MESSAGE_PUMP.getMessage(1301);

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

            }

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

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

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

Creating the Libraries

Code examples for the JNIOR use a library that makes creating applications far easier on the JNIOR. To get started first download the SDK file containing the libraries below.

Name Version Release Date Size MD5
Embedded JNIOR SDK v1.0 Nov 08 2019 1.6 MB 75233695d8010ef8bea3a791c76d43be

Once this file is downloaded, you’ll want to extract the files to a folder you create.  Make sure you remember the file location, as we’ll be using it later. You can get it by right clicking the location bar at the top when in the file explorer.

Once you’ve extracted the SDK, you’ll then want to setup the library in your IDE. INTEG creates applications for the JNIOR through NetBeans, so how to setup the library will be explained in NetBeans. In NetBeans, you’ll want to create the library by going to Tools -> Libraries. This will open the Ant Library Manager Dialog Box.

Library option for JNIOR in netbeans

You’ll then want to create two new libraries by selecting the New Library button at the bottom left of the dialog box. This will prompt the New Library Dialog Box which will ask you what you want to name the library and what type it is. When naming, name the library with the _Common.jar file as INTEG.Common and the Janos_Runtime1.8.jar file as JanosLibrary. Once you finish leave the type as it is and click OK. Then you will need to add the _Common.jar file from the SDK file you extracted previously to the library you create which will now be on the left. You can then repeat this process for the Janos_Runtime1.8.jar file.

Adding The Library To A Project

Now that the libraries are created, we’ll want to add them to our project. If you haven’t created a project yet, create a java project to add a library to. Once the project is created, click on File -> Project Properties. This will open the Project Properties Dialog Box of the current project.

Project Properties for JNIOR Netbeans Project

Here we’ll want to click on the Libraries Category and click the Add Library button to add the Janos_Runtime1.8 Library and then the _Common Library we created previously.

Project Properties for JNIOR Netbeans Project

Once the libraries are added, click OK and the project should now include the libraries in them. One thing to note is that the size of the libraries are very big, and including every class in the library might take a lot of space in the JNIOR, so we are going add some code to the xml.build file to only include classes we use from the library in our project. If you want to learn more about editing the xml.build file, click here.

Adding Only The Classes Needed

In NetBeans, go over to the Projects, Services, Files tabs and select the Files tab. Inside the project’s files should be a file called xml.build. Open that file by double clicking it.

Files for JNIOR netbeans project

Once the file is open, scroll down to the bottom of the file right before the </project> tag at the files end. The first bit of code we are adding is to set the bootclasspath and reference the correct place the files are in. Take the code below and copy and paste it above the </project> tag. One change you’ll need to make to this code is where there is capitalized text, it needs to be changed to file location of the SDK file that was extracted and mentioned at the beginning of this tutorial.

<target name="-pre-init">
<!--  tell the compiler about our library 
  --> 
  <property name="javac.compilerargs" value="-bootclasspath '${libs.JanosClasses.classpath}'" /> 
<!--  set a property for the INTEG SDK 
  --> 
  <property name="janossdk.home" value="INSERT FILE PATH HERE" />  <!--  set a property for the javadepencies application 
  --> 
  <property name="janossdk.javadependencies" value="${janossdk.home}/JavaDependencies.exe" /> 
</target>

Here is where this code should be inserted for the libraries to only use classes referenced in the project. Take the code below and copy and paste it below the previous code, but not past the  </project> tag.

<target name="-pre-jar">
 <!-- 
 before we build the JAR file we need to get all of the classes out of the 
        library that our application depends on 

  --> 
  <echo>Update classes directory with library dependencies</echo> 
      <exec dir="" resolveexecutable="true" 
          executable="${janossdk.javadependencies}">
      <arg line="${build.classes.dir} '${libs.INTEG.Common.classpath}'" /> 
  </exec>
</target>

Also add the code below so the jar file that you throw into the JNIOR to run it is easier to find. It also updates the current version of the project. Copy and paste this right below the previous code you just added, but not past the </project> tag.

<target name="-post-jar">
   <!-- 
      copies the built JAR file out of the dist directory and into the root 
      of the project folder 
  --> 
  <copy file="${dist.jar}" todir="." /> 
  </target>

Now your project should be able to call pre-made classes to help you create the application you want, while not calling every class available and take up space on the JNIOR!

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.

If the JNIOR series 4, models 410, 412 or 414, is not working with the GDC Cinema Server then check to make sure MODBUS is enabled. The built-in library for the JNIOR on the GDC server uses MODBUS.

MODBUS communicates on port 502.

The series 3 enabled the MODBUS server by default. The MODBUS server is an optional application that needs to be enabled on the series 4. You can look at the following links for more information

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!

Run Linux or Mac?

The JNIOR Support Tool has always been a Windows application. There have been ways to get the Support Tool to run on a Mac but I am not sure anyone has been successful on a Linux box.

Well work has begun to supply the features of the Support Tool in a cross-platform Java application. The thought will be to implement the Beacon and Snapshot functionality first and then the Update tab.

JNIOR Supporter Application

This project is in its infancy. Please let us know if we should continue working on it and prioritze it.

The iBoot device from dataprobe is a Web Enabled Power Switch. We can command the devices by issuing HTTP Requests. To do this we will add an iBoot device to the Devices file and add “Power On” and “Power Off” actions to the Macro file. We then associate the actions with macros. We might want separate macros for power on, power off and power cycle.

To create the iBoot 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 the HTTP Request device type.

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 iBoot device. Now click on Action and select “GET”. In the data field enter the resource portion of the URL””. It should look like this… Remember to enter the username and password if needed.

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

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…

The Cinema application for the JNIOR enhances the JNIOR capabilities. While the JNIOR can be used in its most basic form, the Cinema application provides the ability to execute macros, sequence of actions.

The Cinema application does not ship preinstalled. You MUST obtain the application from our website. There is a download on the website that will be opened in the JNIOR Support Tool and published to the JNIOR. This is called an Update Project.

Here are links to latest versions of the JNIOR Support Tool and the Cinema application.

NOTE: This link for the Cinema Update Project is Cinema.jar, if you are using a Series 3 JNIOR you need Cinema.jnior instead. This can be retrieved from the legacy section of our downloads on our site.

Name Version Release Date Size MD5
JNIOR Support Tool v7.16 Jun 12 2024 6.2 MB b75a95c649dd507e069591709265f793
Cinema.jar - Update Project v6.9 Jan 03 2024 545.1 KB 0a2c670e461116768b75288e652c5253

After installing both the JNIOR Support Tool and the Cinema Update Project, you’ll want to open the Support Tool and click on the Update Tab. Once there, the first thing you’ll want to do is select the Open Project button, and select the Cinema Update Project you just downloaded. When you open the Cinema Update Project in the Support Tool you will see the following.

Cinema Update Project

Click Publish and select the JNIOR you want to update. Once the update is complete the JNIOR will have rebooted and the application will be ready to configure. Configuration is largely dependent on what you are trying to do.

Within the Support Tool you can configure devices and macros. Devices are outbound connections from the Cinema application. For example, a projector, sound processor, or lighting system. Those devices can be serial or Ethernet. We have not implemented very many devices but the ones that are implemented satisfy a very large majority of the installations. If you need a device that is not implemented, you can use the “Raw Serial” or “Raw Ethernet” device. This will allow you to send commands that you define to those devices. Even if you pick a device that we have implemented, you may need to add a new command. You can use the Send action and define the bytes that need to be sent.

The Temperature / Humidity Sensor with Cable

INTEG resells a Temperature / Humidity Sensor that is manufactured by Embedded Data Systems. We often refer to this as the Environmental Sensor. As part of the offering from INTEG you get a 12′ cable with an RJ-12 connector that is tested to work with the JNIOR. We also had to create certain applications that work with the module. JANOS only natively supports INTEG expansion modules.

To wire the sensor you will need 3 wires that are for Power, Ground and One Wire Data. Even though the JNIOR uses an RJ-12 connector with 6 pins, only 3 are needed.

In the picture below you can see the 3 wires that are connected. We wire the flat black cable through the side of the sensor. The sensor does come with a mounting plate with a hole in the center. If you wire the unit yourself, you can use that hole for your installation. We chose not to use the bracket so that the device can be mounted flush without the need for rewiring or cutting a hole in the wall or surface.

Humidity Sensor expansion module for JNIOR

A closer look shows us the connection in the terminal block that each wire is connected to. As with INTEG modules, this terminal block gives you the ability to daisy-chain multiple devices.

Wiring for JNIOR Humidity Sensor

The wire needed is a modular flat 6 conductor cable. We use this one from digikey. The other end of the wire is the RJ-12 connector. We use pins 1 – 3. With the connector tab up you can see that the wires in use are on the right.

Wire end for expansion module sensor cable

You can use the Cinema application to execute DMX scripts. Those scripts can be on the same JNIOR or on another JNIOR. Both situations require making a TCP or Raw Ethernet connection to port 10000.

To execute a script once you will send go SCRIPTNAME\r\n

To execute a script a certain number of times you will send the same command but with a repeat parameter. For example, to execute 3 times we would send go SCRIPTNAME -r 3\r\n

To execute a script and have it repeat forever, until you abort it or reboot the JNIOR, you will send the -f parameter. For example, go SCRIPTNAME -f\r\n

Create a DMX device to use in the Macro

Create a Macro to call a DMX script with the Device we just created

Macro tab of the JNIOR Support Tool

You can use the scheduling in Cinema.jar to schedule macros daily, weekly or monthly.

When Cinema.jar is installed and executed for the first time it will create 3 default keys. They will look like this in the registry.

The configuration is a multi-part registry key. There are three parts. The type, the time of day, and the macro to execute.

Here are examples of the different scheduling types

Daily

You can define a macro to execute at a specific time every day. To do this we use the daily type. An example of executing a macro daily at 3:30pm with the name test would be:

daily, 15:30, test

Weekly

You can define a macro to execute on certain days of the week. To do this we use the 2 character day abbreviations in place of the type field. The 2 character day abbreviations are Su, Mo, Tu, We, Th, Fr and Sa. An example of executing a macro every day of the week at 3:30pm with the name test would be:

sumotuwethfrsa, 15:30, test

Monthly

Macros can be defined to execute on a certain date of the month, every month. To do this we use the monthly type followed the day of the month inside the parenthesis. An example of executing a macro with the name test would be:

montly(15), 15:30, test

You can also select to only execute the macro on certain months. Do do this we use 3 character month abbreviations. Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov and Dec. Only one month can be selected at a time.

sep(15), 15:30, test

Reboot Example

Here is a registry example. Below each schedule rule uses the command ‘reboot’. ‘reboot’ and ‘reboot -a’ are two commands that have been implemented in the scheduling logic that don’t need a macro to perform an action. As their names suggest, ‘reboot’ will perform a reboot and ‘reboot -a’ will perform a reboot -a.

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. Edit the jnior.ini file

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.

jnior.ini file

6. Upload the updated jnior.ini file

To upload the file you have two options. You can use your favorite FTP client or use the DCP. If you use the DCP you will want to go to the Folders Tab. From there you can simply drag and drop your ini file to the temp/ folder. I recommend the temp/ folder for the upload so the file gets cleaned up after a reboot.

7. Ingesting the new configuration

To ingest the new configuration we will run the reg -i command from a command-line connection. Again you have multiple options for making the command-line connection. You can use an application like Putty, The JNIOR Command Line tool that is part of the JNIOR Support Tool or the Console tab in the DCP. If you use the DCP your command will look like this.

reg command for JNIOR command line

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

Configuration tab for JNIOR Web Page

9. Reboot the JNIOR again.

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

Sometimes the JNIOR doesn’t appear to be working but follow these steps and you might be able to determine why. There are a few steps that you can take before contacting INTEG or declaring that the JNIOR is not functioning. Hopefully the steps below will help save some time.

The number one way for INTEG to help provide support is to send us a Snapshot using the Support Tool. We can use the Snapshot to look at logs, analyze your configuration and to look at network interactions. Snapshots are most effective when taken soon after the issue is noticed. If too much time has passed the logs and network information may no longer contain relevant information pertaining to the time of the issue.

Power

The JNIOR requires 12 – 24v DC. We recommend a power supply capable of 1 Amp.

The very first thing to check is to make sure the unit has power. No, I don’t mean “did you plug the JNIOR in?” We need to make sure that the internal power supply is functioning and that the wires going in to the connector on the outside of the JNIOR have not broken.

To check the internal power supply we need to verify that the BLUE LED is illuminated next to the power connector.

If the BLUE LED is not illuminated then we want to make sure the wires have not been broken. This can easily happen if there is stress put on the wires during installation or if the connector was not securely fastened before being shipped.

Operating System

Is the OS running? To check this remove power. Upon applying power the amber light next to the BLUE power LED should come on for two or three seconds and then go out. Another thing we can check is to unplug the Ethernet cable. As of JANOS v1.8 the Status LED will blink Morse code indicating the value of the last octet of the IP Address when the Ethernet cable is unplugged.

Ethernet

The first thing to check regarding Ethernet connectivity is that the lights on the Ethernet port are lit. If they are not, please check both end of the cable to make sure it is connected. If the cable appears to be properly connected and the lights are not lit, please try a different cable.

Check Beacon to see that the JNIOR is on the network. Beacon is a feature on the JNIOR that uses UDP to broadcast itself on the network. The JNIOR Support Tool implements beacon and will query the network for any JNIORs. Any JNIOR that hears the query will respond. Because this feature uses UDP a valid working IP Address is not necessary.

If the cable, lights and beacon all appear to be in working order then the last thing to check here is the IpConfig/Allow setting. That will need to be done using the serial port. You can read about that procedure in the Dangers of IpConfig/Allow.

As of JANOS v1.8 the Status LED will blink Morse code indicating the value of the last octet of the IP Address when the Ethernet cable is unplugged.

Serial Port

The serial port can be used to diagnose JNIOR issues. Most of the time the Ethernet connection will be used to do this but in the case that the Ethernet connection is suspect we will need to use the serial port. There are two serial ports on the JNIOR but the RS-232 port is the is the port that facilitates the command line functionality. To connect to the JNIOR for command line use you will need to use the following settings. 115200 baud, 8 data bits, 1 stop bit and no parity. Once the cable is connected and you launched your favorite serial application you will need to press enter to see the login prompt. You could also reboot the JNIOR to observe the banner.

Configuration

If the hardware steps above do not alert you to the issue you are having then configuration is most likely to blame. Please contact us INTEG for further assistance. We can walk you through configuration issues, connect to your computer via TeamViewer, or determine if your unit needs to be replaced.