CuPID Brew System : Industrial Monitoring with CuPID UPS and RS485 Mote

TL;DR

Here’s a few pics of the final product. We’ll get you some more close-ups of the CuPID, but you can see it elsewhere as well:

Networked control panel : The CuPID way.
Networked control panel : The CuPID way.

We’ll get you a little final UI here, but here’s a taste:

Our first revision UI for our control panels. Yummy stuff.
Our first revision UI for our control panels. Yummy stuff.

Introduction

We’re building our networked industrial panel solution. We’ve got all the pieces:

We’ve got our RS485 enabled mote reading data from our temperature controllers. See here (post for remote node coming):

http://www.cupidcontrols.com/2015/10/software-serial-modbus-master-over-rs485-transceiver/

We’re passing that into our UPS-enabled CuPID through our gateway RF node:

http://www.cupidcontrols.com/2015/10/cupid-ups-rf-gateway-uninterrupted-control-for-industrial-applications/

This logs the serial messages on controller status as they are received. It also takes queued messages and sends them to our gateway as available. We use this interface to send and debug:

http://www.cupidcontrols.com/2015/03/rf-mote-web-ui-program-all-the-rf-things/

We’ve also talked about our general web-based UI strategies:

http://www.cupidcontrols.com/2014/09/cupid-remote-read-database-logging-and-ui/

We have a few more elements to wrap up:

Command response

Command response is an important feature. When we send a message, we need confirmation that it was received and acknowledged. Ideally, we receive a status that indicates whether there was an error or not. This is especially important when we have multiple links in the command sequence. In the case below, for example, we queue a message that is sent over serial from the Pi in the CuPID to the Moteino. This command is received and sent to node 2, where it is then interpreted as a setpoint change request to Controller 1. Once the command is successfully processed, the remote node returns the data to the CuPID node, which sends it to the Pi and it is again databased. This is shown below:

Command chain for sending and receiving acknowledgement for a command to a remote controller.
Command chain for sending and receiving acknowledgement for a command to a remote controller.

So on our UI, we double-check our response comes through:

Testing out command and response on our mote interface. We sent our setpoint command out and receive a command acknowledgement response.
Testing out command and response on our mote interface. We sent our setpoint command out and receive a command acknowledgement response.

Linking up setpoint commands and remotes

So the tricky part here is sending commands to the controllers at the proper times. We have to take actions on the UI and immediately translate them into serial commands to send to the motes and then controllers, and then ensure the commands are acknowledged (later). The reason this is tough is that some channels are local, i.e. the CuPID is actually running a control algorithm and controlling outputs, and in some cases we have a mote channel that is controlled remotely. For the former case, the setpoint is a simple value change in the database. For the latter, we need to change the value, mark the setpoint change as a pending setpoint change, and insert the command into the serial queue for processing.  To accomplish this differentiation, we add a ‘type’ field for channels, as well as a ‘pending’ field in which we can put values that we are in the process of updating. The other nice part about this type field is that we can use it to determine which data to display. Some fields just don’t make sense for remote channels, e.g. control inputs on a remote channel.

So now each time a setpoint (or other parameter) is changed, we simply add the name of the field to the ‘pending’ channel entry. Then, when picontrol (the control algorithm script) iterates over a local channel, it will clear this field, and for remote channels, when we receive a message indicating the value has been updated, accepted or rejected, we will also clear this field. The question is: where do we write to the pending field? Do we do this from the UI, i.e. add an additional action to the widget? Or do we do it server-side, perhaps in the wsgi script, where if the value to set matches a list of values, it also sets the pending field? Or perhaps we can enforce it into the database with a value change trigger. All are possible. In the end, we put into into wsgi, for a few reasons. First of all, we like the way our js value updater works, and really didn’t want to mess with it. Second, we have logging capability in our wsgi that makes debug and keeping track of things easy. Lastly, it will make integration of pending setpoints for things like recipes quite easy, if we factor them properly. In other words, if we have a ‘setvalue’ function, and when ‘database’ = ‘systemdatabase’ and ‘table’=’channels’ and ‘value’=’setpoint’, we set ‘pending’=’setpoint’, this is a behavior that can be used universally.

So something like this works great as an add-on to the existing setvalue function:

def setsinglecontrolvalue(database, table, valuename, value, condition=None):
    if table == 'channels':
        if valuename in ['setpointvalue']:
            # get existing pending entry
            pendingvaluelist = []

            pendingentry = getsinglevalue(database, table, 'pending', condition)
            if pendingentry:
                try:
                    pendingvaluelist = pendingentry.split(',')
                except:
                    pendingvaluelist = []

            if valuename in pendingvaluelist:
                pass
            else:
                pendingvaluelist.append(valuename)

            pendinglistentry = ','.join(pendingvaluelist)

            setsinglevalue(database, table, 'pending', pendinglistentry, condition)

    # carry out original query no matter what
    response = setsinglevalue(database, table, valuename, value, condition=None)
    return response


def setsinglevalue(database, table, valuename, value, condition=None):
    query = makesinglevaluequery(table, valuename, value, condition)
    response = sqlitequery(database, query)
    return response

Now we need to get the message to the Gateway CuPID Mote so that it can actually send the set message to the controller. We want to do this as expediently as possible, so we don’t want to wait for picontrol to get to it. It may only be polling every 15s or so, and we want the system to be as responsive as possible. So we insert a conditional that will execute if this is a remote channel:

# Get the channel data
channeldata = readonedbrow(controldatabase, 'channels', condition=condition)[0]

if channeldata['type'] == 'remote' and channeldata['enabled']:
    # Process setpointvalue send for remote here to make it as fast as possible.
    # First we need to identify the node and channel by retrieving the interface

    channelname = channeldata['name']
    
    # Then go to the interfaces table to get the node and channel addresses
    address = getsinglevalue(controldatabase, 'interfaces', 'address', "name='" + channelname + "'")

    node = address.split(':')[0]
    channel = address.split(':')[1]

    # If it's local, we send the command to the controller directly
    if int(node) == 1:
        message = '~setsv;' + channel + ';' + str(value)

    # If not, first insert the sendmsg command to send it to the remote node
    else:
        message = '~sendmsg;' + node + ';~setsv;' + channel + ';' + str(value)

    # Then queue up the message for dispatch
    sqliteinsertsingle(motesdatabase, 'queuedmessages', [gettimestring(), message])

And that does the trick nicely. The best part about this: we have to change NOTHING about any of our UI code. All of our existing setvalue functions (the ‘setvalue’ action in our wsgiactions script) is now mapped, simply by changing ‘setvalue’ to ‘setcontrolvalue’ in the function call!

So let’s give this a shot. We’ll change a setpoint value on our UI and ensure that:

  • A serial message is queued
  • The ‘pending’ status is added to the remote channel.

So we slide away on our UI slider and see what happens.

Our message send test sequence.
Our message send test sequence.

Verifying commands : acknowledgement

The last step is closing the loop on the command, in case something goes awry along the way. We do have the mote set up to receive command acknowledgement from the controllers, and this message will be passed to the gateway. We can use this message as a command acknowledgement, and if we don’t receive it after some time, we will assume our setpoint command went into a black hole and try sending it again. So in the same way that we turned the setpoint command into a serial message, we will take the acknowledge message and deconstruct to remove the pending status from our channel. We return a message in json like:

~nodeid:1;chan:03;svcmd:100.0

For now, we’re not going to worry about the value on the command. Later, we can worry about this, but for now let’s just get back to the channel and zero out the pending status. So we jam out node and channel ids together to get an address of 1:3, and search our interfaces for a MOTE interface of type channel with an address 1:3. We return an entry with a name, which we can match with our channel. Piece of cake. In fact, we already do this in our updateio.py script to insert our remotes data into our channels. All we have to do now is add an entry that:

  • Removes ‘svcmd’ from the node data stored in ‘remotes’ in the ‘data’ field (a json field that stores whatever data we happen to accumulate about a node)
  • Update the channel with this modified data without the ‘svcmd’ keyword
  • Clear out the pending setpointvalue status

We won’t bore you with the code here. It’s all in the git repo in updateio. It works.

Linking up the panel UI

So we need to get all of the data out of our remote entries and into our channel entries. The way we do this is pretty simple and we talked about it elsewhere and referenced it above.: we create an interface. This can be done manually via phpliteadmin (what we did at the time of writing this), or via the web UI (a work in progress). The key fields in the entry are Interface, type and Address. the Interface value needs to be set to MOTE, type to channel, and address to nodeid:channel. Here, nodeid is the id of the RF node (1 for the gateway in the main panel), and channel is the id of the controller on the Modbus network. So in the main panel, we have 1:1, 1:2, and 1:3 for the Kettle, MLT, and HLT controllers, respectively. We also need to give the channels unique names.

Once we have the above entries in place, the updatio script will go grab the setpoint value, process value, and the remainder of the data from the most recent mote entry in the remotes table. It will take ‘sv’ and ‘pv’ and insert these values into the channel entry, and the entire data entry will be stored in the channel ‘data’ field, so any additional data can be accessed by parsing out the data that exists there in json format.

Now, we need to link everything up for display in our brew panel interface. As it turns out, this is already done, so we’re just left to do a little sprucing up to make our UI a bit more application-specific.

Working on it!

 

Two-way error-free RF messaging : avoiding concurrent retry failure

Introduction

So we have our RF nodes happily talking to one another, and most often we have one talking, one listening. This is very easy, and facilitated even in poor reception by our built in SendWithRetry function (we’re speaking of the RFM69 library here, of course). This is what a unidirectional communication structure often looks lke:

A typical RF (or otherwise) communication structure. One talks, one listens

The Problem : The Retry Loop

The problem arises when we want to have two nodes talk to each other that sometimes have other stuff to do. The following situation arises, which results in failure of at least one of the communication attempts. Essentially, they get stuck in a a retry loop:

The retry loop. The boon of two-way communication.

The Solution : The State Machine

The solution is rather simple: tell one node to stop retrying until it’s had a chance to listen. In pseudocode, this looks like this:

The state machine: savior of two-way retry communication.

In Real Life

In real life, this is pretty easily coded using a switch statement, with a delay afterwards to allow enough time for radio messages to show up and complete. 100ms is more than sufficient.

CuPID UPS RF Gateway : Uninterrupted control for industrial applications

Introduction

So we have been using our CuPID RF Controller all over the place. We use it at home, for automation and monitoring, at the shop for keeping an eye on things and connecting everybody, and most recently in industrial control panels. One issue that keeps coming up is reliability, and a few related sub-issues. As a result, we have decided to incorporate a battery and power management solution into our standard controller. This addresses these main issues:

Reliability

One thing guaranteed to eventually corrupt the operating system of a Raspberry Pi (and most OS, for that matter), is memory corruption. A great way to corrupt the memory on the OS is to remove power, have it fluctuate above or below the recommended voltage (nominally 4.75-5.2V on Raspberry Pi), or otherwise halt operations in an unknown state. Unfortunately, even AC power fluctations can cause problems. The Pi is notoriously picky about power supplies and their ability to maintain voltage under load. And, sometimes, you accidentally knock the power supply, something upstream fails … you get the picture. You can’t always choose when power is removed/changed, and you certainly don’t get to pick when it corrupts your OS. And when it does … you get to rebuild. Even if this just means writing an existing image, it’s still a pain, especially in a mission-critical embedded system.

Continuous operation during brief power outages

During commissioning of systems, testing power supplies, or for things as simple as moving your sensor gateway across the house, it is VERY convenient to be able to handle a brief power outage while the Pi still chugs away. And if you lose power for too long? it shuts itself down. For example, our control panels automatically cut all power to the system when the front door is closed. If we didn’t have some sort of UPS, we’d have to shutdown and reboot our CuPID every time we opened the door! Obviously this doesn’t work. What about if you blow a fuse or breaker? No problem.

Power Conditioning

This follows from the above, but line voltage is not always guaranteed. Our first warehouse space monitoring CuPID was continually going offline. The culprit? The neighbor was running high power equipment and pulling our line voltage below where our AC/DC converter could handle it on short intervals. Our battery and boost converter ensure that momentary drops below where our AC/DC converter is comfortable will not give our gateway problems.

External, Field-serviceable power control

One necessary feature of a deployed device is the ability to treat and diagnose. Worse comes to worse, sometimes you need to reset. A great feature of the UPS-enabled CuPID is the ability to reboot, shutdown, and hard reset. Ideally, this is not necessary, but things happen. The MightyBoost gives us a functional button, programmable actions, and an LED to give us status of our CuPID gateway.

Real-time power monitoring

The least important (but still cool) feature of the CuPID UPS gateway is the ability to monitor voltage in real time. You can see just how bad your power supply or building power is, or when things got bad.

The Hardware

The hardware here is quite simple: we take our standard CuPID HAT build and put a MightyBoost on top. Luckily the MightyBoost has the same footprint for the Moteino, so it’s a pretty easy fit. Here is a very abbreviated assembly procedure:

The board with EEPROM, DS2483 and a few passives installed:

CuPID Pi HAT Board
CuPID Pi HAT Board

With a Moteino on top:

Mounting the Moteino on the CuPDI HAT board + Moteino is a piece of cake.
Mounting the Moteino on the CuPDI HAT board + Moteino is a piece of cake.

Mightboost loaded up on the Moteino headers:

CuPID UPS RF with an RS485, loaded into an enclosure.
CuPID UPS RF with an RS485, loaded into an enclosure.

Here, we added in a shutdown jumper to GPIO20, power from the MightyBoost output, and a battery:

CuPID + Moteino + Mightyboost + battery
CuPID + Moteino + Mightyboost + battery

With a few more components, the stack fits nicely into an enclosure. Here, we’ve also added an RS485 board:

fab6

And finally, the CuPID lives in its native environment with On/Off switch, IO connected, and 5V supply in from a DIN mount AC/DC converter.

fab7

Software Serial Modbus Master over RS485 transceiver

The Problem

We like cheap, well-made temperature controllers. Sure, we can make our own, but for a modest sum we can get a nice NEMA-rated package capable of reasonably complex logic with a communications interface. The Love 4C and 16C controllers can both be had for <$100 fit this description and make dependable control and higher-level monitoring possible.

The downside is that the interface is modbus over RS485, which requires a driver chip such as the Maxim RS485 which will translate to serial for control over a UART or other serial interface. This is not a killer, as you can get a nicely laid out RS485 chip from Sparkfun for $10, or make your own.

The reason for this piece is that all RS485 code I could find was designed for use with the hardware serial UART on microcontrollers such as Arduino or clones such as the Moteino. Because I typically use the UART for communication to a Pi or to a debug console, and the 328P I typically use has only one, this was not an option. To make it work over a few digital pins, it is actually quite easy to use the SoftwareSerial library to make this happen.

The Hardware

Here’s the basic board from SparkFun, a bargain at $10:

SparkFun Adafruit board.
SparkFun Adafruit board.

 

Here’s our desktop prototyping setup:

Our desktop prototyping setup.
Our desktop prototyping setup.

 

We’ve mounted this a bunch of ways, but here are a couple of examples:

An RS485 board mounted on a CuPID RF module with MightyBoost. A pretty typical configuration.
An RS485 board mounted on a CuPID RF module with MightyBoost. A pretty typical configuration.
Another mounting configuration on a CuPID RF with RF and MightyBoost. This one is wired.
Another mounting configuration on a CuPID RF with RF and MightyBoost. This one is wired.

Talking Modbus

The modbus protocol is actually quite simple. A frame is created with a bit of metadata that describes the data and then either transmits or waits for it. Because we are using 2-wire modbus rather than 4-wire, communication is not duplex, and we need to instruct the RS485 driver to enter transmit mode. We do this by pulling a digital pin high. This pin is typically referred to as RTS (Ready To Send). When we are done, we set RTS low and the transceiver again waits to receive data.

The data frame contains where who we are, where we are sending/receiving the data to/from, how much there is, and a CRC data integrity check. For more information, SimplyModbus is a great source for bit-by-bit breakdown. Note that ASCII and RTU also differ in the CRC format: RTU uses a two-byte CRC (CRC16),  while ASCII uses a two-character LRC.

An RTU read modbus message:

byte 0 1 2 3 4 5 6 7
value node FC register high register low # registers high # registers low CRC high CRC low

A modbus RTU command modbus message:

byte 0 1 2 3 4 5 6 7
value node FC register high register low set value high set value low CRC high CRC low

There are two types of Modbus typically used: ASCII and RTU. RTU is the binary data that comprises a frame (typically 8 bytes for a request), and ASCII is the byte-by-byte equivalent of the message in ASCII characters. So each hex byte character is translated to an ASCII character, doubling the message size (and adding a byte for a ‘:’ delimiter). Clearly, RTU is much more efficient, but ASCII as usual offers human-readability.

Although the manual for the 4C and 16C list ASCII as the type, the units have an RTU option, accessible in the communications menu.

The Code

The code for the master is quite simple, and operates on a simple state machine. This example is written for a series of 5 controllers with sequential addresses, but the number and addresses of devices polled can be easily configured by the initialization arrays. Registers polled are hard-coded, but could easily be parameterized or customized.

The sequence below sends and receives three basic commands, and rotates through them for each device. It utilizes a helper function to calculate and append CRC on the fly, as well as our serial/radio transmit functions on message receive:

#include <SoftwareSerial.h>

SoftwareSerial mySerial(14, 15, 0); // RX, TX
byte RTSPIN = 16;
int xmitdelay = 0;
unsigned long rxstart;
unsigned long rxwait = 1000;
float sv;
float pv;
byte rtuaddress=0;

// message : node, FC, register start high byte, register start low byte, number of registers, CRC, CRC
byte pvsvmessage[] = {0x01, 0x03, 0x47, 0x00, 0x00, 0x02, 0x00, 0x00 };
byte outputmessage[] = {0x01, 0x03, 0x47, 0x14, 0x00, 0x02, 0x00, 0x00 };
byte modemessage[] = {0x01, 0x03, 0x47, 0x18, 0x00, 0x02, 0x00, 0x00 };

switch ( mbstate ) {
case 0: // This is the transmit stage
  message[0] = rtuaddresses[rtuindex];
  for (byte i=1; i<6; i++) {
    switch (mbmessagetype) {
    case 0:
      message[i] = pvsvmessage[i];
      break;
    case 1:
      message[i] = outputmessage[i];
      break;
    case 2:
      message[i] = modemessage[i];
      break; 
    }
  }
  addcrc(message,6);
  if (DEBUG) {
    Serial.print("sending to controller: ");
    Serial.println(rtuaddresses[rtuindex]);
    Serial.print(message[0],HEX);
    Serial.print(" ");
    Serial.print(message[1],HEX);
    Serial.print(" ");
    Serial.print(message[2],HEX);
    Serial.print(" ");
    Serial.print(message[3],HEX);
    Serial.print(" ");
    Serial.print(message[4],HEX);
    Serial.print(" ");
    Serial.print(message[5],HEX);
    Serial.print(" ");
    Serial.print(message[6],HEX);
    Serial.print(" ");
    Serial.println(message[7],HEX);
  }
 
  pinMode(RTSPIN, OUTPUT);
  digitalWrite(RTSPIN,HIGH);
  delay(xmitdelay);

  mySerial.write(message, sizeof(message));
  delay(xmitdelay);
  digitalWrite(RTSPIN,LOW);
  mbstate = 1;
  rxstart = millis();
  break; 
case 1: // wait for response
  if (mySerial.available() > 0)
  {
    Blink(LED,5);
    cmdlength = mySerial.readBytes(buff, 60);
    if (DEBUG) {
      Serial.print("Received message of length ");
      Serial.println(cmdlength);

      for (i=0; i<cmdlength;i++){
        Serial.print(buff[i], HEX);
        Serial.print(" ");
      }
      Serial.println();
    }
 
    if (buff[1]==3) {
      if (mbmessagetype == 0){
        pv = (float(buff[3] & 255) * 256 + float(buff[4] & 255))/10;
        sv = (float(buff[5] & 255) * 256 + float(buff[6] & 255))/10;
        if (DEBUG) {
          Serial.println("values");
 
          Serial.print("nodeid:");
          Serial.print(NODEID);
          Serial.print(",controller:"); 
          Serial.print(rtuindex);
          Serial.print(",pv:"); 
          Serial.print(pv);
          Serial.print(",sv:");
          Serial.println(sv);
        }
      }
      else if (mbmessagetype == 1) {
        pv = (float(buff[3] & 255) * 256 + float(buff[4] & 255));
        sv = (float(buff[5] & 255) * 256 + float(buff[6] & 255));
        if (DEBUG) {
          Serial.print("Proportional offset: ");
          Serial.println(pv);
          Serial.print("Regulation value");
          Serial.println(sv);
        }
      }
      else if (mbmessagetype == 2) {
        pv = (float(buff[3] & 255) * 256 + float(buff[4] & 255));
        sv = (float(buff[5] & 255) * 256 + float(buff[6] & 255));
        if (DEBUG) {
          Serial.print("Heating/cooling");
          Serial.println(pv);
          Serial.print("Run/Stop");
          Serial.println(sv);
        }
      }
      Blink(SENDLED,5);
      sendControllerMessage(rtuaddresses[rtuindex], pv, sv, mbmessagetype);
    }
    else {
      if (DEBUG) {
        Serial.println("bad response");
      }
    } 
    if (millis() - rxstart > rxwait) {
      if (mbmessagetype >= 2) {
        mbmessagetype = 0;
        if (rtuindex >= sizeof(rtuaddresses)-1) {
          rtuindex = 0;
        }
        else {
          rtuindex ++;
        }
      }
      else {
        mbmessagetype ++;
      }
      mbstate=0;
    }
    break;
  } // switch
}

We use a controller message function and CRC as here:

void sendControllerMessage(byte controller, float pv, float sv, byte messagetype) {
 
  // Initialize send string

  int sendlength = 61; // default
  int wholepv = pv;
  int fractpv = (pv - wholepv) * 1000;
  int wholesv = sv;
  int fractsv = (sv - wholesv) * 1000;
 
  if (messagetype == 0) {
    if (NODEID == 1) {
      sendlength = 39; 
      sprintf(buff, "nodeid:1,chan:%02d,sv:%03d.%03d,pv:%03d.%03d", controller, wholesv, fractsv, wholepv, fractpv);
      Serial.println(buff);
    }
    else {
      sendlength = 30; 
      sprintf(buff, "chan:%02d,sv:%03d.%03d,pv:%03d.%03d", controller, wholesv, fractsv, wholepv, fractpv);
      sendWithSerialNotify(GATEWAYID, buff, sendlength, 1); 
    }
 }
 else if (messagetype == 1) {
   if (NODEID == 1) {
     sendlength = 37; 
     sprintf(buff, "nodeid:1,chan:%02d,prop:%03d,treg:%03d.%01d", controller,wholepv, wholesv, fractpv);
     Serial.println(buff);
   }
   else {
     sendlength = 28; 
     sprintf(buff, "chan:%02d,prop:%03d,treg:%03d.%01d", controller,wholepv, wholesv, fractsv);
 sendWithSerialNotify(GATEWAYID, buff, sendlength, 1); 
     }
   }
   else if (messagetype == 2) {
     if (NODEID == 1) {
     sendlength = 31; 
     sprintf(buff, "nodeid:1,chan:%02d,htcool:%01d,run:%01d", controller,wholepv,wholesv);
     Serial.println(buff);
   }
   else {
     sendlength = 22; 
     sprintf(buff, "chan:%02d,htcool:%01d,run:%01d", controller,wholepv, wholesv);
     sendWithSerialNotify(GATEWAYID, buff, sendlength, 1); 
   }
 }
}
void addcrc(byte* message, int len) {
  mycrc = ModRTU_CRC(message, len);

  long byte1 = mycrc & 255;
  long byte2 = (mycrc & long(255*256))>>8;
  if (DEBUG) {
    Serial.print(byte1,HEX);
    Serial.print(",");
    Serial.println(byte2,HEX);
  }
  message[len] = byte1;
  message[len + 1] = byte2;
}

In debug mode, we get something like what’s shown below:

sending to controller: 1
1 3 47 0 0 2 D0 BF
Received message of length 9
1 3 4 2 FFFFFFCE 3 FFFFFFE8 FFFFFF9A FFFFFFCA 
values
nodeid:2,controller:0,pv:71.80,sv:100.00

SENDING TO 1
chan:01,sv:100.000,pv:071.800
SEND COMPLETE

sending to controller: 1
1 3 47 14 0 2 90 BB
Received message of length 9
1 3 4 0 0 0 0 FFFFFFFA 33 
Proportional offset: 0.00
Regulation value0.00

SENDING TO 1
chan:01,prop:000,treg:000.0
SEND COMPLETE

sending to controller: 1
1 3 47 18 0 2 50 B8
Received message of length 9
1 3 4 0 0 0 1 3B FFFFFFF3 
Heating/cooling0.00
Run/Stop1.00 

SENDING TO 1
chan:01,htcool:0,run:1
SEND COMPLETE

When we’re not in debug, only the messages between the “SENDING” and “SEND COMPLETE” are sent, which our CuPID gateway picks up and logs.

Writing Values

In MODBUS, writing values is as simple as changing the function code. The message format is as above. We need function code 6, and to format our values. We borrow some command processing code, so we can issue setpoint value commands over serial.

The other piece is value conversion, which we glossed over earlier. We have to convert back and forth between our float values. The values in this particular case are stored in tenths of. i.e. a setpoint or process value of 123.4 will be stored as 1234. In hex, this is 04 D2.

So we use the following to go from bytes to value:

pv = (float(buff[3] & 255) * 256 + float(buff[4] & 255))/10;

And this to go from value to bytes:

int highbyte = (sv * 10) / 256;
int lowbyte = int(sv * 10) & 255;

And what we end up with is a routine to send our messages nicely:

void sendsvmessage(int node, float sv) {
  message[0] = node;
  for (byte i=1; i<6; i++) {
    message[i] = setmessage[i];
  }
  Serial.print("received setpoint:");
  Serial.println(sv);
  int highbyte = (sv * 10) / 256;
  int lowbyte = int(sv * 10) & 255;

  message[4] = highbyte;
  message[5] = lowbyte;
  
  addcrc(message,6);
 
  pinMode(RTSPIN, OUTPUT);
  digitalWrite(RTSPIN,HIGH);
  delay(xmitdelay);

  mySerial.write(message, sizeof(message));

  delay(xmitdelay);
  digitalWrite(RTSPIN,LOW);
 
  rxstart = millis();
  mbstate = 1;
}

Finally, it would be nice to confirm that the command was received, and return a command to the serial or radio interface so that the gateway or user can confirm that it was accepted (without looking at the device. So we write in a handler for the message reader:

else if (buff[1]==6) {
 
  sv = (float(buff[4] & 255) * 256 + float(buff[5] & 255))/10;
  if (DEBUG) {
    Serial.print("Command acknowledged for node:");
    Serial.println(buff[0]);
    Serial.print("Setpoint: ");
    Serial.println(sv);
  }
  sendCmdResponseMessage(buff[0], sv);
}
void sendCmdResponseMessage(byte controller, float sv) {
  // Initialize send string

  int sendlength = 61; // default
  int wholesv = sv;
  long fractsv = ((long)(sv*1000))%1000;
 
  if (NODEID == 1) {
    sendlength = 31; 
    sprintf(buff, "nodeid:1,chan:%02d,svcmd:%03d.%03d", controller, wholesv, fractsv);
 Serial.println(buff);
  }
  else {
    sendlength = 23; 
    sprintf(buff, "chan:%02d,svcmd:%03d.%03d", controller, wholesv, fractsv);
    sendWithSerialNotify(GATEWAYID, buff, sendlength, 1); 
  }
}

And we get what we expect on the way out:

SENDING SV CMD
sending to controller: 1
1 6 47 1 1 C9 D 78
Received message of length 8
1 6 47 1 1 FFFFFFC9 D 78 
Command acknowledged for node:
Setpoint: 45.70

SENDING TO 1
chan:01,svcmd:045.700
SEND COMPLETE

This code can be located on the git repo here:

https://github.com/iinnovations/iicontrollibs/tree/master/mote/brew/brewmotewrite

Hamachi Status and IPs without desktop app

Introduction

So you are using logmein Hamachi, the awesome, easily configured VPN solution that runs networks in gateway and mesh configurations, and lets you see anything from anywhere. You have the desktop app, where you can see your devices easily in a window. You also have it set up on your phone, which allows you to log in to your Hamachi network so you can access your devices.

Problem: You can’t find the Hamachi IPs (or device status) easily when you’re on mobile.

When you’re running the desktop app, to get the Hamachi IP (the special IP that lets you access the device as if it were on your local network), you can see it, and just right click and ‘copy address’. This is not the case, however, when you’re on mobile. So .. you can either:

  1. Memorize that silly IP address and hope that it’s online
  2. Use your desktop app to get the IP (defeating the purpose)
  3. Set a bookmark (not an awful solution)
  4. Find a more elegant solution.

We (obviously) went with 4.

Step 1 : Get and parse the Hamachi data

So this is nothing new (although we did improve it to parse out networks recently). Inside our netfun.py lib, we have a set of functions that will parse the text output from the ‘hamachi list’ command. So hamachi list will output something like this:

 * [187-XXX-002]HomeWork capacity: 2/32, [10.40.1.120/24] subscription type: Standard, owner: xxx@gmail.com
 * 175-XXX-053 innovate 10.40.1.101 alias: not set direct UDP 192.168.1.101:43381
 * [283-XXX-722]CuPID Network capacity: 11/32, subscription type: Standard, owner: xxx@gmail.com
 * 136-XXX-383 XXXX2 25.XXX.78.58 alias: not set 2620:9b::xxxx:4e3a via relay TCP
 ! 176-XXX-808 linaro 25.0.0.0 alias: not set This address is a lso used by another peer
 179-XXX-373 cupid-xxxx 25.0.0.0 alias: not set
 179-XXX-060 Colin Phone 25.0.0.0 alias: not set
 179-XXX-134 cupid-stevevorres 25.0.0.0 alias: not set
 * 179-XXX-052 MYBOT 25.XXX.112.71 alias: not set 2620:xx::xxxx:7047 direct UDP 192.168.1.140:49240
 181-XXX-742 colin-home 25.0.0.0 alias: not set
 182-XXX-757 iispace 25.0.0.0 alias: not set

We take this raw data and spit it out into two lists: one containing network dictionaries with their metadata, and another containing a list of client dictionaries with each client’s metadata (online status, etc.). Easy peasy. The function is called gethamachidata(), and is in the netfun lib. It will output something nice:

>>> import netfun
>>> networks,clients=netfun.gethamachidata()
>>> networks
[{'networkid': '187-XXX-002', 'name': 'HomeWork'}, {'networkid': '283-XXX-722', 'name': 'CuPID Network'}]
>>> clients[0]
[{'name': 'innovate', 'alias': 'not set', 'onlinestatus': 'online', 'conntype': 'direct', 'clientid': '175-106-05', 'hamachiip': '10.40.1.101', 'connipport': '192.168.1.101:43381', 'connprotocol': 'UDP'}]
>>> clients[1]
[{'name': 'XXXX22', 'alias': 'not set', 'onlinestatus': 'online', 'conntype': 'via relay', 'clientid': '136-XXX-38', 'hamachiip': '25.XXX.78.58', 'connipport': '', 'connprotocol': 'TCP'}, {'name': 'linaro', 'alias': 'not set', 'onlinestatus': 'offline', 'conntype': 'This addre', 'clientid': '176-XXX-80', 'hamachiip': '25.0.0.0', 'connipport': 'lso used by another p', 'connprotocol': 'is'}, {'name': 'cupid-xxxx', 'alias': 'not set', 'onlinestatus': 'offline', 'conntype': '', 'clientid': '179-XXX-37', 'hamachiip': '25.0.0.0', 'connipport': '', 'connprotocol': ''}, {'name': 'Colin Phone', 'alias': 'not set', 'onlinestatus': 'offline', 'conntype': '', 'clientid': '179-XXX-06', 'hamachiip': '25.0.0.0', 'connipport': '', 'connprotocol': ''}, {'name': 'cupid-stevexxx', 'alias': 'not set', 'onlinestatus': 'offline', 'conntype': '', 'clientid': '179-XXX-13', 'hamachiip': '25.0.0.0', 'connipport': '', 'connprotocol': ''}, {'name': 'MYBOT', 'alias': 'not set', 'onlinestatus': 'offline', 'conntype': '', 'clientid': '179-XXX-05', 'hamachiip': '25.XX.112.71', 'connipport': '', 'connprotocol': ''}, {'name': 'colin-home', 'alias': 'not set', 'onlinestatus': 'offline', 'conntype': '', 'clientid': '181-7XX-74', 'hamachiip': '25.0.0.0', 'connipport': '', 'connprotocol': ''}, {'name': 'iispace', 'alias': 'not set', 'onlinestatus': 'offline', 'conntype': '', 'clientid': '182-XXX-75', 'hamachiip': '25.0.0.0', 'connipport': '', 'connprotocol': ''}, {'name': 'mvbrew', 'alias': 'not set', 'onlinestatus': 'online', 'conntype': 'direct', 'clientid': '184-301-57', 'hamachiip': '25.X.157.62', 'connipport': '73.XX.39.254:35941', 'connprotocol': 'UDP'}, {'name': 'xxxblurg1', 'alias': 'not set', 'onlinestatus': 'offline', 'conntype': '', 'clientid': 'XXX-893-05', 'hamachiip': '25.XXX.45.63', 'connipport': '', 'connprotocol': ''}, {'name': 'Colin Phone', 'alias': 'not set', 'onlinestatus': 'offline', 'conntype': '', 'clientid': '187-XXX-83', 'hamachiip': '25.XXX.25.248', 'connipport': '', 'connprotocol': ''}]

… and so you have a list of networks, and then the clients attached to each of the networks.

Step 2 : Squirt data into webpage

The next part is also pretty easy. We write a little function that runs the above and formats it into a pretty little page. It is in the script hamachidaemon.py, available in the github here. We’d paste it here, but formatting the HTML is a bit of a pain, plus the code will get stale anyway. We do some pretty basic stuff, using jQuerymobile, which we really like, and we get pretty. We add some links too. Typing is for the birds.

Our Hamachi interface. Pretty, with links and networks.

Step 3 : Cron it

As you may guess from the name of the aforementioned script, we run it every so often. We also use this script to double-check that Hamachi is online. So set it up in your cron, and you’ll not only keep yourself online, but also keep your page updated, with links to your hamachi devices! Piece of cake.

 

RF Mote Web UI : Program all the RF things

Introduction

Deploying a bunch of remote nodes and a gateway into the wild presents a number of issues. What does each do? What is connected to it? How often should it tell me what’s up?

Typically wrapping up a group of remote sensors requires a unique program for each node, programming and debugging each node locally with a serial interface, and programming a gateway/data aggregator to understand each individually. This can be a bit of a nightmare, and we really wanted a solution where we would never have to get out our FTDI cable to program a micro directly!

We’ve addressed all of these issues with our UniMote code and our gateway UI for messaging remote nodes. Read on.

UniMote for universal code and remote config

One thing we all loathe is repeating ourselves. Another is constant maintenance of multiple instances of code, even if they are quite similar (and often even more so if they are). For this reason, we’ve built a UniMote sketch that does, well, everything. It works on a battery-powered mote as well as it does on a sensor gateway. You can configure it over serial or RF with simple commands. You can read about that here and here.

Gateway SerialHandler to dispatch and process

On a gateway, we have a mote connected by serial, often to a Raspberry Pi, for example on our Pi HAT.  On our gateway, we need to both listen for incoming messages from our mote, as well as send messages when necessary.

To facilitate this, we set up a pretty simple serial message handler. It continuously listens, and dispatches messages as they show up in a queue set up in a database, This database houses received messages, queued messages, as well as those that have been sent, time-stamped of course. It’s in python, uses pyserial, and is pretty darned straightforward. In the form you’ll find it, it does use our pilib, but you can easily cut and paste if you want a standalone version.

 serialhandler.py

UI to queue serial commands

Our serial handler does nothing except process commands put into a queue, so all we need to do with a UI is to present a way of inserting messages into it. This is pretty easy, and based on things we have talked about previously. We create a UI that executes a simply wsgi function to insert entries in the serial queue. We add some css, and we get the UI below:

Terminal UI for entering sending messages to our motes.
Terminal UI for entering sending messages to our motes.

We can pretty easily see the messages as they go out and are received. Simple stuff.

Mote Control Panel

Now, on top of this basic goodness, we’re going to build something that resembles a control panel, where we can view each node’s vitals, update data, and view what the status of all the IO are. We do it using the same basic methods above, combined with the databased data retrieval we’ve talked about here and here. The end result is a browser-determined data retrieval sequencing that complements the node-side code that is set up to deliver data when the node feels like it. This will be the bedrock that our remote thermostat/PLC code rests firmly on. Here are a couple videos with an explanation and demo of the control panel interface:

Overview:

Demo:

UniMote v2 : Read all the RF things

Introduction

Code discussed below can be found here: newest mote sketch

In our last UniMote installment, we outlined our UniMote program. A one-size fits all sketch for our avr-based, RF-enabled microcontrollers, our sketch has the following main features:

  • Numbered, sequential access to all available IO on R4 and Moteino Mega microcontrollers
  • Remote, programmable IO modes (DI, DO, AIN, PWM, 1Wire)
  • 8 channels with configurable positive/negative feedback and deadband
  • Configurable IO and channel status reporting
  • Remote command for RF send
  • Daisy-chainable commands, e.g. send command to mote to send command to mote to send command, etc.
  • Support for low power sleep modes
  • Configurable program control
  • Remote (RF) and local (serial) modification of all control parameters

What we wanted to add, however, were a few more features.

  • RF parameter readout: While we already had local parameter readout (with the listparams command), we didn’t have a readout feature for communication over RF. For devices that don’t sleep, it’s very useful to poll parameters when necessary. So with the previous feature set, we have configurable bidirectional communication. We can confirm commands, read out all setpoints, process values, io/channel statuses, and program configuration. This is great for a status UI.
  • Command response with status: Like an ACK with useful information, all commands to the remote will now receive a response both acknowledging the command as well as a status response. This will alert us to errors with our command, should we try to set a parameter incorrectly.
Code cleanup:

In addition to the above, there was a lot of code cleanup and optimization to be done. We removed all string objects and replaced them with character arrays to save space and avoid seg faults. We also employed a great library that stores strings (where necessary) in flash memory, to avoid the RAM usage that results in unpredictable behavior when collisions occur. It saves a ton of memory, especially compared to sprintf. Best of all, we fit all of the above into the memory space of at ATMega328P. With no further ado, let’s introduce the features.

Gateway/Mote Universality

Another thing we didn’t like was that we had to have unique gateway and mote sketches. We’ve done away with that. A gateway and a mote are the same. Just change the NODEID and sleep parameters, if you wish.

RF Parameter Readout

In the previous version of UniMote, we had a listparams command that would list all the Mote parameters. It looked something like this:

~listparams
Command character received
NODEID:0,
GATEWAYID:0,
NETWORKID:0,
LOOPPERIOD:2000,
SLEEPMODE:0,
iomode:[0,0,0,0,0,3,0,3,0,0,0,0,0],
ioenabled:[0,0,0,0,0,0,0,0,0,0,0,0,0],
ioreadfreq:[10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000],
ioreportenabled:[0,0,0,0,0,0,0,0,0,0,0,0,0],
ioreportfreq:[0,0,0,0,0,0,0,0,0,0,0,0,0],
chanenabled:[0,0,0,0,0,0,0,0],
chanmode:[0,0,0,0,0,0,0,0],
chanposfdbk:[0,0,0,0,0,0,0,0],
channegfdbk:[-1,0,0,0,0,0,0,0],
chandeadband:[0,0,0,0,0,0,0,0],
chanpvindex:[5,0,0,0,0,0,0,0],
chansv:[15.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00]

This worked well, but this didn’t work for reporting over RF, as each message is only 61 characters long. OOPS. So we broke the reporting into a number of “modes” that determined what would be reported in each chunk. This makes it more efficient anyway: we don’t always need all of the data. In fact, we rarely do. We made all of these functions work for both local and radio readout. So a listparams command has the following format:

~listparams;iovals;0

This would list the first chunk of io values to the Serial display:

cmd:lp,iov:0000.0000,0000.0000,0000.0000,0000.0000,0000.0000

If we change the third parameter to something non-zero, the data will also be sent to that node number over RF. Simple as that. Each chunk is formatted to be 61 characters or less, and is listed in the table below. It’s all json-encoded, too, which makes it easy to extract with our gateway serial handler.

Listparams commands:
Command Description Example Output
cfg mote configuration cmd:lp,node:10,gw:1,nw:10,loop:1000,slpmd:0,slpdly:1000
iomd io modes cmd:lp,iomd:0,0,0,0,0,0,0,0,0,0,0,0,0
ioen io enabled cmd:lp,ioen:0,0,0,0,0,0,0,0,0,0,0,0,0
iov io values 0-4 cmd:lp,iov:0000.0000,0000.0000,0000.0000,0000.0000,0000.0000
iov2 io values 5-8 cmd:lp,iov2:0000.0000,0000.0000,0000.0000,0000.0000,0000.0000
iov3 io values 9-13 cmd:lp,iov3:0000.0000,0000.0000,0000.0000,0000.0000,0000.0000
iordf io read frequencies (100ms increments) cmd:lp,iordf:10,10,10,10,10,10,10,10,10,10,10,10,10
iorpf io report frequencies (100ms increments) cmd:lp,iorpf:10,10,10,10,10,10,10,10,10,10,10,10,10
chen channel enabled statuses cmd:lp,chen:0,0,0,0,0,0,0,0
chmd channel mode (reserved) cmd:lp,chmd:0,0,0,0,0,0,0,0
chpf channel positive feedback indices cmd:lp,chpf:00,00,00,00,00,00,00,00
chnf channel negative feedback indices cmd:lp,chnf:00,00,00,00,00,00,00,00
chdb channel deadband values cmd:lp,chdb:00,00,00,00,00,00,00,00
chpv channel process values 0-3 cmd:lp,chpv:0000.0000,0000.0000,0000.0000,0000.0000
chpv2 channel process values 4-7 cmd:lp,chpv2:0000.0000,0000.0000,0000.0000,0000.0000
chsv channel setpoint values 0-3 cmd:lp,chsv:0015.0000,0000.0000,0000.0000,0000.0000
chsv2 channel setpoint values 4-7 cmd:lp,chsv2:0015.0000,0000.0000,0000.0000,0000.0000

Command response with status

Since we’ve built the UniMote to receive commands from a remote, it’s very useful to receive a reply with two key pieces of information:

  • An echo of the command sent to confirm that it was received as sent
  • A status to indicate success, failure, and an error description of the sent command

So, when a command is sent, we get a short reply indicating the command, and the status. So, for example, if we want to change the loopperiod of the mote, we would see the following:

Issued command:
~sendmsg;10;;~lp;cfg;1

This command to the gateway (NODEID=1), sends a message to node 10 with the content “~lp;cnf;1”. When the node receives the command, it will send listparams configuration to node 1 (back to the gateway). On the remote node 10, we see on serial output:

BEGIN RECEIVED
nodeid:1, ~lp;cfg;1, RX_RSSI:-26
END RECEIVED
SENDING TO 1
cmd:lp,node:10,gw:1,nw:10,loop:1000,slpmd:0,slpdly:1000
SEND COMPLETE
Response from the mote:

And, sure enough, back on the gateway, we get the response:

BEGIN RECEIVED
nodeid:10, cmd:lp,node:10,gw:1,nw:10,loop:1000,slpmd:0,slpdly:1000,RX_RSSI:-23
END RECEIVED

Now let’s try a set command, where we expect a status response

Issued command:
~sendmsg;10;;~modparam;loopperiod;10

This command to the gateway (NODEID=1), sends a message to node 10 requesting that the loopperiod be changed to 1s (100ms increments). Sure enough, we receive the command, it is executed, and a reply message is sent that confirms the action:

BEGIN RECEIVED
nodeid:1,~modparam;loopperiod;10,RX_RSSI:-23
END RECEIVED
cmd:mp,param:loopperiod,setvalue:10,status:0
Sending message to replynode: 1

And back on the sending node, we receive the reply:

BEGIN RECEIVED
nodeid:10,cmd:mp,param:loopperiod,setvalue:10,status:0,RX_RSSI:-34
END RECEIVED

Our node has received confirmation! Logic ensues.

Now, let’s test a parameter set command that would receive an error. Let’s try to set the iomode to 9:

~sendmsg;10;;~modparam;iomode;0;9

The response from the node is:

cmd:mp,param:iomode,ionum:0,iomode:9status:1,error:moderange
Sending message to replynode: 1

Our sending note receives the status of 1, with an error of moderange. Enough said!

Cleanup Notes

Important things we did to optimize memory and to clean things up:

  • Elimination of all String objects: All strings objects have been converted to character arrays (people often refer to these as strings). This has helped RAM tremendously, avoided segfault issues with overruns that result in restart of the micro.
  • Use of FLASH library to take strings out of RAM: RAM is a precious resource (we report it locally after each command), and overrun of available RAM is a really easy way to get very unpredictable behavior, such as variable gibberish and sporadic reboots. Use of the native FLASH library string write and copy functions saves immensely on the tiny bit (2k) of ram available on an ATMega328P
  • Reuse of code for local (serial) output and for Radio commands: All code is built around the same output for local and remote programming. This not only saves loads of code space, but also makes the output the same for both methods.

Reference

The above makes use of the open source libraries available on github:

Explanation and installation here:

Running Hamachi on Snappy Ubuntu Linaro

This is recorded here in case the source link (written for a Beaglebone) goes down. Hamachi is rad. That’s covered elsewhere.

Unfortunately, the armhf source from LogMeIn Hamachi does not work on the new shiny ARMv7 Raspberry Pi 2. Woe are us. Realizing that the Beaglebone has the same problem, namely that it’s running an Ubuntu distro compiled for ARMv7, I figured that someone would have solved this problem. And they have. They blogged about it at the link below. I’ve reproduced the applicable content here for posterity.

http://gencarelle.com/blog/2013/05/31/running-hamachi-on-a-beaglebone-black/

1. Update

apt-get update

2. Install packages (and package requirements).

apt-get install –fix-missing –no-install-recommends lsb lsb-core aptitude libc6-armel libc6-armel-cross linux-libc-dev-armel-cross

3. Make a directory to hold the downloaded packages.

 mkdir /root/packages

4. Change to the new package directory.

cd /root/packages

5. Download the armel libs.

aptitude download libstdc++6-armel-cross libgcc-4.7-dev-armel-cross  libgcc1-armel-cross libgomp1-armel-cross libc6-dev-armel-cross

6. There is no gcc-4.7-arm-linux-gnueabi-base package for this version of Ubuntu. Force Install the armel libs we have.

dpkg -i –force-all *.deb

7. Tell the system where to find the libs hamachi needs. Edit the /etc/ld.so.conf and add this path at the bottom.

/usr/arm-linux-gnueabi/lib

8. Update the library cache.

 ldconfig

9. Download the hamachi package.

wget https://secure.logmein.com/labs/logmein-hamachi_2.1.0.136-1_armel.deb

10. Install

dpkg –force-architecture -i logmein-hamachi_2.1.0.86-1_armel.deb

11. Enjoy!

C

HAT EEPROM Read/write, configuration for RPi B+, RPi 2

This article is more of a mental note than anything, and perhaps something others will find useful. I spent a couple hours with my strongest google-fu, some hardware, and better things to do than getting a memory chip programmed.

Introduction

The new Raspberry Pi B+ and it’s younger brother, the Raspberry Pi 2, have a breakout of the second i2c interface available on the Broadcom 2835 and 2836 chips. The interface is available on pins 28 and 29. While technically you could access it on previous versions, there weren’t jumper pins, and it was, well, harder. Still, even here the installation seemed unnecessarily complicated, if for no other reason than the information was spread across eighteen forum posts with a nearly broken forum board search feature.

Device Trees

The new RPi kernel uses what are referred to as Device Trees that load modules selectively at boot. This is in contrast to the previous versions, which used a blacklist to selectively disable devices. We won’t get into the pros and cons of this approach versus the other, but needless to say, when folks in userland upgraded their kernels, all of these things — SPI, I2C, etc, — stopped working. Big f**king oops for usability, guys.

Enable the I2C buses

Go to the file /boot/config.txt. At the end, find the lines (or add them yourself, that say:

dtparam=i2c_arm=on
dtparam=i2c_vc=on
dtparam=spi=on

Make sure they are uncommented. In order, these will enable i2c-1, i2c-0, and SPI. You have to be careful with i2c-0, as it can interfere with your GPU, specifically your camera. You have been warned.

Now, after the above, give it a good reboot. Now, when you give it an ls /dev,  you should get entries that look like:

i2c-0
i2c-1
spidev0.0
spidev0.1

You are now most of the way there! Now, one note before we move on: If you have used i2c devices before, you know that the convenient utility i2cdetect will typically list out all devices on the bus for you very nicely. Note that this is NOT the case for memory devices on i2c-0. If you take the device on i2c-1 that happily reads address 50 and put it on i2c-0, it will not respond. The script below, however, will tell you whether or not you have an EEPROM on your bus.

Reading/writing your EEPROM

Ok, well if your device doesn’t announce itself like other i2c devices, how do we read and write it? Good question. First, download the EEPROM utilities from the git repo: https://github.com/raspberrypi/hats/tree/master/eepromutils

Next — remember that in order to write to your EEPROM, you will need to make sure that the write-protect pin is either floating (defaults to pulled-down), or has been jumpered to ground. See circuit here: https://github.com/raspberrypi/hats/blob/master/eeprom-circuit.png

Finally, build yourself some binary data images by editing settings text file and using eepmake.c, and drop it onto your EEPROM using eepflash.sh, which you can also use to check your work. The docs on this stuff are good, so I won’t waste time repeating that info here.

Happy HATting !

C

P.S. If and when we figure out how you’re supposed to access the stuff in userland, we’ll let you know!!

 

Push Button RF Data Transmitter

Background

So a friend of mine who works in, let’s say, the sciences, approached me about a simple application, but one that a hardware company was charging an inordinate amount of money for. In the physical sciences equipment business, this is very common: overcharging for a very simple feature, because it’s convenient and it just works. The fact of the matter is that scientists simply don’t have the time to homebrew solutions, they don’t get accolades for making simple instruments work well and cheaply, and taking the time to do so takes them away from their real jobs — doing science. This is where consultants and outsourcing really make sense, even when at first it might seem a bit expensive. Regardless, this doesn’t mean that buying a receipt printer attached to a button to print out data from a device should cost $1000, or a dead-simple temperature controller in a box with a couple relays should cost $2k. but this is the twisted norm.

We are moving away from a time where a tiny handful of people were able to design things that once seemed very complicated, like a microcontroller IO device, to an era where these things are available for dirt cheap and are everywhere. The markets are therefore just waiting to be taken over or at least challenged and/or adjusted by the talents and creativity of the makers around us.

The Project

The device I was approached about was a laboratory instrument that has an RS232 interface. You can send it a couple characters and it sends back the data. What we envisioned was a single button: you press the button, it acquires data and sends it off to a sensor gateway, where the data is databased and stored for later processing, addition of metadata, etc. It’s a pretty general thing. To summarize:

basicmodel

Now the idea of a single button is pretty romantic, in a way. It is the absolute reduction of input to the simplest unit of data transfer: the binary digit. The ultimate in simplicity. As it turns out, it’s not a unique one, either. The Staples big red button has been around forever, and if you do some googling you can find a couple companies that make, essentially, big red networked buttons that do one thing. There are legion applications where you just need one thing to happen. Call an elevator, a taxi, turn on a light, disable an appliance … so many user interfaces can be created with one or three binary elements. Anyway, the imagination runs wild. But let’s get onto the simple nuts and bolts of how to make our little button here.

The Hardware

We’ll construct this unit exactly like our Enhanced Remote, which we wrote about previously. We’ll use the same enclosure, and just attach a button to the top, and run two cables out of one side. This will negate the IP rating of the cable entry, but make it so all of our cables come out of one side. This is a key feature for presentation and accessibility. As a result, however, we need to size up our cable entry to PG9, which really doesn’t fit on the side of the enclosure, despite the appearance. Next time we do this, we’ll get a single cable with enough conductors so we can use a smaller PG7 entry as on the standard remote.

Components for our RF button remote.
Components for our RF button remote.

What we SHOULD have done here was document the button insertion process and what the bare button looks like. It’s one of these guys at right.

Our button's product picture before we seal it up on the enclosure lid.
Our button’s product picture before we seal it up on the enclosure lid.

Strangely, the top bezel on this thing is really narrow, so you need to get the hole size just right. Let’s just say our set of bits didn’t prove up to getting it close enough. So we ended up using a grommet in the hole opening, which had the added benefit of keeping the top sealed. It also gives the button a little spring, and a totally secure feeling. To get it in there just right, we had to cut the grommet in half and use one half on each side. Not a very interesting story.

Wiring

Aside from the step putting this thing together we covered previously, we need to do our wiring to the outside and connect our button. From the outside, everything will be on one USB cable: 5V power, GND, and two data lines. The two data lines will be passed out on the second USB, and our data source can use them as they see fit. For an RS232 source, for example, we’ll use them as RX/TX, after level conversion if necessary. This will make using a Y USB cable possible in the future. Here’s a simple layout:

Simple wiring diagram of our button and RF sender unit. All the external connections go on a single USB bus.
Simple wiring diagram of our button and RF sender unit. All the external connections go on a single USB bus.

The button is pretty straightforward – two pins for the contact and two for the LED. One button contact will go to ground, with the other connected to the IO1, which we’ll configure as INPUT_PULLUP on the Arduino. Not coincidentally, this pin has a hardware interrupt (pin 3) on the Moteino/Arduino. When our button is pressed, the input will change from it’s normal state of 1 to a state of 0. We can set an interrupt on RISING or FALLING and we’ll be good to go.

For the button LED, we’ll need a current-limiting resistor. 330 ohms turns out to be about right, using the 5V input from our USB power input. We attach this to the positive LED terminal, and connect the negative terminal to IO1, which we’ll use as an open-drain output. The IO4 terminal is connected to the drain of the MOSFET footprint, into which we’ve soldered a 2N7000 NPN. IO4 on our mote is io7 internally, and corresponds to pin A2. Once we enable the output, it will be set each cycle, so we don’t /need/ a pulldown, but we’ll configure one anyway.

Construction

Now we need to physically get everything into the box. Not to complicated. First, we solder our button wire. I am personally so tired of soldering crappy stranded wire, such as that in ribbon cable, that I went with some solid core hookup wire. It’s not as pretty and tidy, and makes closing up the box a bit of a pain, but it’s much more robust and cuts down on the short/bad connection sleuthing endemic to stranded wire, especially when you are moving connections around a lot. It also makes screw terminal insertion (especially difficult with the space constraints here) a breeze. Totally worth it in this application.

Getting everything into a box. Pulling cable in and wiring our button.
Getting everything into a box. Pulling cable in and wiring our button.

Once we do some wire coiling and screw terminal insertions, we’re ready to close ‘er up (and then open it up again to program it).

All done! Time to play.
All done! Time to play.

Micro Programming

Programming this thing is pretty basic. You can find the sketch here. We’ll mention a couple things we did that were perhaps noteworthy. The rest has all been covered in our UniMote programming. We decided to slightly modify our interrupt programming. We have noticed that there are issues with running any serial print debug on a function run by an interrupt. This can be quite annoying, especially if you are calling a function that is designed to provide output, such as debug accompanied by our RF send functions, for example. To mitigate this, we used our interrupt to do nothing except set a loop routine to run:

pinMode (3,INPUT_PULLUP);
attachInterrupt (1,setDoButton,RISING);

...

void setDoButton() {
  runDoButton = 1;
}

Then, inside our loop:

if (runDoButton) {
  doButton();
}

The only requirement here is that the loop time is short enough that feedback is fast. Because we’re not really doing much, as long as we make sure our LOOPPERIOD is sufficiently short, we’re good to go.

Finally, sending our value is as simple as wrapping up our standard SendWithSerialNotify function with something that retrieves our data, and sends a specially formatted message. In this case, we’re using the identifiers ‘scalepin’ and ‘scalevalue’. This will allow our gateway to interpret the data accordingly. This is shown below, with dummy data for testing. You can see here that we manually turn the LED output on and off again when the function enters and exits, respectively.

void doButton() {
  if (millis() - lastbutton > debounce && iovalue[7] == 0){
    Serial.println("Button Pressed");
    iovalue[7]=1;
    
    // manually turn on output
    digitalWrite(16,1);
    
    float scalevalue = 9999.9999;
    sendScaleMessage(iopins[7], scalevalue);
    lastbutton = millis();
  }
  runDoButton = 0;
}
void sendScaleMessage(byte pin, float value) {
  int wholePart = value;
  long fractPart = (value - wholePart) * 10000;
  int sendlength = 35; 
  char sendstring[sendlength];
  sprintf(sendstring, "scalepin:%02d,scalevalue:%05d.%05d", pin, wholePart, fractPart);
  sendWithSerialNotify(GATEWAYID, sendstring, sendlength); 
  Serial.println("Waiting, hang on");
  delay(1000); //  This is for visual feedback
  Serial.println("Done");
  iovalue[7]=0;
  digitalWrite(16,0); // turn off light manually
}

Gateway Programming

On the gateway, all we need to do is include a handler for our special message with data fields of ‘scalepin’ and ‘scalevalue’. Pretty easy stuff. We’ll fancy it up later, but for now it’s just as simple as an additional line or two in our serialhandler.py:

elif 'scalevalue' in datadict:
            querylist.append('create table if not exists scalevalues (value float, time string)')
            querylist.append(pilib.makesqliteinsert('scalevalues',[datadict['scalevalue'], pilib.gettimestring()],['value','time']))
            pilib.sqlitemultquery(pilib.logdatabase, querylist)

A couple presses of the button later, and sure enough, we have a table with some values!

Button press dummy data, as viewed in phpliteadmin. Yay data!
Button press dummy data, as viewed in phpliteadmin. Yay data!

We’ll spend some time getting this into a proper page for export and addition of metadata shortly. Oh yeah, and getting real data!

Reference

The above makes use of the open source libraries available on github:

Explanation and installation here: