Hardware components | ||||||
![]() |
| × | 1 | |||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
![]() |
|
This project is a prototype to receive sensor node data and battery life using the Cayenne app. I wanted to aggregate my data collection to a centralized hub using SparkFun's ESP8266 Thing Dev Board. This makes it easier for me to check the battery life and ambient light data from several sensor nodes without the need to go into the field. The data can be displayed on a graph within the app itself. Hopefully this can help jump start someone else's prototyping.
Here are the following steps I took:
1. Setup and get familiar with Cayenne myDevices app
https://www.theengineeringprojects.com/2016/09/getting-started-with-cayenne-arduino.html
2. Hook up SparkFun's ESP8266 Thing Dev Board to the Adafruit RFM Radio
3. Upload the "Host" code to the Thing Dev Board
4. Wire the TSL2561 Sensor to Adafruit's Feather M0 RFM95
5. Upload the "Client" code to the Feather M0 RFM95
6. Open the Cayenne app and test!
// ESP-LoRa Control Center
// An RF LoRa Control Center to dictate commands and receive data from a sensor node.
//
// This is based on Tanmoy Dutta's "Adafruit Feather Huzzah ESP8266 LoRa receiver for data relay to Raspberry Pi IoT Gateway"
// Augmented by: HyperChiicken
#define CAYENNE_PRINT Serial
#include <CayenneMQTTESP8266.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266HTTPClient.h>
#include <RH_RF95.h>
#include <ArduinoJson.h>
// WiFi network info.
char ssid[] = "insert_your_wifi_id_here";
char wifiPassword[] = "insert_your_password_here";
WiFiClient client;
// Cayenne authentication info. This should be obtained from the Cayenne Dashboard.
char username[] = "insert_cayenne_username_here";
char password[] = "insert_password_here";
char clientID[] = "insert_client_id_here";
#define RFM95_CS 16
#define RFM95_RST 0
#define RFM95_INT 15
// Blinky on receipt
#define LED 5
// Set radion frequency
#define RF95_FREQ 915.0
// Singleton instance of the radio driver
RH_RF95 rf95(RFM95_CS, RFM95_INT);
/*----------------------------------------------------------------------------
Function : setup()
Description :
------------------------------------------------------------------------------*/
void setup() {
pinMode(2, OUTPUT);
Serial.begin(115200);
Cayenne.begin(username, password, clientID, ssid, wifiPassword);
_initLoRa();
}
/*----------------------------------------------------------------------------
Function : loop()
Description : Main program loop
------------------------------------------------------------------------------*/
void loop() {
// Set local variables
char buf_txt[32] = " ",
data[8] = " ",
devID[2] = " ",
CMD[2] = " ",
N1_data[8] = " ",
N2_data[8] = " ";
uint16_t full, ir;
int TEST;
long lastConnectionTime = 0;
boolean lastConnected = false,
isReady_N1 = false,
isReady_N2 = false;
int FAILED = 0,
runNum = 0;
Cayenne.loop();
if (rf95.available()) {
// Should be a message for us now
uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
uint8_t len = sizeof(buf);
if (rf95.recv(buf, &len)) {
digitalWrite(LED, HIGH);
delay(10);
// Copy buffer data into char array
memcpy(buf_txt, buf, len);
Serial.println(buf_txt);
// Get Device ID
devID[0] = buf_txt[4];
devID[1] = buf_txt[5];
Serial.println(devID[1]);
// Get Command ID
CMD[0] = buf_txt[7];
CMD[1] = buf_txt[8];
// Get Data
TEST = atoi((char*)CMD);
Serial.println(TEST);
uint8_t x = TEST;
Serial.println(x);
int i = 0;
while (buf_txt[i] != ' ') {
data[i] = buf_txt[9 + i];;
i++;
}
uint8_t len2 = sizeof(data);
Serial.println(data);
Serial.println(x);
// Check signal strength
Serial.print("RSSI: ");
Serial.println(rf95.lastRssi(), DEC);
delay(10);
// Store data & send reply according to device ID
switch (devID[1]) {
case '1': {
memcpy(N1_data, data, len2);
///////////////////////MQTT Code
Cayenne.virtualWrite(x, N1_data);
delay(500);
//////////////////////////////////
isReady_N1 = true;
// Send a reply
uint8_t outgoingData[] = "Node01:OK";
rf95.send(outgoingData, sizeof(outgoingData));
rf95.waitPacketSent();
digitalWrite(LED, LOW);
} break;
case '2': {
memcpy(N2_data, data, len2);
Serial.println(x);
///////////////////////MQTT Code
Cayenne.virtualWrite(x, N2_data);
delay(500);
//////////////////////////////////
isReady_N2 = true;
// Send a reply
uint8_t outgoingData[] = "Node02:OK";
rf95.send(outgoingData, sizeof(outgoingData));
rf95.waitPacketSent();
digitalWrite(LED, LOW);
} break;
default: {
Serial.println("FAILED");
FAILED++;
} break;
}
}
else
{
Serial.println("Receive failed");
FAILED++;
}
}
else {
//displayOnOLED("Receive failed");
}
}
void donothing() {
}
/*----------------------------------------------------------------------------
Function : _initLoRa()
Description : Connect to WiFi access point
------------------------------------------------------------------------------*/
void _initLoRa() {
attachInterrupt(digitalPinToInterrupt(RFM95_INT), donothing, CHANGE);
pinMode(RFM95_RST, OUTPUT);
digitalWrite(RFM95_RST, HIGH);
// displayOnOLED("LoRa RX WiFi Repeater");
delay(1000);
// manual reset
digitalWrite(RFM95_RST, LOW);
delay(10);
digitalWrite(RFM95_RST, HIGH);
delay(10);
while (!rf95.init()) {
Serial.println("LoRa radio init failed");
// displayOnOLED("LoRa radio init failed");
while (1);
}
Serial.println("LoRa radio init OK!");
// displayOnOLED("LoRa radio init OK!");
delay(1000);
if (!rf95.setFrequency(RF95_FREQ)) {
Serial.println("setFrequency failed");
// displayOnOLED("setFrequency failed");
while (1);
}
rf95.setTxPower(5, false);
Serial.println("LoRa Listening...");
// displayOnOLED("LoRa Listening...");
delay(1000);
}
/*----------------------------------------------------------------------------
Cayenne Functions
Description : Get inputs from Cayenne to send out command
------------------------------------------------------------------------------*/
CAYENNE_IN_DEFAULT()
{
CAYENNE_LOG("CAYENNE_IN_DEFAULT(%u) - %s, %s", request.channel, getValue.getId(), getValue.asString());
//Process message here. If there is an error set an error message using getValue.setError(), e.g getValue.setError("Error message");
}
// Send out command #1 to sensor node
CAYENNE_IN(21)
{
CAYENNE_LOG("CAYENNE_IN_DEFAULT(%u) - %s, %s", request.channel, getValue.getId(), getValue.asString());
//Process message here. If there is an error set an error message using getValue.setError(), e.g getValue.setError("Error message");
int i = getValue.asInt();
digitalWrite(2, i);
uint8_t reply[] = "Node02:1";
rf95.send(reply, sizeof(reply));
rf95.waitPacketSent();
Serial.println("Sent a reply");
digitalWrite(LED, LOW);
Serial.println(" ");
}
// Send out command #2 to sensor nide
CAYENNE_IN(22)
{
CAYENNE_LOG("CAYENNE_IN_DEFAULT(%u) - %s, %s", request.channel, getValue.getId(), getValue.asString());
//Process message here. If there is an error set an error message using getValue.setError(), e.g getValue.setError("Error message");
int i = getValue.asInt();
digitalWrite(2, i);
uint8_t reply[] = "Node02:2";
rf95.send(reply, sizeof(reply));
rf95.waitPacketSent();
Serial.println("Sent a reply");
digitalWrite(LED, LOW);
Serial.println(" ");
}
// ESP-LoRa Sensor Node aka Client
// This code is based on Adafruit's Feather9x_TX simple messaging client (trnamitter)
// Augmented by : HyperChiicken
#include <SPI.h>
#include <RH_RF95.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_TSL2561_U.h>
/* Set debug mode ON or OFF*/
//#define DEBUG
bool ACK = 0;
/* for feather32u4
#define RFM95_CS 8
#define RFM95_RST 4
#define RFM95_INT 7
*/
/* for feather m0 */
#define RFM95_CS 8
#define RFM95_RST 4
#define RFM95_INT 3
/* for shield
#define RFM95_CS 10
#define RFM95_RST 9
#define RFM95_INT 7
*/
/* for ESP w/featherwing
#define RFM95_CS 2 // "E"
#define RFM95_RST 16 // "D"
#define RFM95_INT 15 // "B"
*/
/* Feather 32u4 w/wing
#define RFM95_RST 11 // "A"
#define RFM95_CS 10 // "B"
#define RFM95_INT 2 // "SDA" (only SDA/SCL/RX/TX have IRQ!)
*/
/* Feather m0 w/wing
#define RFM95_RST 11 // "A"
#define RFM95_CS 10 // "B"
#define RFM95_INT 6 // "D"
*/
/* Teensy 3.x w/wing
#define RFM95_RST 9 // "A"
#define RFM95_CS 10 // "B"
#define RFM95_INT 4 // "C"
*/
// Change to 434.0 or other frequency, must match RX's freq!
#define RF95_FREQ 915.0
// Singleton instance of the radio driver
RH_RF95 rf95(RFM95_CS, RFM95_INT);
// Create TSL2561 instance
Adafruit_TSL2561_Unified tsl = Adafruit_TSL2561_Unified(TSL2561_ADDR_FLOAT, 12345);
void configureSensor(void)
{
/* You can also manually set the gain or enable auto-gain support */
tsl.setGain(TSL2561_GAIN_1X); /* No gain ... use in bright light to avoid sensor saturation */
// tsl.setGain(TSL2561_GAIN_16X); /* 16x gain ... use in low light to boost sensitivity */
// tsl.enableAutoRange(true); /* Auto-gain ... switches automatically between 1x and 16x */
/* Changing the integration time gives you better sensor resolution (402ms = 16-bit data) */
tsl.setIntegrationTime(TSL2561_INTEGRATIONTIME_13MS); /* fast but low resolution */
//tsl.setIntegrationTime(TSL2561_INTEGRATIONTIME_101MS); /* medium resolution and speed */
// tsl.setIntegrationTime(TSL2561_INTEGRATIONTIME_402MS); /* 16-bit data but slowest conversions */
}
void setup()
{
pinMode(RFM95_RST, OUTPUT);
digitalWrite(RFM95_RST, HIGH);
//while (!Serial);
Serial.begin(9600);
delay(100);
Serial.println("Feather LoRa TX Test!");
// manual reset
digitalWrite(RFM95_RST, LOW);
delay(10);
digitalWrite(RFM95_RST, HIGH);
delay(10);
while (!rf95.init()) {
Serial.println("LoRa radio init failed");
while (1);
}
Serial.println("LoRa radio init OK!");
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
if (!rf95.setFrequency(RF95_FREQ)) {
Serial.println("setFrequency failed");
while (1);
}
Serial.print("Set Freq to: "); Serial.println(RF95_FREQ);
// Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
// The default transmitter power is 13dBm, using PA_BOOST.
// If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then
// you can set transmitter powers from 5 to 23 dBm:
rf95.setTxPower(23, false);
Serial.println("Starting Adafruit TSL2561 Test!");
/* Initialise the sensor */
if (!tsl.begin())
{
/* There was a problem detecting the TSL2561 ... check your connections */
Serial.print("Ooops, no TSL2561 detected ... Check your wiring or I2C ADDR!");
while (1);
}
/* Display some basic information on this sensor */
displaySensorDetails();
/* Setup the sensor gain and integration time */
configureSensor();
/* We're ready to go! */
Serial.println("");
delay(10);
}
void loop()
{
// Set local variables
char buf_txt[32] = " ",
data[7] = " ",
N1_data[7] = " ",
N2_data[7] = " ",
devID[2] = " ",
CMD[2] = "0",
databuf[11] = " ",
radiopacket_end[2] = " ";
char test[2] = " ";
uint8_t dev_ID[1], cmd[1], FAILED;
float vbat, lux, dataOUT;
if (rf95.available()) {
// Should be a message for us now
uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
uint8_t len = sizeof(buf);
if (rf95.recv(buf, &len)) {
Serial.print("Got: ");
// Copy buffer data into char array
memcpy(buf_txt, buf, len);
Serial.println(buf_txt);
// Get Device ID
dev_ID[0] = buf_txt[4];
dev_ID[1] = buf_txt[5];
Serial.print("Device_ID: ");
Serial.println(dev_ID[1]);
devID[0] = buf_txt[4];
devID[1] = buf_txt[5];
// Get command
cmd[1] = buf_txt[7];
CMD[1] = buf_txt[7];
//char cmd_str[2] = " ";
Serial.println(cmd[1]);
//Serial.println(cmd_str);
// Check if the message is addressed to this device
if (dev_ID[1] == 50) {
switch (cmd[1]) {
// Send battery voltage
case 49: {
vbat = checkBattery();
dataOUT = vbat;
dtostrf(vbat, 4, 2, databuf);
} break;
// Send TSL data
case 50: {
lux = getLux();
lux += getLux();
lux += getLux();
lux += getLux();
lux += getLux();
Serial.println(lux);
lux = lux/5;
Serial.println(lux);
dataOUT = lux;
dtostrf(lux, 4, 2, databuf);
} break;
default: {
Serial.println("FAILED");
FAILED++;
} break;
}
Serial.println(databuf);
char radiopacket[20] = "Node02:";
strcat(radiopacket, (char*)CMD);
strcat(radiopacket, databuf);
Serial.println(radiopacket_end[1]);
strcat(radiopacket, radiopacket_end);
Serial.print("Sending "); Serial.println(radiopacket);
Serial.print("Sending..."); delay(10);
// Wait for acknowledgment
while (ACK == 0) {
rf95.send((uint8_t*)radiopacket, sizeof(radiopacket));
rf95.waitPacketSent();
getACK();
}
ACK = 0; // reset variable
}
}
else {
//rf95.sleep();
}
}
}
/*
This routine checks for acknowledgment reply from server
*/
void getACK(void) {
uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
uint8_t len = sizeof(buf);
char buf_txt[32], data[7];
int dev_ID[1];
if (rf95.waitAvailableTimeout(3000)) {
if (rf95.recv(buf, &len)) {
// Copy buffer data into char array
memcpy(buf_txt, buf, len);
// Get Device ID
dev_ID[0] = buf_txt[4];
dev_ID[1] = buf_txt[5];
// Get Data
int i = 0;
while (buf_txt[i] != ' ') {
data[i] = buf_txt[13 + i];;
i++;
}
uint8_t len2 = sizeof(data);
delay(10);
// Check if the message is addressed to this device
if (dev_ID[1] == 50) {
ACK = 1;
Serial.println("OK"); delay(10);
}
else {
ACK = 0;
Serial.println("FAILED"); delay(10);
}
}
}
}
/*
This routine checks the voltage on the battery (if it exists) and returns that value. Should be in the 3.2-4.2 range depending upon the battery used
*/
float checkBattery() {
//This returns the current voltage of the battery on a Feather 32u4.
float measuredvbat = analogRead(9);
measuredvbat *= 2; // we divided by 2, so multiply back
measuredvbat *= 3.3; // Multiply by 3.3V, our reference voltage
measuredvbat /= 1024; // convert to voltage
return measuredvbat;
}
/*
This routine acquires the TSL2561 sensor data
*/
float getLux() {
float measuredlux;
uint16_t full, ir;
tsl.getLuminosity(&full, &ir);
measuredlux = tsl.calculateLux(full, ir);
return measuredlux;
}
/*
This simulates the dtostrf function from avr-libc
*/
char* dtostrf (float val, signed char width, unsigned char prec, char *sout) {
asm(".global _printf_float");
char fmt[20];
sprintf(fmt, "%%%d.%df", width, prec);
sprintf(sout, fmt, val);
return sout;
}
Comments
Please log in or sign up to comment.