Tag Archive: serial

The JNIOR 410 is capable of handling RS-485. To do this you need to wire it correctly.

While RS-485 is commonly referred to as 2 wire, it really should include a third wire for ground. You can get away with 2 wires as long as there is a common ground between all devices. This is hard to guarantee, especially on long runs. Including the third wire is always a good idea.

RS-485 should also include termination resistors at the end of the bus. This helps prevent reflection on the wire from corrupting the communications.

Here is an article from TI on Ten Ways to Bulletproof RS-485 Interfaces.

2 wire

Again, while referred to as 2 wire, a ground wire is good practice.  In this wiring scenario D- will be landed on pin 3 and a small jumper wire will be used to connect pin 3 to pin 2.  D+ will be landed on pin 7 or pin 8 and a small jumper wire will be used to connect pin 7 to pin 8.

        Signal         DB-9
--------------------  ------
Signal Ground (GND)      5
Data (D-)                2, 3
Data (D+)                7, 8

4 wire

While referred to as 4 wire, a ground wire is good practice.

        Signal         DB-9
--------------------  ------
Signal Ground (GND)      5
RS485 TX-                2
RS485 RX-                3
RS485 RX+                7
RS485 TX+                8

DB9 Breakout Connector

You can make wiring of the DB9 connector easier by obtaining a breakout board like this one from Amazon.

Here is an example of wires landing on pins 7 and 8 with a small jumper wire.

RS485 to RS232 converter

Use an RS485 to RS232 converter and let the JNIOR handle the RS232 data.  This will enable 412 and 414 JNIORs to handle RS485.

Using the JNIOR should be a smooth process, with only having to connect an ethernet cord to your network and finding it in the JNIOR support tool. Unfortunately nothing always goes perfectly, and maybe you aren’t able to access your JNIOR via beacon or an internet browser. Connecting to a Series 3 JNIOR serially is also a great way to troubleshoot when its no longer responding correctly. Luckily, the JNIOR can be connected to serially to access and hopefully fix this issue.

To access the JNIOR serially, a serial cable needs to be connected between the RS-232 port on JNIOR and your computer. If you don’t have a serial port on your computer, you may need a serial to USB cable instead. Once that is connected, you can then open a command line prompt (You can open an easy to use command line from the support tool under the Tools Tab.) and access the serial connection. When doing this, make sure that the setting for the Serial Connection match what your JNIORs are. If you can’t check, the default settings for the connection are:

  • Baud Rate: 115200
  • DataBits: 8
  • StopBits: 1
  • Parity: None

Once you have set the correct serial settings, you should be able to hit connect and access the JNIOR via the command line! If by chance you are trying to change the JNIOR’s IP because its using one that doesn’t work with your local networks IP, you can change the IP of a JNIOR by using the ‘ipconfig’ command. Simply enter ‘ipconfig -a’ followed by a space and the IP address you are trying to change the JNIOR to. For example, you are trying to change the IP address to 10.0.0.201, you would use this in the command line: ‘ipconfig -a 10.0.0.201’.

View on GitHub

This post goes over an application that goes through the Iolog, grabs IoEvents that occur every second, reports their Input States as a String out the AUX Port. 

It starts by declaring a class object that has functions we can use to report our input states. The program first calls a constructor function to open the serial port to report our inputs. After the AUX port opens, another function is called to return the IO events from the Iolog monitor. This function then calls another function to build a message containing the IO events as string values. Once again this function calls one last function, this one then sending off the string we built to the AUX port to report the IO events.

  Serial Control Source Code [ Oct 06 2020, 7 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 sample shows you how to read a barcode scanner using the AUX port. You must have the correct settings to communicate successfully with your device. The settings for the port may be set using the setSerialPortParams() method. This method can only be called after the port has been successfully opened.

package barcodescanner;

import com.integpg.comm.AUXSerialPort;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;



public class BarcodeScanner {
    private static int BAUD = 19200;
        

    public static void main(String[] args) {
        AUXSerialPort auxPort = null;


        try {
            // init the aux port
            auxPort = new AUXSerialPort();
            // open the port
            System.out.println("Open the AUX Serial Port");
            auxPort.open();
            // set the parameters
            System.out.println("Set port parameters to " + BAUD + ", 8, none, 1");
            auxPort.setSerialPortParams(BAUD, 8, 1, 0);
        } catch (Exception ex) {
            ex.printStackTrace();
            // cant open the serial port so exit
            System.exit(-1);
        }


        try {
            // create our reader
            InputStream is = auxPort.getInputStream();
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);

            // read the barcodes
            System.out.println("Ready to Scan Barcodes...");
            String line = null;
            // read the serial port in a loop to get all of the barcodes.  each barcode is 
            // terminated by a linefeed.  That means we can use readLine()
            while ((line = br.readLine()) != null) {
                System.out.println("Barcode: " + line);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

}

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

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

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

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

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

 

JNIOR Serial Ports

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

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

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

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

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

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

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

JNIOR to PC Connection

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

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

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

Connecting a Device to the JNIOR

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

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

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

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

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

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

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

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

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

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

Make sure that the Serial Input Buffer Size is adequate for your application. It is 128 bytes by default. If a greater number of bytes that the buffer size are sent and your application does not read them in time then the buffer can fill and data will be lost.

        // display the current buffer size
        System.out.println("auxSerialPort.getInputBufferSize(): " + auxSerialPort.getInputBufferSize());
 
        // set the new buffer size
        auxSerialPort.setInputBufferSize(256);
        // display the new buffer size
        System.out.println("auxSerialPort.getInputBufferSize(): " + auxSerialPort.getInputBufferSize());

The output should look as follows.

auxSerialPort.getInputBufferSize(): 128
auxSerialPort.getInputBufferSize(): 256