+1-757-461-3022 x124

Serial Port Scripting for Industry 4.0

In this article, we cover the addition of QSerialPort and QSerialPortInfo to xTuple's scripting environment and what that means for you. If you are not familiar with serial ports in general, I would suggest taking a ride down Serial port "memory lane" on Wikipedia.

While originally standardized as an "Interface between data terminal equipment and data communication equipment employing serial binary data interchange" in 1969, serial port communication lives on in many forms  from USB devices and virtual serial ports, all the way back to the 25 or 9 pin connectors that exist on many industrial weighing solutions and other industrial machines. While I will focus on serial ports in this article, in upcoming articles look for discussions of QSslSocket (and QTcpSocket), as well as QTcpServer and QUdpSocket. These are protocols, found on modern machinery, which provide a network connection.

For demonstration, I have chosen a 16x2 Character RGB LCD from Adafruit. The code discussed below will be structured to make working with other LCDs possible with few changes. Keep in mind, though, that with most low-level devices, every vendor implements their own command set. In the case of serial communication, I am talking about specific bytes you send over the wire that make your LCD do the things that you want it to do — such as the position of the text, the brightness or contrast, and in this case even the three separate bytes that control the red, green, and blue values of the backlight. And, of course, let's not forget the bytes that come before which tell the screen you are about to send it those three bytes!

When working with serial ports, you must set parameters about how the two sides will communicate. While the details are covered in many wiki articles, one of the most basic things you need to pay attention to is speed, also known as the baud rate. Unlike the more modern ethernet interface, most serial devices cannot auto-negotiate the speed and expect the user to set it manually. The LCD we are working with comes from the factory expecting 9600 bits per second. While that likely makes some of you cringe, it should be more than sufficient for our needs. Qt provides a helper class for us called QSerialPortInfo, which enables us to ask the system what ports are available and what their capabilities may be. The enterprising among you may figure out that one of the things you can ask for is the list of speeds it supports. But in many instances such as the LCD backpack the device answering this query is telling you what it is capable of, not what speed it is currently set to use. Some devices do provide a way for you to inquire the current setting and change it. But as I mentioned, these are always vendor-specific and in this instance such functionality is not supported.

There are a few different "terms of engagement," so to speak, that one can expect to see when working with serial ports. The simplest of these occurs when the sending party just blasts away commands at the target device without the target ever saying anything back. (Such is the case with most character LCDs.) In some cases, the target device will respond with a result code, or simply just an "OK" sent in plain text over the wire, which you must check for and handle on your own. In the case of large amounts of consecutive data, such as a full set of g-code for a CNC, some just expect you to send everything in quick succession, while others expect one line at a time with a delay, or even combined with one line and it saying OK back (e.g., GRBL). The world of possibilities having such connectivity is vast.

If you are wondering about the pretty colored icons, it is awesome on Linux

This screen was created using the xTuple application. All the code behind this screen is QtScript, Qt's embedded JavaScript. I am going to skip the boilerplate code in my discussion, but will include the full contents of both the screen and the script as attachments to this post.

The first thing we should do is define constants. Since we are working at the byte level, it is easier to read COMMAND instead of 0xFE — 0xFE being the byte you send to this LCD, telling it you are about to send it a command. While we're on the subject, with some commands we only have to send COMMAND, and then whatever the command is — for example, HOME (0x48), which moves the cursor to the home position, line 0 position 0. While other commands require only COMMAND, some commands, such as SETCONTRAST (0x50), require sending the initiator (COMMAND), the command itself (SETCONTRAST) and yet another byte containing the value. To further make our lives easier, we create two main functions sendCommand() and sendCommandWithParam() which do the grunt work of sending the commands over the wire. Further convenience is gained by creating separate functions to do each task, such as setColor(), which takes the values from our sliders and sets both the preview color on the screen and the color of the LCD background. 

var COMMAND = 0xFE;
var ON = 0x42;
var OFF = 0x46;

After constants we move on to initialization:

var seriallcd = {};
var sp;

seriallcd.init = function(parent, serialport, baudrate) {
  sp = new QSerialPort(parent);
  sp.setPortName(serialport);
  sp.setBaudRate(baudrate);
  if (!sp.open(QIODevice.ReadWrite)) {
    QMessageBox.critical(mywindow, "Error", "Could not open serial port, error code returned: "+ sp.error);
    return;
  }
  seriallcd.port = sp;
  seriallcd.clear();
}

To actually open the serial port, adjust the port for your computer (/dev/ttyACM0 happens to be the LCD on my Linux computer):

seriallcd.init(mywindow, "/dev/ttyACM0", 9600);

Our two command functions:

seriallcd.sendCommand = function(input) {
  var t = QByteArray();
  t.append(COMMAND);
  sp.write(t);
  t.clear();
  t.append(input);
  sp.write(t);
  sp.flush();
}

seriallcd.sendCommandWithParam = function(input, param) {
  var t = QByteArray();
  t.append(COMMAND);
  sp.write(t);
  t.clear();
  t.append(input);
  sp.write(t);
  t.clear();
  t.append(param);
  sp.write(t);
  t.clear();
  sp.flush();
}

An example wrapper function:

seriallcd.off = function() {
  seriallcd.sendCommand(OFF);
}

With parameter:

seriallcd.setLCDBrightness = function(brightness) {
  brightness = parseInt(brightness);
  if (brightness == "" || brightness < 1 || brightness > 254 ) {
    QMessageBox.critical(mywindow, "Invalid Data", "Brightness level can only be between 1 and 254");
  }
  seriallcd.sendCommandWithParam(SETBACKLIGHTBRIGHTNESS, brightness);
}

Sending text from the textbox and setting the color:

function sSend() {
  seriallcd.print(_input.text);
  _output.plainText += _input.text + "\n";
}

function setColor(newcolor) {
  // newcolor ignored
  seriallcd.setRGBColor(QByteArray([_red.value, _green.value, _blue.value]));
  _color.setStyleSheet("background-color: rgb(" + _red.value + ", " + _green.value + ", " + _blue.value + ");");
}

Have to make our connections:

_send.clicked.connect(sSend);
_close.clicked.connect(mywindow, "close()");
_red["valueChanged(int)"].connect(setColor); // send the new color to the screen when any of the sliders change
_green["valueChanged(int)"].connect(setColor);
_blue["valueChanged(int)"].connect(setColor);

See the two attached files for the full contents. 

When put together, this code will give us the ability to work with many different serial LCD screens. For instance, I was able to drive a Bematech POS pole by simply changing the byte constants for each supported command, while easily adding support for animated movement of the lines, which looks like:

seriallcd.scrollTopRight = function(message){
  var t = QByteArray();
  t.append(SCROLLCOMMAND);
  t.append(SCROLLRIGHTTOP);
  t.append(QByteArray(message));
  t.append(TERMINATOR);
  sp.write(t);
  sp.flush();
  t.clear();
}

In the case of the Bematech POS pole, the device expects not only a command initiator but also a terminator saying when input is finished.

This gives you some "scratching the surface" ideas of what you can do with the xTuple ERP scripting environment and QSerialPort. Coming up: we'll provide more more complex examples such as bi-directional communication with external machines.

up
103 users have voted.