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:
We’ll get you a little final UI here, but here’s a taste:
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):
We’re passing that into our UPS-enabled CuPID through our gateway RF node:
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:
We’ve also talked about our general web-based UI strategies:
We have a few more elements to wrap up:
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:
So on our UI, we double-check our response comes through:
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) 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(':') channel = address.split(':') # 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.
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:
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!