RoadTest: Sub-1 GHz Sensor to Cloud IoT Gateway
Author: abrain
Creation date:
Evaluation Type: Development Boards & Tools
Did you receive all parts the manufacturer stated would be included in the package?: True
What other parts do you consider comparable to this product?: STMicroelectronics Discovery Kit for IoT node
What were the biggest problems encountered?: The most interesting problem was a discrepancy between data structures from the sensor node to the gateway, other than that just finding enough time to do what I’d planned.
Detailed Review:
The element14 SimpleLink Sub-1GHz Sensor to Cloudelement14 SimpleLink Sub-1GHz Sensor to Cloud kit proclaims to be an “End-to-end solution for LPWAN enabling cloud connectivity for sending and receiving sensor data over a long range Sub-1GHz network”.
I'd set myself some reasonably high expectations of what I could achieve in this RoadTest, with a plan that added functionality in incremental steps:
There were about 8 weeks to do the project, although that did span Christmas, with a week at the end to write up and tidy up loose ends. With only 6 major steps in the plan, it ought to be manageable....
First impressions are good; a smart protective box contains two TI SimpleLink CC1350 LaunchPadsTI SimpleLink CC1350 LaunchPads, and a BeagleBone BlackBeagleBone Black (BBB) with wireless cape, something I’d not used before. Also included were a 5V PSU, USB cables, and a pre-flashed microSD card for the BBB.
I got onto the TI website and had a quick read of their Getting Started Guide, and by the time I’d got to page 3 I had the hardware hooked up and my iPad connected to the BBB acting as a Local Gateway.
There’s a critical step where you need to Open the network to new devices, it’s fine when you read that the first time and do it, but when you’re in a hurry connecting later it’s something I forgot to do a few times.
I know you’d expect their demo to work out of the box, this is TI and element14 after all, but it’s still nice when it’s straightforward and well explained.
Before moving on to the next step, I thought I’d look at the long range and power consumption of the demo.
The signal strength received at the BBB end of the system is reported on the web page, so armed with iPad in one hand and the CC1350 SimpleLink board that was the sensor in the other, I moved away from the test setup finding some sockets at convenient distances for a plug-in USB power supply – it would have been good to have a battery powered board ready for this, but that’ll have to wait until later. Previous experience has highlighted the need to use TI’s BoosterPack compatibility checker, and the Fuel Gauge Mk II BoosterPack isn’t compatible, so no quick and easy solution there I’m afraid.
I got the following measurements as a guideline, with the development set up on the 2nd floor:
That’s not exactly what I’d call long range, more than a BlueTooth Low Energy connection for sure, but only about 10m. It hadn’t lost the connection, but for me long range would be 100m minimum, and 1km would be good. I’ll come back to that range test later when I can battery power the device, and we’ll see how far we can go before the device stops responding.
Powering the sensor board of a 3.3V bench power supply, and removing all the jumpers that power the USB interface, the board used about 10mA with both LEDs on, and about 0.1mA with both LEDs off.
That’s a pretty good starting point, but I dropped the supply to just 2.4V and the consumption dropped to 4mA with both LEDs on, and about 0.03mA with both off. Some quick sums suggest a pair of 2400mAh AA batteries could last 13 days if we made sure at least one of the LEDs flashed on a 50% duty cycle, and we could be running for over a year without the LEDs on at all, so the low power claim on the box seems a fair one already.
Next up I connected the BBB to my WiFi network, after making sure that the SSID wasn’t hidden, and ran the Network Gateway option. Selecting the IBM QuickStart option, so I wouldn’t have to wait and sign up to anything seemed the quickest and easiest approach, and within a few moments I had a page where I could select one of the measurements being made and get a graph on my iPad.
Note that the data isn’t persistent, so the graph only shows the data collected since you started plotting that graph.
Emailing myself the link confirmed you can access it off 2 devices simultaneously. This QuickStart cloud gateway doesn’t offer the option of controlling the sensor remotely, for that you need to sign up and register for one of the other options, but I wanted to take things in a slightly different direction.
Now I was happy with the demo, it was time to get the SDK installed and extend the functionality of the demo.
First stop was TI’s website, where I found that unlike the SDK for the CC2640R2F SimpleLInk board I used in a previous RoadTest, the CC13x0 SimpleLink™ Sub-1 GHz
Software Development Kit also had an installer for Mac OS-X. That’s going to have to wait for a different time, though, as I went for the Windows version to build on that existing Code Composer Studio installation.
Having CCS already installed saved a fair bit of time, and being familiar with the IDE from a previous project definitely helped getting the Sensor example code installed from the \Examples\Dev tools\CC 1350 LaunchPad\TI 15.4 stack folder.
I connected the CC1350 board marked sensor to a USB port and ran the code in debug mode, and it made a connection to the gateway and started reporting data - all was set now ready to start adding some additional sensor measurements!
I’d already got a Sensors BoosterPackSensors BoosterPack, which is compatible with the CC1350 board, so I powered down the sensor node and connected the two up. The Quick Start guide for the Sensors Booster pack has the following diagram showing the key I2C addresses for the sensors we’re interested in.
We’re interested in the following two sensors:
The ambient light level is measured by an OPT3001 device on address 0x47.
I found some great info from the following two links:
https://e2e.ti.com/support/wireless_connectivity/proprietary_sub_1_ghz_simpliciti/f/156/t/560486
http://sunmaysky.blogspot.co.uk/2016/03/basic-example-to-use-opt3001-on-cc2650.html
I opted to write some I2C code directly in the routine that handles a polling message from the gateway, processSensorMsgEvt In the sensors.c file.
I started off by adding some includes, both for the I2C and so I could print to the console, I also realised I’d need to do some maths on converting the lux readings, so in all I added the following:
#include #include #include "board.h" #include "board_lcd.h" #include
The first step in the real code to process the sensor message is configuring the I2C
I2C_Handle handle; I2C_Params params; I2C_Transaction i2cTrans; uint8_t rxBuf[32]; // Receive buffer uint8_t txBuf[32]; // Transmit buffer I2C_init(); // Configure I2C parameters. I2C_Params_init(¶ms); // Initialize master I2C transaction structure i2cTrans.writeCount = 0; i2cTrans.writeBuf = txBuf; i2cTrans.readCount = 10; i2cTrans.readBuf = rxBuf; i2cTrans.slaveAddress = 0x47; // 0x47 OPT3001 Ambient light // 0x77 BME280 Environmental // Open I2C handle = I2C_open(CC13X0_LAUNCHXL_I2C0, ¶ms); if (handle == NULL) { System_printf("Error Initializing I2C"); }
Now the I2C was all set up, I could read the ambient light value and convert it to Lux as described in the data sheet
//Config OPT3001 txBuf[0] = 0x01; txBuf[1] = 0xC4; txBuf[2] = 0x10; i2cTrans.writeCount = 3; i2cTrans.readCount = 0; // Do I2C transfer receive if (I2C_transfer(handle, &i2cTrans)){ // System_printf("OPT3001 configure OK!"); } else { System_printf("OPT3001 Configure fail!"); } //Read OPT3001 txBuf[0] = 0x00; i2cTrans.writeCount = 1; i2cTrans.readCount = 2; // Do I2C transfer receive if (I2C_transfer(handle, &i2cTrans)){ // LCD_WRITE_STRING("OPT3001 read OK!",8); int result = (rxBuf[0] << 8 ) | (rxBuf[1]); uint16_t e, m; float lux; m = result & 0x0FFF; e = (result & 0xF000) >> 12; lux= (float)m * (0.01 * exp2(e)); System_printf("Light level (Lux): %d\r\n",(int)lux); // finally we also need to set the sensor value lightSensor.rawData = (int16_t)lux; } else { System_printf("OPT3001 read fail!"); }
A fairly basic test got me readings of around 600 lux in daylight, and just 4 when I covered the sensor! I could print that out on the debug window, and I’d discovered that the code already contained the lightSensor variable ready to for this, but how to communicate that to the gateway?
Luckily, the TI example code anticipated adding some of these sensors, so all I had to do was define LIGHT_SENSOR, and the place to do that is as a predefined symbol under the Project\Show Build Settings menu.
You can see I’ve added the HUMIDITY_SENSOR definition too, in readiness for what comes next.
I added some more I2C code to configure the BME280 sensor
// Now we make a start on reading environmental // data from the BME280 i2cTrans.slaveAddress = 0x77; // write the configuration to the device txBuf[0] = 0xF2; txBuf[1] = 0x01; i2cTrans.writeCount = 2; i2cTrans.readCount = 0; // Do I2C transfer receive if (I2C_transfer(handle, &i2cTrans)){ // System_printf("BME280 ctrl_hum OK!"); } else { System_printf("BME280 ctrl_hum fail!"); } txBuf[0] = 0xF4; txBuf[1] = 0x25; i2cTrans.writeCount = 2; i2cTrans.readCount = 0; // Do I2C transfer receive if (I2C_transfer(handle, &i2cTrans)){ // System_printf("BME280 ctrl_meas OK!"); } else { System_printf("BME280 ctrl_meas fail!"); }
Having read the datasheet, there’s some pretty complicated compensation you can do for the BME280, so I added some type definitions so I could use some of the example code more easily:
typedef signed long BME280_S32_t; typedef unsigned long BME280_U32_t; typedef signed long long BME280_S64_t;
Next step was to define all the required compensation variables
// initialise all the compensation values uint16_t dig_T1; int16_t dig_T2; int16_t dig_T3; uint16_t dig_P1; int16_t dig_P2; int16_t dig_P3; int16_t dig_P4; int16_t dig_P5; int16_t dig_P6; int16_t dig_P7; int16_t dig_P8; int16_t dig_P9; uint8_t dig_H1; int16_t dig_H2; uint8_t dig_H3; int16_t dig_H4; int16_t dig_H5; int8_t dig_H6;
I then read out all the compensation values (every time, rather than just once, but it kept all the code together, and was quick, if a little dirty to implement).
// read out the compensation values txBuf[0] = 0x88; i2cTrans.writeCount = 1; i2cTrans.readCount = 6; // Do I2C transfer receive if (I2C_transfer(handle, &i2cTrans)){ dig_T1 = (rxBuf[1] << 8 ) | (rxBuf[0]); dig_T2 = ((int16_t)rxBuf[3] << 8 ) | ((int16_t)rxBuf[2]); dig_T3 = ((int16_t)rxBuf[5] << 8 ) | ((int16_t)rxBuf[4]); } else { System_printf("BME280 read temp comp failed!"); } txBuf[0] = 0x8E; i2cTrans.writeCount = 1; i2cTrans.readCount = 20; // Do I2C transfer receive if (I2C_transfer(handle, &i2cTrans)){ dig_P1 = (rxBuf[1] << 8 ) | (rxBuf[0]); dig_P2 = (int16_t)((rxBuf[3] << 8 ) | rxBuf[2]); dig_P3 = (int16_t)((rxBuf[5] << 8 ) | rxBuf[4]); dig_P4 = (int16_t)((rxBuf[7] << 8 ) | rxBuf[6]); dig_P5 = (int16_t)((rxBuf[9] << 8 ) | rxBuf[8]); dig_P6 = (int16_t)((rxBuf[11] << 8 ) | rxBuf[10]); dig_P7 = (int16_t)((rxBuf[13] << 8 ) | rxBuf[12]); dig_P8 = (int16_t)((rxBuf[15] << 8 ) | rxBuf[14]); dig_P9 = (int16_t)((rxBuf[17] << 8 ) | rxBuf[16]); dig_H1 = rxBuf[19]; } else { System_printf("BME280 read press comp failed!"); } txBuf[0] = 0xE1; i2cTrans.writeCount = 1; i2cTrans.readCount = 7; // Do I2C transfer receive if (I2C_transfer(handle, &i2cTrans)){ dig_H2 = (int16_t)((rxBuf[1] << 8 ) | (rxBuf[0])); dig_H3 = rxBuf[2]; dig_H4 = (int16_t)((rxBuf[3] << 4 ) | (rxBuf[4] & 0x0F)); dig_H5 = (int16_t)((rxBuf[5] << 4 ) | ((rxBuf[4] >> 4 ) & 0x0F)); dig_H6 = (int8_t)rxBuf[6]; } else { System_printf("BME280 read hum comp failed!"); }
Once we’d got all that, it was time to read some proper values:
// read out the data txBuf[0] = 0xF7; i2cTrans.writeCount = 1; i2cTrans.readCount = 8; // Do I2C transfer receive if (I2C_transfer(handle, &i2cTrans)){ // System_printf("BME280 data read OK!"); } else { System_printf("BME280 data read failed!"); } // compensate the measurements... // converted the following from a function to inline code // taken from // https://github.com/BoschSensortec/BME280_driver/blob/master/bme280.c // Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC. // t_fine carries fine temperature as global value int32_t t_fine; uint32_t adc_T = ( (uint32_t)rxBuf[3] << 12 ) | ( (uint32_t)rxBuf[4] << 4 ) | ( (uint32_t)rxBuf[5] >> 4 ); { int32_t var1; int32_t var2; int32_t temperature; int32_t temperature_min = -4000; int32_t temperature_max = 8500; var1 = (int32_t)((adc_T / 8) - ((int32_t)dig_T1 * 2)); var1 = (var1 * ((int32_t)dig_T2)) / 2048; var2 = (int32_t)((adc_T / 16) - ((int32_t)dig_T1)); var2 = (((var2 * var2) / 4096) * ((int32_t)dig_T3)) / 16384; t_fine = var1 + var2; temperature = (t_fine * 5 + 128) / 256; if (temperature < temperature_min) temperature = temperature_min; else if (temperature > temperature_max) temperature = temperature_max; tempSensor.ambienceTemp = temperature / 100; tempSensor.objectTemp = tempSensor.ambienceTemp; System_printf("Temperature (degC): %d\r\n", temperature/100); } // This is an inline version of the compensate // pressure code uint32_t adc_P = ( (uint32_t)rxBuf[0] << 12 ) | ( (uint32_t)rxBuf[1] << 4 ) | ( (uint32_t)rxBuf[2] >> 4 ); { int32_t var1; int32_t var2; int32_t var3; int32_t var4; uint32_t var5; uint32_t pressure; uint32_t pressure_min = 30000; uint32_t pressure_max = 110000; var1 = (((int32_t) t_fine) / 2) - (int32_t) 64000; var2 = (((var1 / 4) * (var1 / 4)) / 2048) * ((int32_t) dig_P6); var2 = var2 + ((var1 * ((int32_t) dig_P5)) * 2); var2 = (var2 / 4) + (((int32_t) dig_P4) * 65536); var3 = (dig_P3 * (((var1 / 4) * (var1 / 4)) / 8192)) / 8; var4 = (((int32_t) dig_P2) * var1) / 2; var1 = (var3 + var4) / 262144; var1 = (((32768 + var1)) * ((int32_t) dig_P1)) / 32768; /* avoid exception caused by division by zero */ if (var1) { var5 = (uint32_t) ((uint32_t) 1048576) - adc_P; pressure = ((uint32_t) (var5 - (uint32_t) (var2 / 4096))) * 3125; if (pressure < 0x80000000) pressure = (pressure << 1) / ((uint32_t) var1); else pressure = (pressure / (uint32_t) var1) * 2; var1 = (((int32_t) dig_P9) * ((int32_t) (((pressure / 8) * (pressure / 8)) / 8192))) / 4096; var2 = (((int32_t) (pressure / 4)) * ((int32_t) dig_P8)) / 8192; pressure = (uint32_t) ((int32_t) pressure + ((var1 + var2 + dig_P7) / 16)); if (pressure < pressure_min) pressure = pressure_min; else if (pressure > pressure_max) pressure = pressure_max; } else { pressure = pressure_min; } System_printf("Pressure (mBar): %d\r\n", pressure/100); } // This is an inline version of the compensate // humidity code uint32_t adc_H = ( (uint32_t)rxBuf[6] << 8 ) | ( (uint32_t)rxBuf[7] ); { int32_t var1; int32_t var2; int32_t var3; int32_t var4; int32_t var5; uint32_t humidity; uint32_t humidity_max = 100000; var1 = t_fine - ((int32_t) 76800); var2 = (int32_t) (adc_H * 16384); var3 = (int32_t) (((int32_t) dig_H4) * 1048576); var4 = ((int32_t) dig_H5) * var1; var5 = (((var2 - var3) - var4) + (int32_t) 16384) / 32768; var2 = (var1 * ((int32_t) dig_H6)) / 1024; var3 = (var1 * ((int32_t) dig_H3)) / 2048; var4 = ((var2 * (var3 + (int32_t) 32768)) / 1024) + (int32_t) 2097152; var2 = ((var4 * ((int32_t) dig_H2)) + 8192) / 16384; var3 = var5 * var2; var4 = ((var3 / 32768) * (var3 / 32768)) / 128; var5 = var3 - ((var4 * ((int32_t) dig_H1)) / 16); var5 = (var5 < 0 ? 0 : var5); var5 = (var5 > 419430400 ? 419430400 : var5); humidity = (uint32_t) (var5 / 4096); if (humidity > humidity_max) humidity = humidity_max; humiditySensor.humidity = humidity / 1024; System_printf("Humidity (\%): %d\r\n", humidity/1024); }
All that remains is to remember to close the I2C device
/* Deinitialized I2C */ I2C_close(handle);
The end result is shown on the gateway:
The major limitation with this "wireless" device right now was that it wasn't actually wireless, it needed a connection for power!
I put together a simple little board to connect a battery directly onto the 3V3 and 0V pins on the CC1350 board headers, along with a potential divider made from two 10k resistors onto the A1 analogue input.
After getting lucky with the existing definitions in the sensor code for the humidity sensor, I wondered if I'd also get lucky with the battery voltage? There was nothing in the sensors example code in the CC1350 SDK, so it didn't look good.
However, making a telnet connection on to the BBB (note that all you need is the username root, no password as it's configured out of the box - I'll come on to security later!), in the \linux_s2c\example\collector folder, I used grep to look for anything defined in the smsgs files related to a battery sensor:
$
$
$ telnet 192.168.1.34
Trying 192.168.1.34...
Connected to 192.168.1.34.
Escape character is '^]'.
_____ _____ _ _
| _ |___ ___ ___ ___ | _ |___ ___ |_|___ ___| |_
| | _| .'| . | . | | __| _| . | | | -_| _| _|
|__|__|_| |__,|_ |___| |__| |_| |___|_| |___|___|_|
|___| |___|
Arago Project http://arago-project.org SensorToCloud
Arago 2016.12 SensorToCloud
SensorToCloud login: root
Last login: Sat Jan 27 15:04:07 UTC 2018 on pts/0
root@SensorToCloud:~# grep -n -C 3 battery linux_s2c/example/collector/smsgs*
188-#define SMSGS_SENSOR_PRESSURE_LEN 8
189-/*! Length of the motionSensor portion of the sensor data message */
190-#define SMSGS_SENSOR_MOTION_LEN 1
191:/*! Length of the batteryVoltageSensor portion of the sensor data message */
192-#define SMSGS_SENSOR_BATTERY_LEN 4
193-/*! Length of the hallEffectSensor portion of the sensor data message */
194-#define SMSGS_SENSOR_HALL_EFFECT_LEN 2
--
258- /*! Motion Sensor */
259- Smsgs_dataFields_motionSensor = 0x0040,
260- /*! Battery Sensor */
261: Smsgs_dataFields_batterySensor = 0x0080,
262- /*! Door and Window Hall Effect Sensor */
263- Smsgs_dataFields_hallEffectSensor = 0x0100,
264- /*! Fan Sensor */
--
422-/*!
423- Battery Voltage Sensor Field
424- */
425:typedef struct _Smsgs_batterysensorfield_t
426-{
427: /* battery voltage value */
428- uint32_t voltageValue;
429:}Smsgs_batterySensorField_t;
430-
431-/*!
432- Hall Effect Sensor Field
--
582- Smsgs_motionSensorField_t motionSensor;
583- /*!
584- Battery Voltage Sensor field - valid only if
585: Smsgs_dataFields_batterySensor is set in frameControl.
586- */
587: Smsgs_batterySensorField_t batterySensor;
588- /*!
589- Hall Effect Sensor Field - valid only if
590- Smsgs_dataFields_hallEffectSensor is set in frameControl.
root@SensorToCloud:~#
So it looks like I could simply extend the data being sent by the CC1350 to the gateway, and it would already be configured to receive and process the battery voltage!
The key tip for getting the ADC working is making sure the include files for the CC1350 actually include the necessary structures for the ADC. I copied the CC1350_LAUNCHXL.h and CC1350_LAUNCHXL.c files from the CC1350 SDK \examples\rtos\CC1350_LAUNCHXL\drivers\adcsinglechannel folder to the project's /Application/LaunchPad folder.
In the sensor.c file we need to add an include for the ADC library code, and define how many samples we want to average our battery voltage readings over:
#include #define ADC_SAMPLE_COUNT (10)
Below the declaration of the humidity sensor, we need to add:
/* Battery Volts field - valid only if Smsgs_dataFields_battVolts is set in frameControl. */ STATIC Smsgs_batterysensorfield_t batterySensor = { 0 };
In the sensor init method we also need to add:
#if defined(BATTERY_SENSOR) configSettings.frameControl |= Smsgs_dataFields_batterySensor; #endif
In the dataCnfCB method, we needed to comment out the following code:
/* Sensor_msgStats.worstCaseE2EDelay = (Sensor_msgStats.worstCaseE2EDelay > endToEndDelay) ? Sensor_msgStats.worstCaseE2EDelay:endToEndDelay; Sensor_msgStats.avgE2EDelay = (((uint32_t)Sensor_msgStats.avgE2EDelay * (Sensor_msgStats.msgsSent - 1)) + endToEndDelay)/ Sensor_msgStats.msgsSent; */
This was essential, as otherwise the data structure sent from the CC1350 didn't match that expected by the Collector software on the BBB, and the battery voltage reading wasn't correctly read at all.
We then added the following to the processSensorMsgEvt method, below the I2C code added earlier:
uint16_t i; ADC_Handle adc; ADC_Params adcParams; int_fast16_t res; /* ADC conversion result */ uint16_t adcValue1; uint32_t adcValue1MicroVolt; uint16_t adcValues; ADC_init(); ADC_Params_init(&adcParams); adc = ADC_open(CC1350_LAUNCHXL_ADC1, &adcParams); if (adc == NULL) { System_printf("Error initializing ADC channel 1\n"); } else { adcValues = 0; adcValue1MicroVolt = 0; for (i = 0; i < ADC_SAMPLE_COUNT; i++) { res = ADC_convert(adc, &adcValue1); if (res == ADC_STATUS_SUCCESS) { adcValue1MicroVolt += ADC_convertRawToMicroVolts(adc, adcValue1); adcValues++; } else { System_printf("ADC channel 1 convert failed (%d)\n", i); } } if ( adcValues > 0 ) { System_printf("Battery volts %d (mV)\r\n", adcValue1MicroVolt / ( adcValues * 500 ) ); batterySensor.voltageValue = adcValue1MicroVolt / ( adcValues * 500); } ADC_close(adc); }
Note that we convert the ADC reading from volts to millivolts, and double the reading to account for the potential divider in a single step by only dividing by 500.
A little further down the same method we copied the measured battery voltage into the message containing the sensor data:
if(sensor.frameControl & Smsgs_dataFields_batterySensor) { memcpy(&sensor.batterySensor, &batterySensor, sizeof(Smsgs_batterysensorfield_t)); }
Having added the extra data, we had to amend the SendSenorMessage method to account for the extra data length:
if(pMsg->frameControl & Smsgs_dataFields_batterySensor) { len += SMSGS_SENSOR_BATTERY_LEN; }
In that same SendSensorMessage method, we also had to comment out the following so that the data structure matched on both the CC1350 Sensor and the Collector code on the BBB:
/* pBuf = Util_bufferUint16(pBuf, pMsg->msgStats.numBroadcastMsgRcvd); pBuf = Util_bufferUint16(pBuf, pMsg->msgStats.numBroadcastMsglost); pBuf = Util_bufferUint16(pBuf, pMsg->msgStats.avgE2EDelay); pBuf = Util_bufferUint16(pBuf, pMsg->msgStats.worstCaseE2EDelay); */
As the final step before actually sending the message, we had to populate the battery data field:
if(pMsg->frameControl & Smsgs_dataFields_batterySensor) { pBuf = Util_bufferUint32(pBuf, pMsg->batterySensor.voltageValue); } ret = Sensor_sendMsg(Smsgs_cmdIds_sensorData, pDstAddr, true, len, pMsgBuf);
Within the processBroadcastCtrlMsg method, we need to comment out the following:
// Sensor_msgStats.numBroadcastMsgRcvd++;
and
/* Sensor_msgStats.numBroadcastMsglost += ((broadcastMsgId - lastRcvdBroadcastMsgId) -1); */
The final change to the sensor.c file was in the validateFrameControl method, where we added the following:
#if defined(BATTERY_SENSOR) if(frameControl & Smsgs_dataFields_batterySensor) { newFrameControl |= Smsgs_dataFields_batterySensor; } #endif
To support these, we had to define BATTERY_SENSOR as a predefined symbol under the Project\Show Build Settings menu as before with the HUMIDITY_SENSOR.
We also had to add the following as the last entry in the Smsgs_dataFields_t structure in the smsgs.h file:
/*! Battery voltage */ Smsgs_dataFields_batterySensor = 0x0080,
Just after the humidity sensor field also defined the following structure:
/*! Battery Volts Field */ typedef struct _Smsgs_batterysensorfield_t { /*! Raw battery voltage read from an ADC channel (in mV) */ uint32_t voltageValue; } Smsgs_batterysensorfield_t;
We had to remove the following from the Smsgs_msgStatsField_t structure to make sure that the data from the CC1350 sensor matched that expected by the gateway on the BBB.
/*! Number of broadcast messages received from the collector */ // uint16_t numBroadcastMsgRcvd; /*! Number of broadcast messages missed from the collector */ // uint16_t numBroadcastMsglost; /*! Average end to end delay */ // uint16_t avgE2EDelay; /*! Worst Case end to end delay */ // uint16_t worstCaseE2EDelay;
Finally we added the following to the end of the Smsgs_sensorMsg_t structure, as this matched that already defined in the gateway code.
/*! Battery volts field - valid only if Smsgs_dataFields_battVolts is set in frameControl. */ Smsgs_batterysensorfield_t batterySensor;
Adding 3 AA batteries, we got the following displayed on the local gateway:
I felt that was quite a result, and made the system properly portable, so we could get on and test the range of the wireless.
Now we had a truly portable device, we could take this RoadTest on the road!
The configuration of TI's IEEE 802.15.4e/g standards based sub-1GHz radio includes settings for frequency and range, allowing the user to choose from 863MHz (European) or 915MHz (US), and standard or long range modes. We're in the UK, so chose the Long Range European mode for this test.
Opening up the config.h file for the sensor project in Code Composer Studio, I changed the configuration from:
/*! Setting for Phy ID */ #define CONFIG_PHY_ID (APIMAC_STD_US_915_PHY_1)
to
/*! Setting for Phy ID */ #define CONFIG_PHY_ID (APIMAC_GENERIC_ETSI_LRM_863_PHY_131)
Similarly, I made a telnet connection to the BBB and changed the config-phy-id setting in the /linux_s2c/example/collector/collector.cfg file as follows:
; PHY Configuration ID, set the ID to one of the below ID's
; e.g. 3 for 863 MHz
; PHY Description : ID
; - 915MHz US Frequency band operating mode #1: 1
; - 863MHz ETSI Frequency band operating mode #3: 3
; - 433MHz China Frequency band operating mode #3: 128
;
; - 915MHz US Frequency band Long Range operating mode: 129
; - 433MHz China Frequency band Long Range operating mode: 130
; - 863MHz ETSI Frequency band Long Range operating mode: 132
// changed from 1 for ETSI Long Range
config-phy-id = 132
We left the gateway plugged in on our 1st floor office, and headed outside and down the road.
In each direction, we got about 90m before other buildings got in the way and we lost the connection. When we did so, we had to come back to the front door and wait a little while before connection re-established. However, when we repeated the test and stayed within that 90m range, we didn't lose the connection at all.
We could get recorded rssi information from the BBB using the grep rssi linux_s2c/example/commissioner/commissioner.log command, and used it to plot the following graph:
We went out of the office and turned right, walked to the corner, walked back past the office off to the left, then came back to the office. You can see we started at about -50dB, got down to -80dB to the right, but over -100dB and still collecting data as we went off to the left. Note we had a lower -60dB as we walked back past compared to when we started and finished, I'm not too sure why but maybe we were holding the unit differently. You can see that the protocol is pretty robust thought, and the range is pretty useful in this mode.
A question asked by kas.lewis on the original RoadTest page asked about
possible underwater usage of these radios or from above to underwater and what range you get in each case..
That sounded a good thing to try, so we wrapped the boards and battery box in some self-seal plastic bags, put them back in the plastic enclosure we had been using, and lowered the unit into a tank of water for a good few hours... We were right to use the bags, as some water did get in, but through at least 30cm of water on all sides we got a signal and data transferred, although the received signal strength was pretty low at -100dBm.
The fairly ad-hoc but encouraging results suggest at least a limited underwater capability - a more sophisticated test setup would be needed for anything more and is beyond the scope of this RoadTest I'm afraid!
Now I'd got a battery powered device, in a box that survived being underwater, I dried the kit and the box out, fitted some clear tubing to act as a spacer material to provide an air gap around the lid, and parked the unit outside the office to see how it would get on being deployed remotely.
I've not touched the device in just over a week now, so the graphs below show just over 7 days of data collected at 10 second intervals:
You can see the 7 bumps in measured light levels, with low bumps on dark, overcast (even snowing!) days, and spikes on the sunnier days. The humidity falls significantly with sunlight, and the temperature measured inside the enclosure reaches over 30 degrees on the sunniest days, showing the effects of solar heating even on a clear enclosure on winter days in January!
The battery voltage is falling slowly, and has a long way to go before the unit would stop working. Coupled with the high light levels on the sunny days, as part of the next steps I'm tempted to deploy a small solar panel with a current limiting circuit to trickle charge the NiMH AA cells I used.
If I actually made some effort to save power, such as longer periods between communications (just 10 seconds at the moment!) and minimising the time the LEDs are switched on for, we could see a long period of operation off just battery power alone, supporting the claims of long life on even a single coin cell being possible. Note that I had removed all teh jumpers between the USB to serial interface and the actual CC1350, so I had made a token energy saving effort!
Adding a second device to the network was, as you'd hope, pretty straightforward.
I bought a second CC1350 LaunchpadCC1350 Launchpad, and a Sensors BoosterPackSensors BoosterPack, plugged them together and connected them to my MacBook. I told Parallels to use it with Windows then Code Composer Studio (CCS) complained that the CC1350 needed a firmware update. I pressed OK to continue with that. I got the usual warning about not disconnecting, so pressed on, and the next thing I knew CCS was complaining it couldn't connect to the device.
Parallels could see the XDS110 device, so I knew it was sort of ok somehow, but CCS really wasn't happy, and I had the feeling I'd bricked a brand new CC1350. If you look on TI's forum, you can see a lot of questions about bricking devices.... However, after a few attempts, I plugged the CC1350 into the USB cable whilst holding down the reset button, and this time the firmware update tool found and set the device into firmware update mode, and I got it sorted, so that might be worth trying if you're in a similar situation.
Once I got through that, I simply programmed the device with my code, pressed the button on the local gateway web page to make it open for new devices, and a short time later a second device appeared!
I hadn't made up a second battery power board, so the ADC pin on the second device was left floating. Other than that, it was clearly darker inside the office than outside, but all looked good, and you can't get much easier than that for growing a network.
One of the things I really wanted to do was to be able to get this data to my own MQTT broker, as it's all well and good relying on nice front ends other people have done, but I wanted to understand more of what was going on, and I figured the best way to do that was to try it for myself.
MQTT seems a pretty established standard for IoT communications, it's been around a long time and has a lot of clients for it now, but this is my first time trying it on a BBB. I found inspiration from a number of sources, including:
https://www.seeed.cc/MQTT-bright-flashing-lights-based-on-Seeed-BeagleBone-Green-series-p-1855.html
https://stackoverflow.com/questions/36443406/connecting-nodejs-and-cloud-mqtt
http://myroboticadventure.blogspot.co.uk/2014/06/using-javascript-with-bonescript-to.html
What follows is how I did it too.
First step was to telnet onto the BBB and run the following commands:
$
$
$ telnet 192.168.1.34
Trying 192.168.1.34...
Connected to 192.168.1.34.
Escape character is '^]'.
_____ _____ _ _
| _ |___ ___ ___ ___ | _ |___ ___ |_|___ ___| |_
| | _| .'| . | . | | __| _| . | | | -_| _| _|
|__|__|_| |__,|_ |___| |__| |_| |___|_| |___|___|_|
|___| |___|
Arago Project http://arago-project.org SensorToCloud
Arago 2016.12 SensorToCloud
SensorToCloud login: root
Last login: Sat Jan 27 21:47:41 UTC 2018 on pts/1
root@SensorToCloud:~# npm install mqtt -g
/usr/bin/mqtt_pub -> /usr/lib/node_modules/mqtt/bin/pub.js
/usr/bin/mqtt_sub -> /usr/lib/node_modules/mqtt/bin/sub.js
/usr/bin/mqtt -> /usr/lib/node_modules/mqtt/mqtt.js
mqtt@2.12.0 /usr/lib/node_modules/mqtt
├── inherits@2.0.3
├── reinterval@1.1.0
├── xtend@4.0.1
├── minimist@1.2.0
├── commist@1.0.0 (leven@1.0.2)
├── split2@2.1.1 (through2@2.0.3)
├── concat-stream@1.6.0 (typedarray@0.0.6)
├── mqtt-packet@5.4.0 (process-nextick-args@1.0.7, safe-buffer@5.1.1, bl@1.2.1)
├── readable-stream@2.3.3 (string_decoder@1.0.3, util-deprecate@1.0.2, process-nextick-args@1.0.7, core-util-is@1.0.2, safe-buffer@5.1.1, isarray@1.0.0)
├── end-of-stream@1.4.0 (once@1.4.0)
├── pump@1.0.2 (once@1.4.0)
├── websocket-stream@5.0.1 (safe-buffer@5.1.1, ws@3.1.0, duplexify@3.5.1)
└── help-me@1.1.0 (through2@2.0.3, callback-stream@1.1.0, glob-stream@6.1.0)
root@SensorToCloud:~#
Next step was to create a file test_mqtt.js with the following contents, and FTP that onto the BBB in the /home/root folder:
var mqtt = require('mqtt'); // Choose your own client id! var options = { clientId: 'xxxxxxxx', keepalive: 60, reconnectPeriod: 1000, clean: true, }; var client = mqtt.connect('wss://iot.eclipse.org:443//ws', options); client.on('connect', function () { client.subscribe('Data'); client.publish('Data', 'Hello mqtt'); }); client.on('message', function (topic, message) { console.log(message.toString()); // client.end(); });
With the exception of the client id and server details, this is pretty much taken directly from the example code in the /usr/lib/node_modules/mqtt/README.md file.
Next step was to make use of the Eclipse Paho web MQTT client - http://www.eclipse.org/paho/clients/js/utility/?
to configure the client with the right client ID and connect to the broker as in the screenshot below.
Make sure to change the topic to Data and to press the subscribe button. Now we can run the test code on the BBB, as follows:
root@SensorToCloud:~# node test_mqtt.js
Hello mqtt
You should also see the message appear on the web page:
Check two way communication by changing the Publish Message Topic to Data, entering a message and pressing the Publish button.
Not only is the message shown in the web client:
but also on the BBB console:
root@SensorToCloud:~# node test_mqtt.js
Hello mqtt
Hello, world!
In many ways, this is distinctly not good! We've now opened our BBB up to pretty much the entire planet, where all that stands between operation and a Denial of Service attack on our IoT client is being able to guess that client id.
We've made a start by using the secure web sockets connection to port 443 on the broker when we specified wss://iot.eclipse.org:443, not using a publicly hosted broker and using our own certificates would be good progress, but I'm going to focus on showing this working for now, and not on how to make it secure, even though that's vitally important and something we all need to be mindful of when developing IoT applications.
Unfortunately, installing that mqtt package breaks the iot-gateway, so we need to update the packages used by that by using more npm commands, as follows:
root@SensorToCloud:~# cd linux_s2c/example/iot-gateway/
root@SensorToCloud:~/linux_s2c/example/iot-gateway# npm install
All that remains now is to publish our environmental data to this MQTT broker....
That's pretty much this RoadTest done, I didn’t quite make it working out a nice way for how to get the data to the MQTT broker, I need to spend a little more time working through the gateway code, so that’ll have to wait for a separate post I’m afraid.
i also want to link or write something about improving the security of the setup. As shipped, it needs to be open so that it’s easy to connect to and get going, but I think as it’s an IoT kit, and we all really need the IoT to be trustable, it would be worth just getting down those simple steps that could be taken to make it all more secure - setting a root password, for instance, and passwording or disabling the WiFi access if you don’t need it, let alone setting up certificates for the MQTT.
As for functionality, I’m probably going to add a solar panel and see if I can get pretty much endless operation out of the sensor, I’ll probably add air pressure as a measurement too, then work on a better enclosure to make the measurements more of the environmental conditions rather than how the enclosure’s been affected by solar heating for instance.
Looking back I was probably a little too ambitious in this RoadTest, but I’ve learnt a lot along the way and I’ve quite enjoyed it! I’ve been incredibly lucky really in that the TI team left enough in the code to act as pointers to how to tackle each aspect, and even the difference in data structures between sensor and gateway was a good learning experience, and a worthy reminder of one of the fragilities of using MQTT efficiently, compared to the bloated flexibility of XML.
There‘s very little fault I can find with either the supplied product or support, TI and element14 have pretty much nailed it in my book here, providing a great starting point for someone or a team looking to get going with an IoT device or application. The code is good and robust enough from the box to run for well over a week, is well commented to guide tailoring it for your own applications, and the price is pretty good if you are serious about making a start with a real IoT demo. The CC1350 sensor really is low power enough to be feasible as a battery powered remote node, whilst the combination of the BeagleBone Black and another CC1350 as the gateway provide a nicely self contained easily deployed scalable hub for gathering the data.
Many thanks to both TI and elemenet14 for giving me the chance to explore the capabilities of this great package!
Top Comments
Good start to the review, I like the way you show how you have stepped through setting the board up and getting it to work.
Kind regards
Very nice review. I like how you walk through everything so those who may want to do what you have done can easily follow along. Also thank you very much for the water test, I appreciate your time and…
Hi Kas, and no problem with the water test.
The IBM QuickStart was all set up, I just had to click a button on the web page presented by the BeagleBoneBlack and it took me straight to the data on IBM's…