UThing Example 1: MQTT Temperature Sensor

From Bright Things Wiki
Jump to: navigation, search

This page describes how to implement a simple temperature sensor using a μThing.

Using a μThing to implement a temperature sensor is of course massive overkill. This is only meant to explain the architecture and how to use some of the advanced features in the uThing/Ionic combination.

The example is also meant to illustrate the whole point of the μThing, which is going from idea to production ready implementation in very few steps.

Overall Architecture

The intended architecture is illustrated in the following drawing:

pub?w=800&.png

The temperature sensors are the Dallas/Maxim DS18B20 1-Wire sensors. These are hooked up directly to one of the Atmel digital I/O pins on the uThing. An Arduino sketch will read the value of the attached sensors and expose them via the I²C bus. The Arduino sketch represents the only code that will have to be developed for this project.

The Ionic standard i2cbridge will pull the values from the Atmel device and make them available on Ionic's internal message bus ubus.

Another Ionic standard package mqttbridge will then periodically read these values from the ubus and publish them as MQTT messages.

Atmel Programming (Arduino Sketch)

The Atmel processor used on the μThing is 100 % software compatible with both the Arduino Leonardo and the Arduino Yun, so programs can be developed using the standard Arduino Software (IDE).

While the μThing is roughly equivalent of an Arduino Yun, the two "halfs" are wired up a bit different. A major difference is that the I²C bus is wired up and enabled by default in Ionic. I²C is therefore the obvious choice for communication between the Atheros and the Atmel side.

For this particular example, I've written a sketch that look like this:

/*
 * I2C Bridge Temperature Test
 * 
 * Author: Lars Boegild Thomsen <lth@bright-things.com>
 * 
 */

#include <Wire.h>
#include <OneWire.h>
#include <DallasTemperature.h>

#define ONE_WIRE_BUS 9

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature t(&oneWire);

#define I2C_TYPE_NONE 0
#define I2C_TYPE_BOOL 1
#define I2C_TYPE_BYTE 2
#define I2C_TYPE_INT 3
#define I2C_TYPE_LONG 4
#define I2C_TYPE_FLOAT 5

// Variables to be exposed to I2C
int t1 = 0;
int t2 = 0;
int t3 = 0;
int t4 = 0;
int t5 = 0;
long upCounter = 0;

unsigned int i2cRequestAddr = -1;

// Structures exposing variables
typedef struct I2cRegister {
  int type; 
  void *address;
};

const I2cRegister registers[] = {
  {4, &upCounter},
  {3, &t1},
  {3, &t2}, 
  {3, &t3}, 
  {3, &t4}, 
  {3, &t5}
};


void setup() {
  
  Serial.begin(115200);

  // Power Temperature Sensors
  digitalWrite(12, HIGH);

  t.begin();
  t.setResolution(12);
  
  Wire.begin(42); /* Obviously */

  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent);

}

void loop() {

  t.requestTemperatures();
  
  upCounter = millis();

  t1 = t.getTempCByIndex(0) * 1000;
  t2 = t.getTempCByIndex(1) * 1000;
  t3 = t.getTempCByIndex(2) * 1000;
  t4 = t.getTempCByIndex(3) * 1000;
  t5 = t.getTempCByIndex(4) * 1000;

  delay(100);
  
}

void requestEvent() {

  // Check if we actually have an address
  if (i2cRequestAddr == -1) 
    return;

  byte bytes[4];

  switch (registers[i2cRequestAddr].type) {
    case I2C_TYPE_BOOL: 
      bytes[0] = (*(bool *)registers[i2cRequestAddr].address) & 1;
      Wire.write(bytes, 1);
      break;
    case I2C_TYPE_BYTE:
      bytes[0] = *(byte *)registers[i2cRequestAddr].address;
      Wire.write(bytes, 1);
      break;
    case I2C_TYPE_INT:
      bytes[0] = (*(int *)registers[i2cRequestAddr].address);
      bytes[1] = (*(int *)registers[i2cRequestAddr].address) >> 8;
      Wire.write(bytes, 2);
      break;
    case I2C_TYPE_LONG:
      bytes[0] = (*(long *)registers[i2cRequestAddr].address);
      bytes[1] = (*(long *)registers[i2cRequestAddr].address) >> 8;
      bytes[2] = (*(long *)registers[i2cRequestAddr].address) >> 16;
      bytes[3] = (*(long *)registers[i2cRequestAddr].address) >> 24;
      Wire.write(bytes, 4);
      break;
  }

}

void receiveEvent(int byteLen) {

  byte receivedBytes[byteLen];   // Buffer to hold received bytes
  byte count = 0;                // Received byte counter

  while (count < byteLen and Wire.available()) {
    receivedBytes[count] = Wire.read();
    ++count;
  }

  if (count < 3)
    return;

  unsigned int registerAddress = receivedBytes[0] + receivedBytes[1] * 0x100;
  byte registerType = receivedBytes[2];
  bool *boolValue;
  byte *byteValue;
  int *intValue;
  long *longValue;

  // If we receive exactly 3 bytes it is meant as an address/type for upcoming request
  if (count == 3) {
    i2cRequestAddr = registerAddress;
  }

  // Bail out if type conflict
  if (registerType != registers[registerAddress].type) 
    return;

  // If we got this far - store the data  
  switch (registers[registerAddress].type) {
    case I2C_TYPE_BOOL:   
      boolValue = (bool *)registers[registerAddress].address;
      *boolValue = receivedBytes[3] & 1;
      break;
      
    case I2C_TYPE_BYTE:
      byteValue = (byte *)registers[registerAddress].address;
      *byteValue = receivedBytes[3];
      break;
      
    case I2C_TYPE_INT:
      intValue = (int *)registers[registerAddress].address;
      *intValue = receivedBytes[4] * 0x100 + receivedBytes[3];
      break;
      
    case I2C_TYPE_LONG:
      longValue = (long *)registers[registerAddress].address;
      *longValue = receivedBytes[6] * 0x1000000 + receivedBytes[5] * 0x10000 + receivedBytes[4] * 0x100 + receivedBytes[3];
      break;
      
    case I2C_TYPE_FLOAT:
      // haven't bothered yet but I really should
      break;     
  }

}

The sketch uses the standard Wire Library to communicate on the I²C bus and the OneWire Library and DallasTemperature Library to read temperatures from the sensors.

The sketch is prepared for 5 temperature sensors, t1, t2, t3, t4 and t5 and these values are exposes to the I²C bus using a struct and an array of pointers. The main loop reads all temperatures and then sleep 100 ms. Since I haven't gotten around to extending the I²C protocol I am using to support floating point, I multiply the temperature with 1000 and store it as an integer.

This basically represents the only programming necessary for this little example.

Configuring the I²C bridge

Bright Things UN Ltd. have created a standardized i2cbridge that will work with sketches following the approach illustrated in the previous section.

The i2cbridge is configured in /etc/config/i2cbridge. In our case, the bridge has been configured as follows:

root@pm2:~# cat /etc/config/i2cbridge 

config i2creg 'uptime'
        option bus 0
        option device 42
        option addr2 0
        option addr1 0
        option datatype 4
        option readonly 1

config i2creg 't1'
        option bus 0
        option device 42
        option addr2 0
        option addr1 1
        option datatype 3
        option readonly 1

config i2creg 't2'
        option bus 0
        option device 42
        option addr2 0
        option addr1 2
        option datatype 3
        option readonly 1

config i2creg 't3'
        option bus 0
        option device 42
        option addr2 0
        option addr1 3
        option datatype 3
        option readonly 1

config i2creg 't4'
        option bus 0
        option device 42
        option addr2 0
        option addr1 4
        option datatype 3
        option readonly 1

config i2creg 't5'
        option bus 0
        option device 42
        option addr2 0
        option addr1 5
        option datatype 3
        option readonly 1

The i2cbridge will make the values accessible through the standard Ionic messagebus ubus.

root@pm2:~# ubus -v list
'dhcp' @0d9f6b0f
        "ipv4leases":{}
        "ipv6leases":{}

 ....... snip .........

'i2cbridge.register.t1' @c9a539f3
        "put":{"value":"(unknown)"}
        "get":{}
'i2cbridge.register.t2' @788f0979
        "put":{"value":"(unknown)"}
        "get":{}
'i2cbridge.register.t3' @4ac80b4c
        "put":{"value":"(unknown)"}
        "get":{}
'i2cbridge.register.t4' @44f40bb8
        "put":{"value":"(unknown)"}
        "get":{}
'i2cbridge.register.uptime' @0515c5ac
        "put":{"value":"Integer"}
        "get":{}
 ....... snip .........

'system' @f3f002b7
        "board":{}
        "info":{}
        "upgrade":{}
        "watchdog":{"frequency":"Integer","timeout":"Integer","stop":"Boolean"}
        "signal":{"pid":"Integer","signum":"Integer"}
        "nandupgrade":{"path":"String"}

 ....... snip .........

{"config":"String","section":"String","type":"String","match":"Table","option":"String","options":"Array","ubus_rpc_session":"String"}
        "rename":{"config":"String","section":"String","option":"String","name":"String","ubus_rpc_session":"String"}
        "order":{"config":"String","sections":"Array","ubus_rpc_session":"String"}
        "changes":{"config":"String","ubus_rpc_session":"String"}
        "revert":{"config":"String","ubus_rpc_session":"String"}
        "commit":{"config":"String","ubus_rpc_session":"String"}
        "apply":{"rollback":"Boolean","timeout":"Integer","ubus_rpc_session":"String"}
        "confirm":{"ubus_rpc_session":"String"}
        "rollback":{"ubus_rpc_session":"String"}

Getting the value from one of the temperature sensors connected to the Atmel processor is as simple as:

root@pm2:~# ubus call i2cbridge.register.t1 get
{
        "value": "29875"
}

Remember the temperature was multiplied by 1000, so technically the value is in m°C (milli degrees Celcius - and yes I simply cannot work in an airconditioned environment and live in the tropics, so my office really is rather warm).

Configuring Mosquitto

Ionic is build with a MQTT message broker built-in. I have configured Mosquitto to run in bridge mode:

# =================================================================
# Bridges
# =================================================================
connection pm2
address 10.1.0.1
topic pm2/# both 2 "" gw/

This bit of configuration uses another MQTT bridge that I have running on my LAN.

Configuring the MQTT bridge

In order to make the temperatures available as MQTT messages, the mqttbridge is used. As for the i2cbridge this is configured using the standard UCI format in /etc/config/mqttbridge:

root@pm2:~# cat /etc/config/mqttbridge 

config object 'sysup'
        option ubus 'system'
        option method 'info'
        option variable 'uptime'
        option frequency '60'
        option adjust '0.016666666666666666'
        option unit 'm'
        option round '0'
        option description 'System Uptime'

config object 'atup'
        option ubus 'i2cbridge.register.uptime'
        option method 'get'
        option variable 'value'
        option frequency '60'
        option adjust '0.000016666666666666'
        option unit 'm'
        option round '0'
        option description 'Atmel Uptime'

config object 't1'
        option ubus 'i2cbridge.register.t1'
        option method 'get'
        option variable 'value'
        option adjust '0.001'
        option description 'Room Temp.'
        option round '1'
        option frequency '10'
        option unit '°C'

config object 't2'
        option ubus 'i2cbridge.register.t2'
        option method 'get'
        option variable 'value'
        option adjust '0.001'
        option description 'Enclosure Temp.'
        option round '1'
        option frequency '10'
        option unit '°C'

In short, the mqttbridge will periodically (every 10 seconds) read the temperature values (and a few other variables) from the ubus, multiply the values with 0.001, round of to 1 decimal and publish these values using MQTT.

The MQTT bridge can also be configured through Ionic's web-based GUI:

Pm2 MQTT Bridge LuCI.png

To test, I use the mosquitto_sub utility:

lth@ncpws04:~$ mosquitto_sub -h mqtt.bright-things.net -t "bt/#"
{"description":"Atmel Uptime","value":1155,"unit":"m"}
{"description":"Room Temp.","value":29.9,"unit":"°C"}
{"description":"Room Temp.","value":30,"unit":"°C"}
{"description":"Enclosure Temp.","value":29.3,"unit":"°C"}
{"description":"Room Temp.","value":30,"unit":"°C"}
{"description":"Room Temp.","value":30,"unit":"°C"}
{"description":"Enclosure Temp.","value":29.3,"unit":"°C"}

Extremely simple Web example

I have implemented (the actual details will follow in another example) a simple web-based subscription page that show these values live. It is available at http://mqtt.bright-things.com and it will look like this:

Webmqtt.png