All posts by colin

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:

CuPID MEGA Remote: Networked Home Thermostat

Background

As we rambled on about previously, we’re pretty into the idea of a wireless, networked temperature controller. An industrial temperature controller with, you know, modern features like monitoring over the local network and/or the web. So we built a prototype, and recently an even better prototype. See here for construction:

http://www.cupidcontrols.com/2014/11/cupid-mega-remote-killer-controller/

Here, we take it for a spin in a pretty popular application: the home thermostat. Once again, the 10,000 foot view looks like this:

Basic CuPID Remote communications and control overview.
Basic CuPID Remote communications and control overview.

The Hardware

In the link above, we detail what goes into the box. Just to review, what we have is the following:

Available Inputs/Outputs:

We detailed all of the available IO on the Moteino mega here, see also here. On this module, we have five IO available on screw terminals:

  • IO0 : Isolated relay output, open-drain output, PWM output, digital output
  • IO1 -4 : Open-drain output, digital output, analog input, optional hardware pullup, optional hardware voltage divider to input

Internally, these IO map according to our MEGA map (full map here):

  • IO0 : io9, pin 14
  • IO1-4 : io21-24, pins 28-31
Used IO

Temperature input:

Here, we are going to use a waterproof 1Wire temperature sensor, the DS18B20. We’ll put it on IO4, and use a resistor pullup on the data line, as specified in the datasheet. The next board revision has available locations for though-hole resistor pullups, but for this guy we’ve just hand-soldered one into place.

Output contact:

Our furnace run signal is the closure of a contact. One side is a pretty typical 24VAC. Because we don’t want to mix signals, we use the contact of a mechanical relay installed on the board. This allows the controller to control more or less anything without worry for things like noise, overvoltage, etc.

Interface IO
  • 4-digit 7-segment with dot point LED display via MAX7221 (previously discussed with code here)
  • 12-bar bicolor display (red, green, red+green = yellow) display via 74HC595 shift registers) (previously discussed with code here)
  • Rotary encoder with button (right, left increment with no stop,  button knob press) (previously discussed with code here)

Controller Setup

First, we need to tell our controller what to read and what to do with it. We do this using the standard command set at controller setup, as detailed previously in our UniMote write-up. Configuration is as simple as issuing our standard command set over serial or RF. If, for example if we wanted to set the first pin as an analog input, which we might do for an RTD with one of the built-in voltage dividers:

Set mode to analog input:

~modparam;iomode;21;2

Set the read frequency:

~modparams;ioreadfreq;21;15

Enable input:

~modparam;ioenabled;21;1

Enable reporting (over RF):

~modparam;ioreportenabled;21;1

Set report frequency (default is 0, meaning report will happen each time input is read):

~modparams;ioreportfreq;21;30

For this controller, however, we are going to use a 1Wire DS18B20 temperature sensor, so our configuration for our input is as follows. Because 1Wire inputs are handled a bit differently (see here for details), they will automatically report each time they are read, using the tag ‘owrom’ instead of the ‘ioval’ used for general IO reporting. If IO reporting is enabled (as above for analog), it will also report using the ‘ioval’ message tag, as the 1Wire value is stored in the iovalues[] array on read, but we don’t need to double-report. This will probably change eventually, but for now it is what it is.

~modparams;iomode;24;4
~modparams;ioenabled;24;1
~modparams;ioreadfreq;24;15

Next we configure our output. We could enable reporting for this as well (to tell us when the furnace is on), but for now we’ll not.

~modparams;iomode;9;1
~modparams;ioenabled;9;1

And finally we configure and enable our control channel, setting the positive feedback to the output above.

~modparam;chanposfdbk;0;9
~modparam;chanenabled;0;1

We’ll configure the remainder of the channel parameters, such as deadband and delay time, from the interface.

Interface Operation

The way the interface works is pretty simple and described by the menu tree below under Controls and Menu Tree. By default, the display shows the current temperature on the LED numerical display and the bar graph shows where the temperature is relative to the setpoint.

Bar location: temperature relative to setpoint

In the bar display, the center is the setpoint. The current temperature is displayed relative to the setpoint on the bar display. Each bar is a unit, 1F in Fahrenheit mode or 1C in Celsius mode. If the temperature is within one temperature unit of the setpoint, two bars in the center will be displayed. If the temperature is two below the setpoint, the bar will be two to the left of center,  if it is three above, it will be three to the right of center … you get the picture. If it is beyond the numerical limit of the scale (6), it will show yellow at the end of the scale.

Bar color: controller action

If the controller is taking action, based on setpoint and deadband, the color will turn to red. If no action is being taken or pending (see below), the color will remain green.

Bar flashing or solid: controller action is active or pending

When the setpoint is outside of the currently measured value + deadband, the controller will attempt to take action. This will change the color of the bar display element. If the delay is > 0, however, the controller will wait until the delay time has expired before acting. During this time, the bar display will flash to indicate the action is pending.

Demonstration of several controller display modes. The bar display indicates process value relative to setpoint, as well as the action condition.
Demonstration of several controller display modes. The bar display indicates process value relative to setpoint, as well as the action condition.
Controls and Menu Tree

The menu is currently described by the tree below. Each movement vertically is controlled by a button push; horizontal is controlled by turning the rotary encoder knob.

CuPID Temperature Controller Menu Tree
CuPID Temperature Controller Menu Tree

 

Remote Data View/Display

As we detailed previously, the data is sent out on RF in json format, which our gateway RF Mote picks up and passes on to our gateway, a raspberry pi. The serial handler, which you can see here, handles a few different types of messages and puts them where they belong as inputs in our databases.

So magically, we open up our gateway and take a look and see our mote shows up on our motes table (this happens with no configuration required):

Our mote automagically shows up on our gateway once it receives its broadcast.
Our mote automagically shows up on our gateway once it receives its broadcast.

We add an interface from the UI for the mote, which has a NODEID of 15 (as you can see from the entry above). What this does is tell the updateio script that if there is a mote present in the motes table with a NODEID of 15, to insert it into our Inputs table.

Adding the interface for our mote ensures it will show up in our list of control inputs.
Adding the interface for our mote ensures it will show up in our list of control inputs.

And, as promised, our Mote temperature value shows up as an input:

Our list of inputs, now with our mote value inserted.
Our list of inputs, now with our mote value inserted.

We can edit it to give it a friendly name by expanding the entry and editing, which goes into a metadata table called ioinfo.

inputedit
Editing our Mote input entry to give it a friendly name.

We don’t have a direct UI edit page for the ioinfo table yet, but it’s on the way. In the meantime, we edit it using phpliteadmin, and also add to the options field the specification “formula:1.8*x+32”. This keyword will tell our IO updater to parse the input using the formula above (in python’s interpreter). In this case, we take the value in Celsius reported by the temperature controller and translate it to Fahrenheit for the UI. Easy peasy. Now let’s take a look at our data accumulating in our Dataviewer (this is some time later, temperature spikes from handling the temperature sensor):

Our dataviewer ... viewing mote data.
Our dataviewer … viewing mote data.

Set up alerts

Just for fun, let’s set up an alert so that when the temperature gets above or below a couple setpoints, we get an email alert. The current actions/notifications/indicators system is set up to handle arithmetic comparisons on Channel values. A Channel is the basic control structure for our CuPID Controller. It has an input, positive and negative feedback outputs, logging preferences, and an assortment of other control parameters. Although it’s only currently set up for simple on/off control (with deadband and delay), we’ve built in provisions for multiple feedback modes. It’s what we use for our reflow oven: set a TC input, set a GPIO output to a relay module, plug in a recipe, and it will control an action. If you don’t set any outputs (or disable them), it just acts as a data container, which in this case will allow us to set up alerts. In the future, it will allow remote setpoint and control parameter commands to be entered from the web UI (see Next Time below).

So let’s add one:

Adding a channel from the UI.
Adding a channel from the UI.

Then we specify that our Mote is the control input, and we’re done:

Editing our channel to specify our Mote temperature as Control Input
Editing our channel to specify our Mote temperature as Control Input

Next we create two alerts. One for Too Hot, and one for Too Cold. We could do other things like trigger on channel outputs active, which would take into account whatever control algorithm we’re using, or a boolean compare for if a channel is enabled (in case somebody accidentally disables it), or really do comparison on any value in the channels table (and eventually ANY table in the database).

Adding email alert actions.
Adding email alert actions.

For now, we configure the Actions, one will be less than, one greater than, both using the Control Value of Channel 1 (which we set above to be the reported Mote Temperature). There are a ton of other parameters you can set here, which are actually detailed in the Parameters expandable table item. Lots of flexibility.

Configuring email alerts. There are a ton of options here, and nearly anything can be set to trigger an action.
Configuring email alerts. There are a ton of options here, and nearly anything can be set to trigger an action.

To test our alert, we set the Too Cold Alert to have a temperature of 70, meaning that we should get an email if it drops below 70 for the specified OnDelay time. Sure enough, I get an email:

Test email alert.
Test email alert.

And after we change the temperature target back to a more reasonable 60F, we get an email letting us know our Alert has turned off. This email, called “Active Reset”, is optional.

Next Time

Remote setpoint by Web GUI:

We already have all the tools necessary to enable control from our gateway. We can send commands over RF in the same format we used above, and with this we can change setpoint, control parameters, you name it. Next time, we’ll add in the control library infrastructure to send remote commands in an object-oriented fashion. We’ll need to do some thinking about data concurrency and race conditions, especially considering we don’t have an RTC on our thermostat.

Finally, we’ll add some shiny widgets to our UI to control all of it. That will be the easy part.

Alert on All the Things

As shown above, we have only partially fleshed out the Actions feature. We need to be able to alert on everything. Why not? We also want to be able to add more complex logic. Like … if one of five rooms is cold, turn on the heat. This will likely entail creating some auxiliary variables. We’ll have a think on that.

Reference

The above makes use of the open source libraries available on github, including sketches, python and all web libraries:

Explanation and installation here:

CuPID MEGA Remote : Killer Controller

Last we met, we built a prototype temperature wireless RF temperature controller. We got some good ideas and a Beer Fridge monitor out of the deal. We also realized, however, that we were flying pretty close to the sun in terms of the available memory we had to work with. Considering we weren’t concerned about power consumption, wanted to make our IO as flexible as possible (for example more interrupts and UARTs), we decided that a more powerful micro is in order. Enter the Moteino MEGA. Take a Moteino R4 and swap out the ATMEGA328P for a 1284P, add a reset button, and you get the Moteino MEGA. Fantastic idea. It’s a slightly larger footprint, but all progress has a price, right?

Now since last time, when we realized soldering was going to be a chore with all of our itty-bitties on one board, we had a couple PCBs made. Since we wanted to use the same enclosure and still use all hand-solderable DIP components, we ended up needing two PCBs stacked. We left the headers for our Moteino exposed, however, so we can still get to them easily. Shown below are the boards with all of the components (some optional) that we can jam onto it.

 

Components for our MEGA controller
Components for our MEGA controller

The other great part about stacking boards is that it will put our displays high enough that we won’t need to attempt to fix them to the inside of the enclosure. This helps for taking it apart and service, and also eliminates the need for the messy silicone arrangement we were attempting on the last build. Here are a few photos after it’s all assembled. You’ll notice the switch mode power supply is not used here, as we’re controlling a thermostat, which has a 24VAC contact. We put this on the mechanical relay, an advantage for when you need to handle things like AC voltage.

Our board and components, assembled, with a demo fit into our box.
Our board and components, assembled, with a demo fit into our box.

The astute reader will note that the board has been modestly pruned in a few locations where my CAD skills and attention to detail were a bit, um, unfocused. This is why we prototype! Here are a few more pics that show the fit, including the height of the displays:

Profiles of our controller, all assembled.
Profiles of our controller, all assembled. The displays are nicely elevated for viewing

Finally, we need to attach our encoder for user input, as well as the remainder of our IO. Here, we’re going to use a waterproof 1Wire temperature sensor, and close a relay contact for 24VAC that runs the house furnace. We’ll power it on a USB power supply and feed that in. We’ll also need to drill holes for the cable glands. We won’t bore you with those details. Here are a few pictures from during assembly:

Getting all the IO into the box. In this case, we've got 5V USB power in, a 24VAC furnace run contact, and a 1Wire temperature sensor.
Getting all the IO into the box. In this case, we’ve got 5V USB power in, a 24VAC furnace run contact, and a 1Wire temperature sensor.

And finally, here is the end product:

All wired up! Everything sealed up and in its place.
All wired up! Everything sealed up and in its place.

Now on to the installation and programming. In our next post, we build this guy into a home thermostat using a Raspberry Pi in our CuPID base unit as our control/sensor gateway.

Reference

The above makes use of the open source libraries available on github, including sketches, python and all web libraries:

Explanation and installation here:

 

UniMote MEGA : Templated Control for Moteino MEGA (UniMote MEGA)

Alright, so we’re super into the modular controller idea (see here for original post on the 328P version), and really like this new MEGA unit offered by LowPowerLab, the Moteino MEGA. It’s got tons of room for our programs, routines, and who doesn’t want MOAR? Anyhow, we’ll have to redesign our memory map based on the scads of IO now available. Here is what our available io map looks like:

UniMote Pin Label Name Function
0 0 PB0
1 1 PB1
2 3 PD0
3 8 PD1
4 9 PD2
5 10 PD3
6 11 PD4
7 12 PD5
8 13 PD6
9 14 PD7
10 16 PC0
11 17 PC1
12 18 PC2
13 19 PC3
14 20 PC4
15 21 PC5
16 22 PC6
17 24 PA0
18 25 PA1
19 26 PA2
20 27 PA3
21 28 PA4
22 29 PA5
23 30 PA6
24 31 PA7

Here’s our EEPROM map:

Item Bytes EEPROM Beg EEPROM End
NodeID 1 0 0
NetworkID 1 1 1
GatewayID 1 2 2
Encryption Key 16 3 18
iomode 25 19 43
ioenabled 25 44 68
ioreadfreq 25 69 93
loopperiod 1 94 94
sleepmode 1 95 95
sleepdelay 1 96 96
chanenabled 8 97 104
chanmode 8 105 112
chanposfdbk 8 113 120
channegfdbk 8 121 128
chandeadband 8 129 136
chanpvindex 8 137 144
chansv 8 145 152

With all this space, we’ve also added a load of other features. We’ll detail those here shortly.

CuPID Remote Enhanced : Flexible IO Mote

Introduction

So recently, we’ve been down the road of creating a versatile harem of remotes. Of particular interest is our small form factor unit, which eschews any front-panel indicators or controls for low power and size.

We want this unit to be small, flexible, attractive, and powerful. We want to be able to have it battery or wall-powered. For inputs, we want 1Wire, digital, analog, and anything we can do on Arduino digital pins. For outputs, logic-level digital, as well as open-drain transistor outputs would be nice. We also want to monitor battery power, which we typically do with a voltage divider, as we did before.

Previously, we showed we can package this all up in a little box we like in our CuPID remote:

CuPID Remote

Since we’ve nailed down our design, we decided to get it onto a board, clean up the box, and make it pretty.

The Board

Let’s introduce the board:

Our CuPID Remote board: flexible and capable, and fits just right. Really, just barely.
Our CuPID Remote board: flexible and capable, and fits just right. Really, just barely.

The Fit

It’s tight. I mean, really tight. If we do nothing, we can wedge the board in there, and we wouldn’t even need to screw it down. The round corners above are gently filed, giving a free fit. Here’s how it fits:

It fits, but barely. But with so little room, we need to make it all count.
It fits, but barely. But with so little room, we need to make it all count.

We’ve left room at the top and bottom, where we’ll drill and fit cable entries. They need a bit of room for the sealing nut and for the cable bend.

Putting it together

So here, we’ll put together one configuration of this remote: a USB-powered, remote sensor node. This one will go into a system with a powered sensor, so our power output screw terminal will come in handy. Here’s our parts layout:

Our parts layout. We don't need to use the female headers, but it's nice to be able to pull the RF unit out if we want.
Our parts layout. We don’t need to use the female headers, but it’s nice to be able to pull the RF unit out if we want.

After some soldering, we get this:

Our assembled board, and how it fits. Looks good!
Our assembled board, and how it fits. Looks good!

Finally, we put our holes in our enclosure, and feed in some USB power into our terminal blocks. We’re ready for whatever sensor we decide to use.

Our completed USB mote, ready for sensor.
Our completed USB mote, ready for sensor.

CuPID Remote : Wireless RF Temperature Controller

The Project

After our remote temperature sensor project, we wanted to work on another of the basic unit operations of a typical controls systems network: the temperature controller. Building the basic CuPID Remote, we wanted to incorporate a number of features:

  • Local indication of control variables
  • Local control of control parameters and current system status
  • RF transmission to a CuPID base unit
  • Flexible IO to allow addition of sensors and local control of relays
  • Encapsulation within a waterproof enclosure

Basically, we wanted a fully functional, waterproof industrial temperature controller, with the added features that it would communicate with RF to our CuPID controller, and then to the world over the internets.

For this project, we’re going to use a 5V usb power supply (although the regulator built in to our Moteino microcontroller could take less or more), and we’ll send out a few logic-level (3.3V) signals to control a solid-state relay. The idea, however, is that we can power it directly using the same power supply that runs something like a solenoid, if for example you were controlling the temperature on a jacketed fermenter by controlling the flow of glycol. In that case, we’d throw in a switching regulator to bring the input voltage down for the microcontroller, and put in a miniature relay or power MOSFET.  We’ll get to this shortly.

The Parts

For the project, we’ll use the following parts:

  • Moteino R4 with RFM69W Receiver
  • 4-digit 7-segment common-anode display, e.g. this
  • Encoder with button, e.g. this guy
  • Polycase WC22F waterproof enclosure
  • MAX 7221 8-digit LED Display Driver
  • PG Cable Glands, e.g. HEYCO 3207
  • Assorted connectors, ribbon cables, and a prototyping board. We won’t list these because they’ll change when we get our PCBs made

Construction

So the really unfortunate part about prototyping embedded electronics is trying to get a bunch of stuff into a small box without a PCB … as you shall see. At first layout, we wanted to shove everything above into our teensy box, along with a couple shift registers to drive a 12-bar LED graph, like we did before. We’ll end up putting this option into the PCB, but as you can see, the prospect of hand-soldering all of these components is a bit mind-numbing.

Layout of all components within our enclosure. We don't use all of these for the prototypes.
Layout of all components within our enclosure. We don’t use all of these for the prototypes.

So we pulled off the shift registers, the power supply, the relay, and soldered up the rest. These are all options for the future. The soldering was still pretty ugly. Here is the wiring diagram for what was left:

Pin layout for the prototype
Pin layout for the prototype

First, we create our holes, insert our cable glands and cables, and insert them into our screw terminals. From left to right, we have USB (5V, GND), Cat5 (outputs 1,2,3, 5V, and GND), and our 1Wire sensor (Data, 5V, GND).

Our cable entries. From left to right: USB, IO, and 1Wire temperature sensor.
Our cable entries. From left to right: USB, IO, and 1Wire temperature sensor.

Now we solder it all up. We add vertical headers to the Moteino board so we can still use an FTDI cable with the lid off. Here is everything all soldered up and inserted:

Assembly of our components into our little box.
Assembly of our components into our little box.

Alright, so let’s mount her and plug her in. Here’s what we get all plugged in and running:

Live action shot of our controller. If you look closely, you can see how we tried a number of adhesives we weren't terribly happy with, which left remnants. They don't affect the visibility once the LEDs are on.
Live action shot of our controller. If you look closely, you can see how we tried a number of adhesives we weren’t terribly happy with, which left remnants. They don’t affect the visibility once the LEDs are on.

One of the nice parts we mentioned previously is the mounting of the headers on the board so we can open it up and easily connect our FTDI cable:

A nice part about this design is that we can easily attach our FTDI connector cable to the headers once we open the top. In the future, we'll install an FTDI chip on-board and use the USB cable for data transfer.
A nice part about this design is that we can easily attach our FTDI connector cable to the headers once we open the top. In the future, we’ll install an FTDI chip on-board and use the USB cable for data transfer.

 

The Code Bits

 7-segment LCD

The tough part of the LCD is handled by a neat little chip, the MAX7221, which will handle up to eight digits, four of which we’re using here. You don’t, of course, need to have this chip, using shift registers, as we demonstrated with a bar segment display previously. If you have a common anode display, however, this chip is a great way to offload the processing and not worry about the timing of the remainder of the program. Here is the code. We use the LedControl library, which has built-in functions for numerical digits, and the remaining characters can be pieced together with binary addition of the digits and the set row command:

void handledisplay(){
  byte displaymode = 0;
  float newlcdvalue;
  
  if (menuy) {
    blinky = 1;
  }
  else {
    blinky = 0;
  }
  
  //  Serial.println(testvalue);
  if (blinky) {
    byte blinkinterval = (millis()/200) % 2;
    if (!blinkinterval) {
      displayon = 1;
    }
    else {
      displayon = 0;
    }
  }
  else {
    displayon = 1;
  }
  
  if (menux == 0){
    if (menuy == 0){
      if (tempmode == 'F'){
        newlcdvalue = CtoF(chanpv[0]);
      }
      else{
        newlcdvalue = chanpv[0];
      }
    }
    if (menuy == 1) {
//      Serial.println("I am in the menu");
      displaymode = 1;  // display characters
      lcdvalue = chartoint(tempmode);
    }
  }
  else if (menux == 1) {
    if (tempmode == 'F'){
        newlcdvalue = CtoF(chansv[0]);
      }
      else{
        newlcdvalue = chansv[0];
      }
  }
  
  if (displayon){
    if (newlcdvalue != lcdvalue) {
      if (displaymode == 0){
        lcdvalue = newlcdvalue;
        setdigits(newlcdvalue);
      }
      else if (displaymode == 1){
        setchars('-','-','-',tempmode);
      }
    }
  }
  else {
    lc.clearDisplay(0);
    lcdvalue = -9999; // ensure refresh
  }
}
void setchars(char char1, char char2, char char3, char char4){
  lc.clearDisplay(0);
  lc.setRow(0,0,chartoint(char1));
  lc.setRow(0,1,chartoint(char2));
  lc.setRow(0,2,chartoint(char3));
  lc.setRow(0,3,chartoint(char4));
}
byte chartoint(char mychar){
  byte charbyte = 62;
  switch (mychar) {
    case '-':
      charbyte = 0;
      break;    
    case 'F':
      charbyte = 71;
      break;
    case 'C':
      charbyte = 78;
      break;
    case 'V':
      charbyte = 62;
      break;
    case 'S':
      charbyte = 91;
      break;
    case 'P':
      charbyte = 104;
      break;
  }
  return charbyte;
}
void setdigits(float lcdvalue) {
   
  int dig1 = lcdvalue / 10;
  float leftovers = lcdvalue - (dig1 * 10);
  int dig2 = leftovers;
  leftovers -= dig2;
  int dig3 = leftovers * 10;
  leftovers -= dig3 / 10;
  int dig4 = leftovers;
    
  if (lcdvalue < 0) {
    
    // display value
    lc.clearDisplay(0);
    
    
    // this should work for the negative sign
    lc.setRow(0,0,1);
    lc.setDigit(0,1,dig1,false);
    lc.setDigit(0,2,dig2,true);
    lc.setDigit(0,3,dig3,false);
  }
  else {
    // display value
    lc.clearDisplay(0);
    lc.setDigit(0,0,dig1,false);  
    lc.setDigit(0,1,dig2,true);
    lc.setDigit(0,2,dig3,false);
    lc.setDigit(0,3,dig4,false);
  }
}

Eventually, we’ll wrap up all the digits for setRow, using our chartoint() function, so we can use the same subfunction for all set functions.  In the meantime, here are the binary values for the segments, using the notation A-H shown above in the wiring schematic:

LCD segment binary value
a 64
b 32
c 16
d 8
e 4
f 2
g 1
h 128
Encoder

Although the way the encoder works is a bit bizarre and archaic, the code is pretty simple. We attach an interrupt one of the pins so that the handler function is called whenever the encoder is activated. It looks a bit like this:

pinMode (encoder0PinA,INPUT);
  pinMode (encoder0PinB,INPUT);
  attachInterrupt(1, doEncoder, CHANGE);

void doEncoder() {
  int pinAval = digitalRead(encoder0PinA);
  int pinBval = digitalRead(encoder0PinB);
  byte direction;
  if ((pinAval == HIGH) && (lastpinAval == LOW)) {
    if (pinBval == HIGH) {
      direction = 0; // CCW
    } else {
      direction = 1; // CW
    }
    // PV menu
    if (menux == 0){
      if (menuy == 0){
        if (direction == 1) {
          menux = 1;
        }
      }
      else if (menuy == 1){
        if (tempmode == 'F'){
          tempmode = 'C';
        }
        else if (tempmode == 'C'){
          tempmode = 'F';
        }
      }
    } // menu top  level
    // SV menu
    else if (menux == 1){
      if (menuy == 0){
        if (direction == 0) {
          menux = 0;
        }
      }
      else if (menuy == 1) {
        if (direction == 1) {
          chansv[0] += 0.1;
        }
        else if (direction == 0) {
          chansv[0] -= 0.1;
        }
      }
    } // menu top  level
  } // 
  lastpinAval = pinAval;
}

You can see our simple menu structure handling here as well, a simple 2D array of states and handling.

The Web UI

Clips coming. Looks like this.

Testing

In production. Here’s a screenshot:

Temperature cycling of our Beer Freezer with a deadband of 2C.
Temperature cycling of our Beer Freezer with a deadband of 2C.

Next

Add segment display for output indication

We’d like to shove one of these in a box. We’ve already prepared one, but we’ll definitely need to put it on a PCB to make the connections. Stand by.

Put Moteino interface on power USB

We’ve already got USB going into our enclosure. No reason to require opening the enclosure lid, if we’ve already got data lines headed in. Trick is, we’ll have to put a permanent FTDI in place to do so. We’ll at least create pads on our PCB to populate if we want to. IF we have room.

Moteino / Arduino and 1Wire : Optimize your read for speed

Hello all,

I’ve been picking apart my code to do some optimization, and an obvious target was the read of 1Wire devices, the DS18B20 in particular. Currently, I use the OneWire library, without the DallasTemperature library, which I didn’t much care for.

What all 1Wire read routines have in common is that they issue the Convert command prior to reading a measured value. For the DS18B20, this is the Convert T command, 0x44. Depending on the resolution, the conversion takes from 75-750ms, per the datasheet.

There are three main issues I had with the implementation using OneWire as in the examples:

  • There was no provision for setting the resolution. While this is available in the DallasTemperature library, it just didn’t make sense to me to rewrite all of my lean code around this library to get this one feature.
  • Wait times are hardcoded. Per the datasheet, it is possible to check with the device for status after issuing the Convert T command to know exactly when the temperature conversion is complete. In most implementations, however, this wait time is hard-coded, and typically VERY generously. For example, in a read operation at 12 bits that actually takes <600ms, the hardcoded wait time is 1000ms. This is a 40% savings, ladies and gentlemen. Futhermore, most test code does not even adjust the delay time for different resolution. This is just insane. A ready that should take 60-75ms will implement a wait of 1000ms (?!). I mean, I understand if speed is not that important to you, but this is crazy.
  • Everything is done synchronously. Nothing else can happen while we are waiting for this conversion This is very inefficient, that is, in the case that your program needs to do anything else. This is  the real reason I dug into this. I wanted to read sensors, but be able to update a display in real-time. The moral of the story: if anything else in your program depends on timing, forget using the default 1Wire read code. So to easily work out the first two items, I created the example below in code box 1. Run it after substituting your 1Wire pin, and you’ll get something like this for reading 9, 10, 11, and 12 bit resolutions:
dsaddress:2838FF400500004E,
 Conversion took: 76 ms
 Raw Scratchpad Data:
 50 1 0 0 1F FF 10 10 21
 Temp (C): 21.00

dsaddress:2838FF400500004E,
 Conversion took: 150 ms
 Raw Scratchpad Data:
 50 1 0 0 3F FF 10 10 51
 Temp (C): 21.00

dsaddress:2838FF400500004E,
 Conversion took: 298 ms
 Raw Scratchpad Data:
 50 1 0 0 5F FF 10 10 C1
 Temp (C): 21.00

dsaddress:2838FF400500004E,
 Conversion took: 596 ms
 Raw Scratchpad Data:
 4F 1 0 0 7F FF 1 10 37
 Temp (C): 20.94

A HUGE improvement over stock wait times. With the accuracy of the DS18B20, it really doesn’t make much sense to use 12 bits, so 10 bits saves me loads in timing. I left in a bunch of original code that’s been commented out so you can see how it was done previously.

Now, we can also separate conversion commands and reading the data back. If you have multiple sensors, you actually want to use the Skip ROM and Convert T commands to tell all devices on the bus to convert simultaneously, but that’s another topic. In the meantime while conversion is taking place, we can do other stuff.

So we separate our read DS18B20 routine into find, set resolution, send conversion command, and finally read temperature. Between the last two, we just continually check in our loop to see that data is ready, and when it is, we read it. Pretty simple, but SUPER EFFECTIVE. You can see we lose a little due to overhead, but still, plenty fast, and we can do other stuff at the same time.

Enjoy!
C

Temp (C): 21.50
Elapsed time (ms): 99
Temp (C): 21.50
Elapsed time (ms): 170
Temp (C): 21.62
Elapsed time (ms): 321
Temp (C): 21.56
Elapsed time (ms): 618

Code box 1:

#include <OneWire.h>

#define LED 9
#define SERIAL_BAUD   115200

void setup(void) {
Serial.begin(SERIAL_BAUD);
}

void loop(void) {
for (int i=9;i<13;i++){
handleOWIO(6,i);
Serial.println();
}

delay(1000);
Blink(LED,3);
}

void handleOWIO(byte pin, byte resolution) {
int owpin = pin;

// Device identifier
byte dsaddr[8];
OneWire myds(owpin);
getfirstdsadd(myds,dsaddr);

Serial.print(F("dsaddress:"));
int j;
for (j=0;j<8;j++) {
if (dsaddr[j] < 16) {
Serial.print('0');
}
Serial.print(dsaddr[j], HEX);
}
// Data

Serial.println(getdstemp(myds, dsaddr, resolution));

} // run OW sequence

void getfirstdsadd(OneWire myds, byte firstadd[]){
byte i;
byte present = 0;
byte addr[8];
float celsius, fahrenheit;

int length = 8;

//Serial.print("Looking for 1-Wire devices...\n\r");
while(myds.search(addr)) {
//Serial.print("\n\rFound \'1-Wire\' device with address:\n\r");
for( i = 0; i < 8; i++) {
firstadd[i]=addr[i];
//Serial.print("0x");
if (addr[i] < 16) {
//Serial.print('0');
}
//Serial.print(addr[i], HEX);
if (i < 7) {
//Serial.print(", ");
}
}
if ( OneWire::crc8( addr, 7) != addr[7]) {
//Serial.print("CRC is not valid!\n");
return;
}
// the first ROM byte indicates which chip

//Serial.print("\n\raddress:");
//Serial.print(addr[0]);

return;
}
}

float getdstemp(OneWire myds, byte addr[8], byte resolution) {
byte present = 0;
int i;
byte data[12];
byte type_s;
float celsius;
float fahrenheit;

switch (addr[0]) {
case 0x10:
//Serial.println(F("  Chip = DS18S20"));  // or old DS1820
type_s = 1;
break;
case 0x28:
//Serial.println(F("  Chip = DS18B20"));
type_s = 0;
break;
case 0x22:
//Serial.println(F("  Chip = DS1822"));
type_s = 0;
break;
default:
Serial.println(F("Device is not a DS18x20 family device."));
}

// Get byte for desired resolution
byte resbyte = 0x1F;
if (resolution == 12){
resbyte = 0x7F;
}
else if (resolution == 11) {
resbyte = 0x5F;
}
else if (resolution == 10) {
resbyte = 0x3F;
}

// Set configuration
myds.reset();
myds.select(addr);
myds.write(0x4E);         // Write scratchpad
myds.write(0);            // TL
myds.write(0);            // TH
myds.write(resbyte);         // Configuration Register

myds.write(0x48);         // Copy Scratchpad

myds.reset();
myds.select(addr);

long starttime = millis();
myds.write(0x44,1);         // start conversion, with parasite power on at the end
while (!myds.read()) {
// do nothing
}
Serial.print("Conversion took: ");
Serial.print(millis() - starttime);
Serial.println(" ms");

//delay(1000);     // maybe 750ms is enough, maybe not
// we might do a ds.depower() here, but the reset will take care of it.

present = myds.reset();
myds.select(addr);
myds.write(0xBE);         // Read Scratchpad

//Serial.print("  Data = ");
//Serial.print(present,HEX);
Serial.println("Raw Scratchpad Data: ");
for ( i = 0; i < 9; i++) {           // we need 9 bytes
data[i] = myds.read();
Serial.print(data[i], HEX);
Serial.print(" ");
}
//Serial.print(" CRC=");
//Serial.print(OneWire::crc8(data, 8), HEX);
Serial.println();

// convert the data to actual temperature

unsigned int raw = (data[1] << 8) | data[0];
if (type_s) {
raw = raw << 3; // 9 bit resolution default
if (data[7] == 0x10) {
// count remain gives full 12 bit resolution
raw = (raw & 0xFFF0) + 12 - data[6];
} else {
byte cfg = (data[4] & 0x60);
if (cfg == 0x00) raw = raw << 3;  // 9 bit resolution, 93.75 ms
else if (cfg == 0x20) raw = raw << 2; // 10 bit res, 187.5 ms
else if (cfg == 0x40) raw = raw << 1; // 11 bit res, 375 ms
// default is 12 bit resolution, 750 ms conversion time
}
}
celsius = (float)raw / 16.0;
fahrenheit = celsius * 1.8 + 32.0;
Serial.print("Temp (C): ");
//Serial.println(celsius);
return celsius;
}

void Blink(byte PIN, int DELAY_MS)
{
pinMode(PIN, OUTPUT);
digitalWrite(PIN,HIGH);
delay(DELAY_MS);
digitalWrite(PIN,LOW);
}

Code box 2:

#include <OneWire.h>

#define LED 9
#define SERIAL_BAUD   115200

OneWire myds(6);
byte readstage;
byte resolution;
unsigned long starttime;
unsigned long elapsedtime;
byte dsaddr[8];

void setup(void) {
Serial.begin(SERIAL_BAUD);
readstage = 0;
resolution = 12;
}

void loop(void) {

if (readstage == 0){
getfirstdsadd(myds,dsaddr);
dssetresolution(myds,dsaddr,resolution);
starttime = millis();
dsconvertcommand(myds,dsaddr);
readstage++;
}
else {
if (myds.read()) {
Serial.println(dsreadtemp(myds,dsaddr, resolution));

Serial.print("Elapsed time (ms): ");
elapsedtime = millis() - starttime;
Serial.println(elapsedtime);
readstage=0;
if (resolution == 12){
resolution = 9;
}
else {
resolution ++;
}
}
}

Blink(LED,5);
}

void getfirstdsadd(OneWire myds, byte firstadd[]){
byte i;
byte present = 0;
byte addr[8];
float celsius, fahrenheit;

int length = 8;

//Serial.print("Looking for 1-Wire devices...\n\r");
while(myds.search(addr)) {
//Serial.print("\n\rFound \'1-Wire\' device with address:\n\r");
for( i = 0; i < 8; i++) {
firstadd[i]=addr[i];
//Serial.print("0x");
if (addr[i] < 16) {
//        Serial.print('0');
}
//      Serial.print(addr[i], HEX);
if (i < 7) {
//Serial.print(", ");
}
}
if ( OneWire::crc8( addr, 7) != addr[7]) {
Serial.print("CRC is not valid!\n");
return;
}
// the first ROM byte indicates which chip

//Serial.print("\n\raddress:");
//Serial.print(addr[0]);

return;
}
}

void dssetresolution(OneWire myds, byte addr[8], byte resolution) {

// Get byte for desired resolution
byte resbyte = 0x1F;
if (resolution == 12){
resbyte = 0x7F;
}
else if (resolution == 11) {
resbyte = 0x5F;
}
else if (resolution == 10) {
resbyte = 0x3F;
}

// Set configuration
myds.reset();
myds.select(addr);
myds.write(0x4E);         // Write scratchpad
myds.write(0);            // TL
myds.write(0);            // TH
myds.write(resbyte);         // Configuration Register

myds.write(0x48);         // Copy Scratchpad
}

void dsconvertcommand(OneWire myds, byte addr[8]){
myds.reset();
myds.select(addr);
myds.write(0x44,1);         // start conversion, with parasite power on at the end

}

float dsreadtemp(OneWire myds, byte addr[8], byte resolution) {
byte present = 0;
int i;
byte data[12];
byte type_s;
float celsius;
float fahrenheit;

switch (addr[0]) {
case 0x10:
//Serial.println(F("  Chip = DS18S20"));  // or old DS1820
type_s = 1;
break;
case 0x28:
//Serial.println(F("  Chip = DS18B20"));
type_s = 0;
break;
case 0x22:
//Serial.println(F("  Chip = DS1822"));
type_s = 0;
break;
default:
Serial.println(F("Device is not a DS18x20 family device."));
}

present = myds.reset();
myds.select(addr);
myds.write(0xBE);         // Read Scratchpad

//Serial.print("  Data = ");
//Serial.print(present,HEX);
//  Serial.println("Raw Scratchpad Data: ");
for ( i = 0; i < 9; i++) {           // we need 9 bytes
data[i] = myds.read();
//    Serial.print(data[i], HEX);
//    Serial.print(" ");
}
//Serial.print(" CRC=");
//Serial.print(OneWire::crc8(data, 8), HEX);
//  Serial.println();

// convert the data to actual temperature

unsigned int raw = (data[1] << 8) | data[0];
if (type_s) {
raw = raw << 3; // 9 bit resolution default
if (data[7] == 0x10) {
// count remain gives full 12 bit resolution
raw = (raw & 0xFFF0) + 12 - data[6];
} else {
byte cfg = (data[4] & 0x60);
if (cfg == 0x00) raw = raw << 3;  // 9 bit resolution, 93.75 ms
else if (cfg == 0x20) raw = raw << 2; // 10 bit res, 187.5 ms
else if (cfg == 0x40) raw = raw << 1; // 11 bit res, 375 ms
// default is 12 bit resolution, 750 ms conversion time
}
}
celsius = (float)raw / 16.0;
fahrenheit = celsius * 1.8 + 32.0;
Serial.print("Temp (C): ");
//Serial.println(celsius);
return celsius;
}

void Blink(byte PIN, int DELAY_MS)
{
pinMode(PIN, OUTPUT);
digitalWrite(PIN,HIGH);
delay(DELAY_MS);
digitalWrite(PIN,LOW);
}

LED Bar Graph with Arduino and shift registers

Intro

So, this article was supposed to be called ‘LED Bar Graph with MAX7221’. We’ve got a pile of these neat little 12-segment bar graphs over here that have red and green LEDs in each digit. As it turns out, however, we’ve got common anode, while the MAX7221/7219 only drives common cathode. This is a bummer, and probably why Adafruit discontinued the common anode and only carries the common cathode now. We’d just pick up some common cathode and be done with it, except, like I said, we’ve got a pile of them. And it seemed like a cool project to make it work with even simpler components than our MAX7221.

Multiplexing?

Connecting up a bunch of LEDs on a shift register really isn’t newsworthy, in fact we posted about it earlier. The key here is that, while we can control all LEDs on one anode individually (each anode of three controls four segments), we can’t turn one LED in one group and one in another group without turning a bunch of others on inadvertently. Looking at our datasheet, which is similar to this one, reveals that we have 14 pins total, three of which are duplicated. So we’ve got eight cathodes and three anodes.

Some basic math is illuminating: we have 24 individual LEDs, each with two states. That gives 2^24 possible state combinations. We only have 14 pins, 3 of which are duplicates, leaving 11. This is only 2^11 possible pin states. We obviously need some trickery. And that’s where LED multiplexing comes in. It works the same way as a row/column setup does: each LED is lit very quickly in succession. It tricks our eyes, and allows fewer pins for a fixed number of LEDs. Take a typical 7-segment plus dot digit, for example. If you have eight eight-segment digits (what you can put on a MAX7221), you have 8×8=64 segments. Individually wired, this would take 128 pins, or 64 with a common anode or cathode. But with only eight cathodes and eight anodes, one per digit and one per segment type for a total of 16, you can multiplex it and operate it at one fourth the pin count. You could do the same thing with two eight-bit shift registers, such as the 74HC595 we’re using here. In our case, we have eight total cathodes per group (four green, four red), and three anodes. We’ll use one register for the cathodes (as we did above), and use the other register for our three anodes. So on to it.

Shift Registers

Each shift register gives us eight bits (we’ll use 74HC595s), so we can get away with two of them and have five bits to spare. They’re about $0.10 apiece. A MAX7221 is something like a dollar, so it’s marginally cheaper.

Testing reveals that driving these guys from 5V seems to be just fine, so our wiring looks a bit like this:

Wiring schematic for two shift registers and a 12-segment bar display.

Notice that we’ve wired our output enable pin to ground, which will make it permanently active. This is fine, since we aren’t sharing our SPI lines with other devices.

We’ve hooked up our clock and data lines on outputs 8-10 on the ‘duino, and we borrowed and modified some code to allow us to set the color on each of the first four digits (eight LEDs):

 void setDisplay(int byte1, int byte2) {
  int numtoset1 = 255-byte1;
  int numtoset2 = byte2;
  digitalWrite(latchPin, LOW);
  
    // shift out the bits:
    shiftOut(dataPin, clockPin, MSBFIRST, numtoset2);
    shiftOut(dataPin, clockPin, MSBFIRST, numtoset1);  

    //take the latch pin high so the LEDs will light up:
    digitalWrite(latchPin, HIGH);
}

Note that we’ve added provision for two bytes, which we’ll need for our remaining digits (actually, for controlling the anodes, but we’ll get to that later). This means that our closest shift register will get the second byte we send, meaning for our one shift register testing, we’ll put our test value in byte1. To start, we have set up the first four bits for red, and the second four for green. Our cathodes are the outputs, so low turns them on, but we take the complement so we can use 1 for ‘on’. This yields the following values to turn on each LED:

segment color value
1 R 1
1 G 16
2 R 2
2 G 32
3 R 4
3 G 64
4 R 8
4 G 128

So to get yellow, we just apply both red and green. 255 is all on, 0 is all off. We don’t need to remember these, as we can just use the built-in bit() function, passing 0-3 for red and 4-7 for green. We wrap it up again in a function that allows us to set, using two arguments: digit and color. We’ll modify this later when we create control for all digits. Here is the wrapper function:

void setDigit(int digit, int colorint) {
  int byte2 = 0;
  int byte1 = 0;
  digit = digit %4;
  if (colorint > 0){
    if (colorint == 1){
      byte1 = bit(digit);
    }
    else if (colorint == 2){
      byte1 = bit(digit + 4);
    }
    else if (colorint == 3){
      byte1 = bit(digit) + bit(digit +4);
    }    
  }  
  setDisplay(byte1,byte2);
}

We set up a basic function to set the first digits:

void loop() {
  setDigit(0,0);
  setDigit(1,1);
  setDigit(2,2);
  setDigit(3,3);
}

This will set the digits one at a time. Persistence of vision dictates that we can’t tell the difference whether or not they are on simultaneously or in sequence (as long as the frequency is >25Hz (!)) … and we get what we expect:

Our segments, lit in series, appear to be lit simultaneously, the core principle of multiplexed LED illumination.
Our segments, lit in series, appear to be lit simultaneously, the core principle of multiplexed LED illumination.

Neither I nor the camera can tell that they aren’t all constantly on. Now, the astute reader should ask: “Why are you turning the digits on separately, instead of all together?” This is a great question, and really the point of this post. See Mutiplexing, above. The short answer is that we don’t have enough pins to set all states simultaneously and uniquely, so we have to shuffle through them.

Now on to the remaining digits. We’ll modify our setDigit() routine to tolerate digits greater than four, and set the proper anode. Note the use of the mod operator to bring all digit values into the range of 0-3 for our cathodes.

void setDigit(int digit, int colorint) {
  int byte2 = 0;
  int byte1 = 0;
  if (digit < 4){
    byte2 = 1;
  }
  else if (digit < 8){
    byte2 = 2;
  }
  else if (digit < 16) {
    byte2 = 4;
  }
  digit = digit %4;
  if (colorint > 0){
    if (colorint == 1){
      byte1 = bit(digit);
    }
    else if (colorint == 2){
      byte1 = bit(digit + 4);
    }
    else if (colorint == 3){
      byte1 = bit(digit) + bit(digit +4);
    }    
  }  
  setDisplay(byte1,byte2);
}

We’ll call it like so:

setDigit(0,0);
  setDigit(1,1);
  setDigit(2,2);
  setDigit(3,3);
  setDigit(4,0);
  setDigit(5,1);
  setDigit(6,2);
  setDigit(7,3);
  setDigit(8,0);
  setDigit(9,1);
  setDigit(10,2);
  setDigit(11,3);

This seems fine, but what we get are fluttering LEDs, as shown here:

Obviously, 8×8 matrices make eight calls work, so our timing is probably suspect, or the difference between eight and twelve calls breaks the camel’s back. We don’t feel like doing the math. We don’t need to troubleshoot that, however. Remember that we’re making eight calls for only three anodes, so we can further optimize. We can combine the twelve calls into three (there are only three anodes). We’ll just restructure our algorithm a bit.

First, we define a quick function to return a value from digit and color:

int getByteFromColorInt(int digit, int colorint){
  int returnvalue=0;
  if (colorint > 0){
    if (colorint == 1){
      returnvalue = bit(digit);
    }
    else if (colorint == 2){
      returnvalue = bit(digit + 4);
    }
    else if (colorint == 3){
      returnvalue = bit(digit) + bit(digit +4);
    }    
  }
  return returnvalue;
}

Then, we split up the calls into groups:

void setBarDigits(int barColors[]) {
  int i;
  int byte21 = 0;
  int byte22 = 0;
  int byte23 = 0;
  for (i=0;i<4;i++) {
    byte21 += getByteFromColorInt(i, barColors[i]);
  }
  Serial.print("byte21: ");
  Serial.println(byte21);
  setDisplay(byte21,1);
  for (i=4;i<8;i++){
    byte22 += getByteFromColorInt(i%4, barColors[i]);
  }
  Serial.print("byte22: ");
  Serial.println(byte22);
  setDisplay(byte22,2);
  for (i=8;i<12;i++){
    byte23 += getByteFromColorInt(i%4, barColors[i]);
  } 
  Serial.print("byte23: ");
  Serial.println(byte23);
  setDisplay(byte23,4);
}

And simply call it like so:

void loop() {
  int barColors[12]={0,1,0,1,0,1,0,1,0,1,0,1};
  setBarDigits(barColors);
}

And we have success:

 

Alternating LEDs show that our segment control of multiple anodes is workign as advertised.
Alternating LEDs show that our segment control of multiple anodes is working as advertised.

One thing to remember is that the setBarDigits() function is running three states in series, and ending on one. So if you throw in a delay after the call, or do something time-consuming between when it ends and when it’s called again, it’s going to remain in the last state. So doing this, for example:

 void loop() {
  int barColors[12]={0,1,0,1,0,1,0,1,0,1,0,1};
  setBarDigits(barColors);
  delay(1000);
}

Will give you this:

 

Which is probably not what you want. So you need to run it often. This is why separate chips dedicated to this like the MAX7219/7221 make sense: they do all of this elsewhere so you don’t have to worry about it. Anyway, so let’s have some fun with some animation. First, we modify our setBarDigits() function to accept a runtime argument. This way the function will run its loop to maintain an set of digits for the time we specify:

void setBarDigits(byte barColors[], int runtime) {
  int i;
  int byte21 = 0;
  int byte22 = 0;
  int byte23 = 0;
  long starttime = millis();
  boolean doRun = true;
  
  while (doRun){
    byte21 = 0;
    byte22 = 0;
    byte23 = 0;
    for (i=0;i<4;i++) {
      byte21 += getByteFromColorInt(i, barColors[i]);
    }
    Serial.print("byte21: ");
    Serial.println(byte21);
    setDisplay(byte21,1);
    for (i=4;i<8;i++){
      byte22 += getByteFromColorInt(i%4, barColors[i]);
    }
    Serial.print("byte22: ");
    Serial.println(byte22);
    setDisplay(byte22,2);
    for (i=8;i<12;i++){
      byte23 += getByteFromColorInt(i%4, barColors[i]);
    } 
    Serial.print("byte23: ");
    Serial.println(byte23);
    setDisplay(byte23,4);
    
    if (millis()-starttime < runtime) {
      doRun = true;
    }
    else {
      doRun = false;
    }
  }
}

Then we create an array of states we’d like to see and pass them one by one:

void loop() {
  byte barColors1[12]={1,0,0,0,1,0,0,0,1,0,0,0};
  byte barColors2[12]={0,1,0,0,0,1,0,0,0,1,0,0};
  byte barColors3[12]={0,0,1,0,0,0,1,0,0,0,1,0};
  byte barColors4[12]={0,0,0,1,0,0,0,1,0,0,0,1};
  byte barColors5[12]={2,0,0,0,2,0,0,0,2,0,0,0};
  byte barColors6[12]={0,2,0,0,0,2,0,0,0,2,0,0};
  byte barColors7[12]={0,0,2,0,0,0,2,0,0,0,2,0};
  byte barColors8[12]={0,0,0,2,0,0,0,2,0,0,0,2};
  byte barColors9[12]={3,0,0,0,3,0,0,0,3,0,0,0};
  byte barColors10[12]={0,3,0,0,0,3,0,0,0,3,0,0};
  byte barColors11[12]={0,0,3,0,0,0,3,0,0,0,3,0};
  byte barColors12[12]={0,0,0,3,0,0,0,3,0,0,0,3};
  
  byte* barColorArray[12];
  barColorArray[0] = barColors1;
  barColorArray[1] = barColors2;
  barColorArray[2] = barColors3;
  barColorArray[3] = barColors4;
  barColorArray[4] = barColors5;
  barColorArray[5] = barColors6;
  barColorArray[6] = barColors7;
  barColorArray[7] = barColors8;
  barColorArray[8] = barColors9;
  barColorArray[9] = barColors10;
  barColorArray[10] = barColors11;
  barColorArray[11] = barColors12;

  for (int i=0;i<12;i++){
    setBarDigits(barColorArray[i],200);
  }
}

And we get this charming display:

But we still need to verify that we can set digits independently. The above doesn’t really demonstrate that, as we could be tying all the anodes together. So let’s switch it up a bit:

Perfect!

Direct Raspberry Pi IO with Apache and WiringPi

So last time, we demonstrated how to control your Pi IO with an intermediate database, using either our CuPID Controls UI and interface or a barebones installation with IO setpoints. We like this arrangement, for many reasons, such as error-handling, safety interlocks, datalogging, race conditions, and many other bonus features. It’s what we use for all of our applications.

We have come to realize, however, that there are advantages and elegance to an even simpler approach. Specifically, if you want the fastest possible response and minimal infrastructure, you may want to manipulate the Raspberry Pi IO directly. You may not want to rely on an intermediate database and daemon script to enact your commands.  For each application there is a best (or better) approach.

For this reason, we decided to find an effective way to bypass the typical problem of manipulating IO from a web interface: scripts run by anybody other than root, www-data for example, cannot execute the typical API, RPi.GPIO, to enable or disable GPIO outputs. This is currently possible using specialty web services, such as WebIOPi, but we would like to do so on a standard web server, such as Apache. This offers the possibility of doing so over not just your local network, but from anywhere you wish, as it is over a universal web interface, using javascript and html.

WiringPi offers this possibility, using the IO access from /sys/class/GPIO, as documented here. We don’t do anything special here except set up a wsgi method to execute these commands and call it using some javascript/jQuery. These are detailed below.

 The Requirements

  • Apache with mod_wsgi enabled
  • WiringPi. Installation is covered here
  • wsgi script alias directed to your wsgi script, something like this in your site configuration file:
 WSGIScriptAlias /wsgigpioactions /usr/lib/iicontrollibs/wsgi/wsgigpioactions.wsgi
  • A wsgi script, at location above, outlined below
  • An html document sourcing jQuery, so we can use the ajax call

WiringPI

We won’t bore you with stuff better covered elsewhere, like all of the features of WiringPi, or how to install it. The main feature we exploit is listed here:

  • wiringPiSetupSys (void) ;

This initialises wiringPi but uses the /sys/class/gpio interface rather than accessing the hardware directly. This can be called as a non-root user provided the GPIO pins have been exported before-hand using the gpio program. Pin numbering in this mode is the native Broadcom GPIO numbers – the same as wiringPiSetupGpio() above, so be aware of the differences between Rev 1 and Rev 2 boards.

The key feature, in bold, is that we can manipulate GPIO without root access. This is key, as our scripts executed from a web page via wsgi will be run as www-data.

Another nice feature of the package is that it comes with a handy command-line utility, gpio, that allows us to do our manipulation quite easily. So the code below will set up GPIO 18 for non-root access, and turn it on and then off:

gpio export 18 out
gpio -g write 18 1
gpio -g write 18 0

It doesn’t really get much easier than that. The export command automatically uses BCM numbering, and the ‘-g’ will let our gpio write command know to also use this scheme.

Although there is a python wrapper for this utility, we call it directly from our python scripts with a couple simple functions. More on that later.

The WSGI script

The server-side script is the special sauce. It is what will take our web page command and read or write on GPIO using WiringPi with the basic commands listed above. The mechanics of the wsgi call are explained here and here, but the gist is that with some leading headers and a return of data as requested, we can pass data to a wsgi script in json format, execute as we see fit in python, and return the data in a dictionary to do as we see fit in our web page. Take a look here for the complete wsgi script. We pass an object to the script in json defining an action, and act on our action as we see fit. Here, for example, is our action on an output toggle:

if d['action'] == 'wptoggleGPIOvalue':
    try:
        BCMpin = int(d['BCMpin'])
    except KeyError:
        data['message'] = 'No pin sent with command'
    else:
        from subprocess import check_output, call

        output = int(check_output(['gpio','-g','read','18']))
        # call(['gpio','export',BCMpin,'output'])
        if output == 0:
            call(['gpio','-g','write',BCMpin,'1'])
        else:
            call(['gpio','-g','write',BCMpin,'0'])

This is really basic stuff. The command assumes the gpio has already been exported as writable by /sys/class/,  reads the state, and writes the inverse. Piece of cake. We’ll add in some error-handling and export py-fu, but these are the nuts and bolts.

Polling data from the web page using jQuery

Using some basic ajax, we can execute calls to our wsgi script to get our data. The basic function is below:

function wsgiwpgpioactions (commands,callback) {
    // Get the data
    commands=commands || {action:'testaction'}
    callback = callback || logdone;
    $.ajax({
        url: "/wsgigpioactions",
        type: "post",
        datatype:"json",
        data: commands,
        success: function(response){
            callback(response)
        }
    });
}

We just need to pass it an action to carry out and a callback to execute on the data. It will execute asynchronously, execute the command we have issued, and return whatever data we have requested. Linking this to buttons, we can set up the jQuery action on a keypress:

$("testbutton").click(function)(){
    var command = {action:'wptoggleGPIOvalue',GPIOid:18}
    wsgigpioactions(command)
}

So in the above function, if we create a button with id of ‘testbutton’ and click it, it will execute a toggle of GPIO 18 as listed in the wsgi script above. Easy enough. If we want to get a little more complicated with it, we can apply the same key behavior to multiple button classes with different gpio IDs. The function below uses a helper function to extract a GPIO id from the DOM based on where the click came from. This way we don’t have to write  a button behavior for each:

$('.canpress').click(function(){
        var clickid = this.id
        var result = getGPIOfromid(clickid)
        if (result.mode == 'mode'){
            var action = 'wptoggleGPIOmode'
        }
        else if (result.mode == 'value') {
            var action = 'wptoggleGPIOvalue'
        }
        var command = {action:action,GPIOid:result.GPIOid}

        if (runqueries) {
//            alert('running command:')
//            alert('action: ' + action + ' , GPIOnum: ' + result.GPIOnum)
            wsgiwpgpioactions(command,updatestatusdata)
        }
        else {
            alert('queries not activated for command:')
            alert('action: ' + action + ' , GPIOnum: ' + result.GPIOnum)
        }
    });

Our helper function:

function getGPIOfromid(clickid) {
    if (clickid.search('mode') > 0){
            var mode = 'mode';
            // Chop up string
            // starts with GPIO
            // double-digit number
            if (clickid.search('mode') == 6) {
                var GPIOnum = clickid.slice(4,6);
                var GPIOid = clickid.slice(0,6);
            }
            // single digit number
            else {
                var GPIOnum = clickid.slice(4,5);
                var GPIOid  =clickid.slice(0,5);
            }
        }
        else if (clickid.search('value') > 0) {
            var mode = 'value';
            // Chop up string
            // starts with GPIO
            // double-digit number
            if (clickid.search('value') == 6) {
                var GPIOnum = clickid.slice(4,6);
                var GPIOid = clickid.slice(0,6);
            }
            // single digit number
            else {
                var GPIOnum = clickid.slice(4,5);
                var GPIOid  =clickid.slice(0,5);
            }
        }
    return {GPIOnum:GPIOnum, mode:mode, GPIOid:GPIOid}
}

 Putting it all together

Now, as we did before, we’ll set up a physical GPIO interface page, so we can monitor status and punch some buttons. First of all, we’ll need to get our gpio status regularly to update our buttons. So we wrap a text parser around the gpio readall function to return a dictionary array of current gpio statuses:

def getgpiostatus():

    from subprocess import check_output

    gpiolist=[]
    alloutput = check_output(['gpio','readall'])
    lines = alloutput.split('\n')[3:18]
    for line in lines:
        BCM1 = line[4:6].strip()
        wpi1 = line[10:12].strip()
        name1 = line[15:22].strip()
        mode1 = line[25:30].strip()
        val1 = line[32:34].strip()
        phys1 = line[36:39].strip()

        phys2 = line[42:44].strip()
        val2 = line[46:48].strip()
        mode2 = line[50:55].strip()
        name2 = line[57:65].strip()
        wpi2 = line[68:70].strip()
        BCM2 = line[74:76].strip()

        if BCM1 and BCM1 != '--':
            gpiolist.append({'BCM': BCM1, 'wpi': wpi1, 'name': name1, 'mode': mode1, 'value': val1, 'phys': phys1})
        if BCM2 and BCM2 != '--':
            gpiolist.append({'BCM': BCM2, 'wpi': wpi2, 'name': name2, 'mode': mode2, 'value': val2, 'phys': phys2})

    return gpiolist

We put that into our general-purpose library, pilib, and set up our wsgi script to call it (you can easily grab the function from our git blob and put it into your wsgi script if you don’t want pilib)

if d['action'] == 'wpgetgpiostatus':
    from pilib import getgpiostatus
    data = getgpiostatus()

And define a function in our web page to call the wsgi and return the data to the web page:

//// Update all GPIO data
function updategpioData() { wsgiwpgpioactions({action:'wpgetgpiostatus'},updatestatusdata)
}

This function runs our our ajax function wsgiwpgpioactions, with a callback of updatestatusdata, which will run once the ajax function is done. This function  takes the data as returned and inserts it into the DOM using some jQuery;

function updatestatusdata(statusdata){
    var text = '';
    $.each(statusdata, function(index,gpiodict){

        var value = gpiodict.value;
        var mode = gpiodict.mode;

        var valueelements =  $('.' + 'GPIO' + statusdata[index].BCM + 'value');
        valueelements.removeClass('on')
        valueelements.removeClass('off')
        if (value == '1'){
            text = 'On';
            valueelements.addClass('on')
        }
        else if (value == '0'){
            text = 'Off';
            valueelements.addClass('off')
        }
        else {
            text = 'Err';
        }
        valueelements.html(text);
        var modeelements = $('.' + 'GPIO' + statusdata[index].BCM + 'mode');

        modeelements.removeClass('input')
        modeelements.removeClass('output')
        if (mode == 'IN'){
            text = 'In';
            modeelements.addClass('input')
        }
        else if (mode == 'OUT'){
            text = 'Out';
            modeelements.addClass('output')
        }
        else if (mode == 'ALT0'){
            text = 'I2C';
            modeelements.addClass('I2C')
        }
        else {
            text = 'Err';
        }
        modeelements.html(text)
    })
}

So all we have to do is label our html elements properly with classes, and our update function will populate them nicely. For example:

<tr>
   <td>11</td>
   <td align="center"><div class="GPIO17value lightindicator canpress" id="GPIO17value">X</div></td>
   <td>&nbsp;</td>
   <td align="center"><div class="GPIO18value lightindicator canpress" id="GPIO18value">X</div></td>
   <td>12</td>
</tr>

You’ll notice above we added and removed ‘on’ and ‘off’ classes, and a few others, so that the buttons and elements look proper when they change values:

.lightindicator.on, .lightindicator.input {
        background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #77ff77), color-stop(1, #00cc00) );
        background:-moz-linear-gradient( center top, #77ff77 5%, #00cc00 100% );
        filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#77ff77', endColorstr='#00cc00');
        background-color:#ededed;
    }
    .lightindicator.off {
        background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #dddddd), color-stop(1, #777777) );
        background:-moz-linear-gradient( center top, #dddddd 5%, #777777 100% );
        filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#dddddd', endColorstr='#777777');
        background-color:#ededed;
    }

Demo

So let’s do something a bit more interesting with the framework that demonstrates we can do some real-time operation. We stripped down our Pi GPIO page EVEN FURTHER to contain only four buttons. We define them as 18, 23, 24, 25, in this version. We create four super-big buttons on our page, like so:

Our basic four GPIO button screen. Press a button, turn on an output.
Our basic four GPIO button screen. Press a button, turn on an output.

Next, we hook up some china special laser diodes on a board with some mosfets. We use one of our power output boards because it nicely breaks out our GPIO and power on the CuPID COM1 or COM2 bus. This is really nothing fancier than directly connecting our GPIO to the transistor gates. We’re just using what we have available here … and using our COM power lets us put our lights wherever we want (or as far as we care to stretch ethernet cable. Anyhow, here are a few pictures:

Our test setup. A CuPID and a few laser diodes, hooked up to four GPIO.
Our test setup. A CuPID and a few laser diodes, hooked up to four GPIO.
A close-up of our output setup. We're using a CuPID output board as a breakout for the GPIO on ethernet and to grab 5V and ground.
A close-up of our output setup. We’re using a CuPID output board as a breakout for the GPIO on ethernet and to grab 5V and ground. We machined up a little holder for our laser diodes.
The backside of our output breadboard. Four NPN 2N7000 mosfets, four 47ohm current limiting resistors, and four 10k pull downs on our outputs.
The backside of our output breadboard. Four NPN 2N7000 mosfets, four 47ohm current limiting resistors, and four 10k pull downs on our outputs.

And finally, let’s show it in motion. Beware, poor quality video.

CuPID Remote : Read, Database, Logging and UI

The Project

Building on our previous post, where we laid out the fundamentals for our Mote communication in a temperature Mote, and built up a modular Mote sketch that allows us to reprogram parameters via serial and radio, this time we’ll build up the processing back end that will get our data into our CuPID gateway and our User Interface – a web browser.

So we’ll set out to do the following:

  • Process IO data on gateway end into remote table
  • Process IO data into control and display algorithms alongside local data in the gateway

The Basics

We talked briefly about getting data into our CuPID from our gateway Moteino in another post, but we’ll rehash the basics here just to cover the bases.

We’ve got our gateway Moteino talking to our remotes, and passing on the data over serial to our Pi/CuPID, which is listening on the serial port /dev/ttyAMA0. It parses out the data between message demarcations and ends up with a nice json-formatted message, which it drops into a dictionary. We end up with something like this:

{'iovalue': '0', 'RX_RSSI': '-67', 'iomode': '01', 'iopin' : '04', 'nodeid': '2'}

This message comes from node 2, which is telling us that pin 4 is in mode 01 (digital output), and currently has a value of 0 (off). Cool. Let’s get it into tables, where it will be useful!

Tabling the Data

We are going to have a table of values brought in from the remotes, aptly called ‘remotes’, so we’ll throw this in there. Before we do so, however, let’s think about the structure of the table. We want enough fields to be present so that we can quickly identify whether a record should be inserted or replaced. To do this, we’ll set up a few auxiliary variables:

  • msgtype – this is metadata that will tell our scripts how to process
  • keyvalue – a unique value for the data to be reported. will vary with msgtype
  • keyvaluename – an identifier for the keyvalue, for handy use

Based on the keys present in the data, we’ll classify the message, and do a smart replace in the remote table.

So, for example, for two common message types:

runquery = False
        nodeid = datadict['nodeid']
        querylist = []
        if 'iovalue' in datadict:
            # iovalue type message
            try:
                msgtype = 'iovalue'
                keyvalue = datadict['iopin']
                keyvaluename = 'iopin'
            except:
                print('oops')
            else:
                runquery = True

        elif 'owdev' in datadict:
            # 1Wire message
            try:
                msgtype = 'owdev'
                keyvalue = datadict['owrom'][2:]
                keyvaluename = 'owrom'
            except:
                print('oops')
            else:
                runquery = True
        if runquery:
            deletequery = pilib.makedeletesinglevaluequery('remotes',{'conditionnames':['nodeid', 'keyvalue','keyvaluename'],'conditionvalues':[nodeid,keyvalue,keyvaluename]})
            insertquery = pilib.makesqliteinsert('remotes',  [nodeid, msgtype, keyvaluename, keyvalue, stringmessage, pilib.gettimestring()], ['nodeid', 'msgtype', 'keyvaluename', 'keyvalue', 'data', 'time'])
            querylist.append(deletequery)
            querylist.append(insertquery)
            pilib.sqlitemultquery(pilib.controldatabase, querylist)

So here, for each message type, we create a key with a value and a name. For io values, this is ‘iopin’ and then the number of the pin. This uniquely identifies a table entry. If an entry exists with the same nodeid, key name, and key value, we will replace it. We do this by constructing a query with our auxiliary function, makedeletesinglevaluequery, which takes a list of conditions and values and constructs a query. We then create an insert query with our data, which consists of the above, a data field that contains the raw message, and a timestamp. We can do most of the above with foreign key constraints on the database, but we keep it in our script for now. Running our serial monitor with this processing sequence, we get what we expect in our remotes table:

Our basic motes table, viewed here in phpliteadmin
Our basic motes table, viewed here in phpliteadmin

We’ll need to add in a hygiene function to remove old entries, but this will work for now. We’re now ready to do some postprocessing on our table, but first let’s create a basic page to display our mote table. Some copy pasta of existing pages and a little jQuery later, and we have this:

Our basic Mote table.
Our basic Mote table.

Great! Now let’s get it into our IO tables, so we can view, log and process it just like everything else.

Integrate the IO

To get our remote data into our IO tables, we’ll add the mote as an interface into our interfaces table. This way, we can selectively enable and disable processing of individual motes, and we can add options for poll period, how stale data needs to be before we ignore it, and other good stuff.  For now, we create a basic interface entry:

Our interfaces table entry for our Mote will allow selectively setting options and enable/disable. It will be processed just like our other interfaces.
Our interfaces table entry for our Mote will allow selectively setting options and enable/disable. It will be processed just like our other interfaces.

Alright, so we just need to add a case for our updateio script to process the entries. This takes a little py-fu, but it’s not too bad. We’ll need to process each message type separately, and we cheat on the name of each input for the moment, as we don’t feel like setting up metadata entries in our ioinfo table just yet. We are pleasantly greeted:

Our mote entries, all wrapped up and inserted into our IO table. We're now ready to process them as if they were local inputs!
Our mote entries, all wrapped up and inserted into our IO table. We’re now ready to process them as if they were local inputs!

We can now use our remote inputs as if they were local! Let’s take a view in our dataviewer. We scroll down to find our io automatically logging (default of 100 points), and select the onewire sensor. We’re done!

Our imported data in our dataviewer. We're now transparently reading remote data as if it were local.
Our imported data in our dataviewer. We’re now transparently reading remote data as if it were local.

Icing

Let’s revisit the idea of metadata for these channels. We hardcoded colloquial names for our mote inputs based on their unique input IDs, but it would be ideal if we again treat them like our other IO and give them metadata entries. This will also allow us to add other options, such as scaling. So we add a text column to our ioinfo table, and create entries for our remote devices. We can do this simply by editing the text in the IO table and pressing “update”:

Updating metadata for our remote inputs.

Next, we’d like to add a scaling factor. Our analog input being reported here, iopin 20, is hooked up to a voltage divider that will monitor battery input. So the value, 512, is 513 / 1024 * Vcc, where Vcc is 3.3V. We’re reading about 1.65V. Our voltage divider is a 470k resistor and a 1M resistor, with the 470k on the ground side. In other words, if we are reading 1.65V, the voltage at the high side is 1.65V * (1000 + 470)/470 = 5.2V. This is about right, within error on the resistors. We probably shouldn’t be using 5V power for our 1Wire sensor which will put data at 5V where Vcc is 3.3V, but oh well. Seems ok. So this gives us a total scaling factor of (1470/470)/1024*3.3 =0.0101 . We should technically add 1 to the input value, as it’s zero-indexed, but we’ll ignore that for now. Later, we’ll parse formulas in python, but leave that for another day.

So back to how we’ll make this happen. We’ll put a json-encoded option of ‘scale’ into the options field of the ioinfo metadata table. Then, when we process our io, we’ll see if this field exists, and scale it as we process it into the io table. A little more py-fu and this happens. We make the entry manually using phpliteadmin, since we don’t have an edit page set up for this options field yet:

Adding our options field in manually in phpliteadmin. We add scale to our battery monitor channel to scale it properly.
Adding our options field in manually in phpliteadmin. We add scale to our battery monitor channel to scale it properly.

Now let’s flip back to our inputs page. Just like magic, we’re all scaled! Now we have an accurate battery monitor that will log!

Our Battery monitor is updated and scaled, as we specified in our metadata table.
Our Battery monitor is updated and scaled, as we specified in our metadata table.

 Next time:

  • Programming our Motes to send channel status data to our gateway
  • Setting up our gateway to reprogram our Motes when they come online, using a table of set values, and

Reference

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

Explanation and installation here: