|
Post by camblonie on Jan 23, 2015 3:10:43 GMT
Just when I thought I had my head around Erics code I switched to the duplex setup from Computourist. github.com/computourist/RFM69-MQTT-clientBasic question on your sketch Computourist. What in the code is making it report only the temp and humidity? What toggles mes.cmd? Then why does it send each 3 times? Scott
|
|
|
Post by computourist on Jan 23, 2015 9:30:10 GMT
- Periodic transmission is controlled by the end node. (and can be different for each node) Values to be included are declared from line 200 on in the end node sketch: You can include any valid device-value. For radio testing (antenna adjustment) I often include only signal strength (dev02) and set the interval to 10 seconds. In this way you get a near-real-time feedback when adjusting the antenna's. - The cmd.mes parameter is set in the gateway on line 400: mes.cmd = 0; // default is 'SET' value if (strPayload == "READ") mes.cmd = 1; // in this case 'READ' valueIf the MQTT message contains a valid READ request the mes.cmd flag is set, instructing the end node to send the required parameter. - It does not send each parameter three times. What may be happening is that your client is subscribed to a topic in three different ways (maybe with wildcards? ) - Computourist Just when I thought I had my head around Erics code I switched to the duplex setup from Computourist. github.com/computourist/RFM69-MQTT-clientBasic question on your sketch Computourist. What in the code is making it report only the temp and humidity? What toggles mes.cmd? Then why does it send each 3 times? Scott
|
|
|
Post by camblonie on Jan 24, 2015 16:01:32 GMT
Thanks Computourist. Of course it was as easy as commenting out. Regarding the 3 publish, at the command prompt: mosquitto_sub -d -h 192... -t # Which I assume subscribes to everything. I see 3 posts of every node device though.
|
|
|
Post by demondreamer on Jan 25, 2015 0:08:32 GMT
Just when I thought I had my head around Erics code I switched to the duplex setup from Computourist. github.com/computourist/RFM69-MQTT-clientBasic question on your sketch Computourist. What in the code is making it report only the temp and humidity? What toggles mes.cmd? Then why does it send each 3 times? Scott I'm in the same boat as you. I spent a LOT of time understanding how Eric's code works. It was a wonderful motivation for learning to program cut/paste and slightly modify other's code. Computourist's is very different, though I think I'm getting it with some trial and error. I've been working on adding a new sensor device. I have ultrasonic sensors that I want to use, with the NewPing library, and was able to get it working last night. If you use a program like Notepad++ with the "compare" plugin, you can compare what I've posted below with the unmodified sketches. This will highlight where things have been added. Sending "READ" payload to topic "home/rfm_gw/sb/node17/dev40" should return the distance measurement in inches. Here's the RFM_DHT_NODE_17 sketch with device 40 (ultrasonic sensor) added. // RFM69 DHT node sketch
//
// This node talks to the MQTT-Gateway and will:
// - send sensor data periodically and on-demand
// - receive commands from the gateway to control actuators
// - receive commands from the gateway to change settings
//
// Several nodes can operate within a single network; each have a unique node ID.
// On startup the node operates with default values, set on compilation.
//
// Hardware used is a 3.3 Volt 8MHz arduino Pro; this is easier to interface to RFM69
//
// A DHT-11 is used for temperature & humidity measurements, other sensors and outputs can be added easily.
//
// Message format is: nodeID/deviceID/command/integer/float/string
//
// Depending on the type of data (integer, float or string) one of the payload variables is used
// Command = 0 means write a value in the node, cmd = 1 means read a value
//
// Current defined devices are:
//
// 0 error: tx only: error message if no wireless connection (generated by gateway)
// 1 node: read/set transmission interval in seconds, 0 means no periodic transmission
// 2 RSSI: read radio signal strength
// 3 Version: read version node software
// 4 voltage: read battery level
// 5 ACK: read/set acknowledge message after a 'set' request
// 6 toggle: read/set toggle function on button press
// 7 timer: read/set activation timer after button press in seconds, 0 means no timer
// 8 buttonpress: read/set flag to send a message when button pressed
//
// 10 actuator: read/set LED or relay output
// 20 Button: tx only: message sent when button pressed
// 31 temperature: read temperature
// 32 humidity: read humidity
// 40 ping: read ping distance
// 99 wakeup: tx only: first message sent on node startup
//
// The button can be set to:
// - generate a message on each press (limited to one per 10 seconds) and/or
// - toggle the output ACT1 (local node function) or
// - activate the output for a fixed time period (local node function)
//
// A debug mode is included which outputs messages on the serial output
//
// RFM69 Library by Felix Rusu - felix@lowpowerlab.com
// Get the RFM69 library at: https://github.com/LowPowerLab/
//
// version 1.7 by Computourist@gmail.com december 2014
//
#include <RFM69.h>
#include <SPI.h>
#include <DHT.h>
#include <NewPing.h>
//
// CONFIGURATION PARAMETERS
//
#define NODEID 17 // unique node ID within the closed network
#define GATEWAYID 1 // node ID of the Gateway is always 1
#define NETWORKID 100 // network ID of the network
#define ENCRYPTKEY "xxxxxxxxxxxxxxxx" // 16-char encryption key; same as on Gateway!
#define DEBUG // uncomment for debugging
#define VERSION "DHT V1.7 NEWPING V0.1" // this value can be queried as device 3
// Wireless settings Match frequency to the hardware version of the radio
#define FREQUENCY RF69_433MHZ
//#define FREQUENCY RF69_868MHZ
//#define FREQUENCY RF69_915MHZ
#define IS_RFM69HW // uncomment only for RFM69HW!
#define ACK_TIME 50 // max # of ms to wait for an ack
// DHT 11 / sensor setting
#define DHTPIN 4 // DHT data connection
#define DHTTYPE DHT11 // type of sensor
#define ACT1 9 // Actuator pin (LED or relay)
#define BTN 8 // Button pin
#define SERIAL_BAUD 9600
#define HOLDOFF 10000 // blocking period between button messages
#define TRIGGER_PIN 5 // Arduino pin tied to trigger pin on the ultrasonic sensor.
#define ECHO_PIN 6 // Arduino pin tied to echo pin on the ultrasonic sensor.
#define MAX_DISTANCE 500 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.
//
// STARTUP DEFAULTS
//
long TXinterval = 60; // periodic transmission interval in seconds
long TIMinterval = 20; // timer interval in seconds
bool ackButton = false; // flag for message on button press
bool toggleOnButton = true; // toggle output on button press
//
// VARIABLES
//
long lastPeriod = -1; // timestamp last transmission
long lastBtnPress = -1; // timestamp last buttonpress
float hum, temp; // humidity, temperature
int uS; // ultrasonic ping
int ACT1State; // status ACT1 output
int signalStrength; // radio signal strength
bool setAck = false; // send ACK message on 'SET' request
bool send1, send2, send3, send4;
bool send5, send6, send7, send8;
bool send10, send20, send31, send32;
bool send40; // message triggers
bool promiscuousMode = false; // only listen to nodes within the closed network
bool curState = true; // current button state
bool lastState = true; // last button state
bool wakeUp = true; // wakeup flag
bool timerOnButton = false; // timer output on button press
bool msgBlock = false; // flag to hold button messages to prevent overload
typedef struct { // Radio packet format
int nodeID; // node identifier
int devID; // device identifier
int cmd; // read or write
int intVal; // integer payload
float fltVal; // floating payload
char payLoad[10]; // string payload
} Message;
Message mes;
DHT dht(DHTPIN, DHTTYPE, 3); // initialise temp/humidity sensor for 3.3 Volt arduino
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance.
RFM69 radio;
//
//===================== SETUP ========================================
//
void setup() {
#ifdef DEBUG
Serial.begin(SERIAL_BAUD);
#endif
pinMode(ACT1, OUTPUT); // set actuator 1
ACT1State = 0;
digitalWrite(ACT1, ACT1State);
dht.begin(); // initialise temp/hum sensor
radio.initialize(FREQUENCY,NODEID,NETWORKID); // initialise radio
#ifdef IS_RFM69HW
radio.setHighPower(); // only for RFM69HW!
#endif
radio.encrypt(ENCRYPTKEY); // set radio encryption
radio.promiscuous(promiscuousMode); // only listen to closed network
wakeUp = true; // send wakeup message
send8 = false; // initialise button press flag
#ifdef DEBUG
Serial.print("Node Software Version ");
Serial.println(VERSION);
Serial.print("\nTransmitting at ");
Serial.print(FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
Serial.println(" Mhz...");
#endif
} // end setup
//
//
//==================== MAIN ========================================
//
void loop() {
// RECEIVE radio input
//
if (receiveData()) parseCmd(); // receive and parse any radio input
// DETECT BUTTON PRESS
//
curState = digitalRead(BTN);
msgBlock = (millis() - lastBtnPress < HOLDOFF); // hold-off time for additional button messages
if (curState == LOW && lastState == HIGH) { // button pressed ?
delay(5);
lastBtnPress = millis(); // take timestamp
if (ackButton && !msgBlock) send20 = true; // set button message flag
if (toggleOnButton) { // button in toggle state ?
ACT1State = !ACT1State; // toggle output
digitalWrite(ACT1, ACT1State);
} else
if (TIMinterval > 0 && !timerOnButton) { // button in timer state ?
timerOnButton = true; // start timer interval
ACT1State = HIGH; // switch on ACT1
digitalWrite(ACT1, ACT1State);
}
}
lastState = digitalRead(BTN);
// TIMER CHECK
//
if (TIMinterval > 0 && timerOnButton) // =0 means no timer
{
if ( millis() - lastBtnPress > TIMinterval*1000) { // timer expired ?
timerOnButton = false; // then end timer interval
ACT1State = LOW; // and switch off Actuator
digitalWrite(ACT1, ACT1State);
}
}
// PERIODIC TRANSMISSION
//
if (TXinterval > 0)
{
int currPeriod = millis()/(TXinterval*1000);
if (currPeriod != lastPeriod) { // interval elapsed ?
lastPeriod = currPeriod;
// list of sensordata to be sent periodically..
// remove comment to include parameter in transmission
send1 = true; // send transmission interval
send2 = true; // signal strength
send4 = true; // voltage level
send10 = true; // actuator state
send31 = true; // send temperature
send32 = true; // send humidity
send40 = true; // send ping distance
}
}
// SEND RADIO PACKETS
//
sendMsg(); // send any radio messages
} // end loop
//
//
//===================== FUNCTIONS ==========================================
//
//======== RECEIVEDATA : receive data from gateway over radio
//
bool receiveData() {
bool validPacket = false;
if (radio.receiveDone()) // check for received packets
{
if (radio.DATALEN != sizeof(mes)) // wrong message size means trouble
#ifdef DEBUG
Serial.println("invalid message structure..")
#endif
;
else
{
mes = *(Message*)radio.DATA;
validPacket = true; // YES, we have a packet !
signalStrength = radio.RSSI;
#ifdef DEBUG
Serial.print(mes.devID);
Serial.print(", ");
Serial.print(mes.cmd);
Serial.print(", ");
Serial.print(mes.intVal);
Serial.print(", ");
Serial.print(mes.fltVal);
Serial.print(", RSSI= ");
Serial.println(radio.RSSI);
Serial.print("Node: ");
Serial.println(mes.nodeID);
#endif
}
}
if (radio.ACKRequested()) radio.sendACK(); // respond to any ACK request
return validPacket; // return code indicates packet received
} // end recieveData
//
//
//============== PARSECMD: analyse messages and execute commands received from gateway
//
void parseCmd() { // parse messages received from the gateway
send1 = false; // initialise all send triggers
send2 = false;
send3 = false;
send4 = false;
send5 = false;
send6 = false;
send7 = false;
send8 = false;
send10 = false;
send31 = false;
send32 = false;
send40 = false;
switch (mes.devID) // devID indicates device (sensor) type
{
case (1): // polling interval in seconds
if (mes.cmd == 0) { // cmd == 0 means write a value
TXinterval = mes.intVal; // change interval to radio packet value
if (TXinterval <10 && TXinterval !=0) TXinterval = 10; // minimum interval is 10 seconds
if (setAck) send1 = true; // send message if required
#ifdef DEBUG
Serial.print("Setting interval to ");
Serial.print(TXinterval);
Serial.println(" seconds");
#endif
}
else send1 = true; // cmd == 1 is a read request, so send polling interval
break;
case (2): // signal strength
if (mes.cmd == 1) send2 = true;
break;
case (3): // software version
if (mes.cmd == 1) send3 = true;
break;
case (4): // battery level
if (mes.cmd == 1) send4 = true;
break;
case (5): // set ack status
if (mes.cmd == 0) {
if (mes.intVal == 0) setAck = false;
if (mes.intVal == 1) setAck = true;
if (setAck) send5 = true; // acknowledge message ?
}
else send5 = true; // read request means schedule a message
break;
case (6): // set toggle
if (mes.cmd == 0) {
if (mes.intVal == 0) toggleOnButton = false;
if (mes.intVal == 1) toggleOnButton = true;
if (setAck) send6 = true; // acknowledge message ?
}
else send6 = true;
break;
case (7): // timer interval in seconds
if (mes.cmd == 0) { // cmd == 0 means write a value
TIMinterval = mes.intVal; // change interval
if (TIMinterval <5 && TIMinterval !=0) TIMinterval = 5;
if (setAck) send7 = true; // acknowledge message ?
} // cmd == 1 means read a value
else send7 = true; // send timing interval
break;
case (8): // set button ack status
if (mes.cmd == 0) {
if (mes.intVal == 0) ackButton = false;
if (mes.intVal == 1) ackButton = true;
if (setAck) send8 = true; // acknowledge message ?
}
else send8 = true;
break;
case (10): // Actuator 1
if (mes.cmd == 0) { // cmd == 0 means write
if(mes.intVal == 0 || mes.intVal == 1) {
ACT1State = mes.intVal;
digitalWrite(ACT1, ACT1State);
if (setAck) send10 = true; // acknowledge message ?
#ifdef DEBUG
Serial.print("Set LED to ");
Serial.println(ACT1State);
#endif
}}
else send10 = true; // cmd == 1 means read
break;
case (31): // temperature
if (mes.cmd == 1) send31 = true;
break;
case (32): // humidity
if (mes.cmd == 1) send32 = true;
break;
case (40): // ping
if (mes.cmd == 1) send40 = true;
break;
}
} // end parseCmd
//
//
//====================== SENDMSG: sends messages that are flagged for transmission
//
void sendMsg() { // prepares values to be transmitted
bool tx = false; // transmission flag
mes.nodeID=NODEID;
mes.intVal = 0;
mes.fltVal = 0;
mes.cmd = 0; // '0' means no action needed in gateway
int i;
for ( i = 0; i < sizeof(VERSION); i++){
mes.payLoad[i] = VERSION[i]; }
mes.payLoad[i] = '\0'; // software version in payload string
if (wakeUp) { // send wakeUp call
mes.devID = 99;
wakeUp = false; // reset transmission flag for this message
txRadio(); // transmit
}
if (send1) { // transmission interval
mes.devID = 1;
mes.intVal = TXinterval; // seconds (integer)
send1 = false;
txRadio();
}
if (send2) {
mes.devID = 2;
mes.intVal = signalStrength; // signal strength (integer)
send2 = false;
txRadio();
}
if (send3) { // node software version (string)
mes.devID = 3; // already stored in payload string
send3 = false;
txRadio();
}
if (send4) { // measure voltage..
mes.devID = 4;
long result; // Read 1.1V reference against AVcc
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
delay(2); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Convert
while (bit_is_set(ADCSRA,ADSC));
result = ADCL;
result |= ADCH<<8;
result = 1126400L / result; // Back-calculate in mV
mes.fltVal = float(result/1000.0); // Voltage in Volt (float)
txRadio();
send4 = false;
}
if (send5) { // Acknowledge on 'SET'
mes.devID = 5;
if (setAck) mes.intVal = 1; else mes.intVal = 0; // state (integer)
send5 = false;
txRadio();
}
if (send6) { // Toggle on Buttonpress
mes.devID = 6;
if (toggleOnButton) mes.intVal = 1; // read state of toggle flag
else mes.intVal = 0; // state (integer)
send6 = false;
txRadio();
}
if (send7) { // timer interval
mes.devID = 7;
mes.intVal = TIMinterval; // seconds (integer)
send7 = false;
txRadio();
}
if (send8) { // Send Button pressed message
mes.devID = 8;
if (ackButton) mes.intVal = 1;
else mes.intVal = 0; // state (integer)
send8 = false;
txRadio();
}
if (send10) { // state of Actuator 1
mes.devID = 10;
mes.intVal = ACT1State; // state (integer)
send10 = false;
txRadio();
}
if (send20) { // Button presssed
mes.devID = 20;
mes.intVal = 1; // state (integer)
send20 = false;
txRadio();
}
if (send31) { // Temperature
mes.devID = 31;
temp = dht.readTemperature();
mes.fltVal = temp; // Degrees Celcius (float)
send31 = false;
txRadio();
}
if (send32) { // Humidity
mes.devID = 32;
hum = dht.readHumidity();
mes.fltVal = hum; // Percentage (float)
send32 = false;
txRadio();
}
if (send40) { // ping
mes.devID = 40;
uS = sonar.ping(); // Send ping, get ping time in microseconds (uS).
mes.fltVal = (uS / US_ROUNDTRIP_IN); // Convert ping time to distance in inches (0 = outside set distance range)
send40 = false;
txRadio();
}
}
//
//
//======================= TXRADIO
//
void txRadio() // Transmits the 'mes'-struct to the gateway
{
if (radio.sendWithRetry(GATEWAYID, (const void*)(&mes), sizeof(mes)))
#ifdef DEBUG
{Serial.print(" message ");
Serial.print(mes.devID);
Serial.println(" sent...");}
else Serial.println("No connection...")
#endif
;} // end txRadio
and here is the corresponding RFM_MQTT_GW_19 sketch with the device 40 added. // RFM69 MQTT gateway sketch
//
// This gateway relays messages between a MQTT-broker and several wireless nodes and will:
// - receive sensor data from several nodes periodically and on-demand
// - send/receive commands from the broker to control actuators and node parameters
//
// Connection to the MQTT broker is over a fixed ethernet connection:
//
// The MQTT topic is /home/rfm_gw/direction/nodeid/devid
// where direction is: southbound (sb) towards the remote node and northbound (nb) towards MQTT broker
//
// Connection to the nodes is over a closed radio network:
//
// RFM Message format is: nodeID/deviceID/command/integer/float/string
// where Command = 1 for a read request and 0 for a write request
//
// Current defined devices are:
// 0 error: Tx only: error message if no wireless connection
// 1 node: read/set transmission interval in seconds, 0 means no periodic transmission
// 2 RSSI: read reception strength
// 3 Version: read version node software
// 4 voltage: read battery level
// 5 ACK: read/set acknowledge message after 'SET' request
// 6 toggle: read/set toggle function on button press
// 7 timer: read/set activation timer after button press in seconds, 0 means no timer
// 8 buttonpress: read/set flag to send a message when button pressed
//
// 10 actuator: read/set LED or relay output
// 20 Button: Tx only: message sent when button pressed
// 31 temperature: read temperature
// 32 humidity: read humidity
// 99 wakeup: Tx only: sends a message on node startup
//
// ==> Note:
// - Interrupts are disabled during ethernet transactions in w5100.h (ethernet library)
// (See http://harizanov.com/2012/04/rfm12b-and-arduino-ethernet-with-wiznet5100-chip/)
// - Ethernet card and RFM68 board default use the same Slave Select pin (10) on the SPI bus;
// To avoid conflict the RFM module is controlled by another SS pin (8).
//
//
// RFM69 Library by Felix Rusu - felix@lowpowerlab.com
// Get the RFM69 library at: https://github.com/LowPowerLab/s
//
// version 1.8 by Computourist@gmail.com december 2014
// version 1.9 fixed resubscription after network outage Jan 2015
#include <RFM69.h>
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
#define DEBUG // uncomment for debugging
#define VERSION "GW V1.9"
// Ethernet settings
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xBA, 0xBE }; // MAC address for ethernet
byte mqtt_server[] = { 192, 168, 1, 181 }; // MQTT broker address
byte ip[] = { 192, 168, 1, 162 }; // Gateway address (if DHCP fails)
// Wireless settings
#define NODEID 1 // unique node ID in the closed radio network; gateway is 1
#define RFM_SS 8 // Slave Select RFM69 is connected to pin 8
#define NETWORKID 100 // closed radio network ID
//Match frequency to the hardware version of the radio (uncomment one):
#define FREQUENCY RF69_433MHZ
//#define FREQUENCY RF69_868MHZ
//#define FREQUENCY RF69_915MHZ
#define ENCRYPTKEY "xxxxxxxxxxxxxxxx" // shared 16-char encryption key is equal on Gateway and nodes
#define IS_RFM69HW // uncomment only for RFM69HW! Leave out if you have RFM69W!
#define ACK_TIME 50 // max # of ms to wait for an ack
// PIN settings
#define MQCON 7 // MQTT Connection indicator
#define R_LED 9 // Radio activity indicator
#define SERIAL_BAUD 9600
typedef struct { // Radio packet structure
int nodeID; // node identifier
int devID; // device identifier 0 is node; 31 is temperature, 32 is humidity
int cmd; // read or write
int intVal; // integer payload
float fltVal; // floating payload
char payLoad[10]; // char array payload
} Message;
Message mes;
#ifdef DEBUG
bool act1Stat = false; // remember LED state in debug mode
bool msgToSend = false; // message request by debug action
int curstat = 0; // current polling interval in debug mode
int stat[] = {0,1,6,20}; // status 0 means no polling, 1, 6, 20 seconds interval
#endif
int dest; // destination node for radio packet
bool Rstat = false; // radio indicator flag
bool mqttCon = false; // MQTT broker connection flag
bool respNeeded = false; // MQTT message flag in case of radio connection failure
bool mqttToSend = false; // message request issued by MQTT request
bool promiscuousMode = false; // only receive closed network nodes
long onMillis; // timestamp when radio LED was turned on
char *subTopic = "home/rfm_gw/sb/#"; // MQTT subscription topic ; direction is southbound
char *clientName = "RFM_gateway"; // MQTT system name of gateway
char buff_topic[30]; // MQTT publish topic string
char buff_mess[30]; // MQTT publish message string
RFM69 radio;
EthernetClient ethClient;
PubSubClient mqttClient(mqtt_server, 1883, mqtt_subs, ethClient );
//
//============== SETUP
//
void setup() {
#ifdef DEBUG
Serial.begin(SERIAL_BAUD);
#endif
radio.setCS(RFM_SS); // change default Slave Select pin for RFM
radio.initialize(FREQUENCY,NODEID,NETWORKID); // initialise radio module
#ifdef IS_RFM69HW
radio.setHighPower(); // only for RFM69HW!
#endif
radio.encrypt(ENCRYPTKEY); // encrypt with shared key
radio.promiscuous(promiscuousMode); // listen only to nodes in closed network
pinMode(R_LED, OUTPUT); // set pin of radio indicator
pinMode(MQCON, OUTPUT); // set pin for MQTT connection indicator
digitalWrite(MQCON, LOW); // switch off MQTT connection indicator
digitalWrite(R_LED, LOW); // switch off radio indicator
#ifdef DEBUG
Serial.print("Gateway Software Version ");
Serial.println(VERSION);
Serial.print("\nListening at ");
Serial.print(FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
Serial.println(" Mhz...");
#endif
if (Ethernet.begin(mac) == 0) { // start the Ethernet connection
#ifdef DEBUG
Serial.println("Failed to configure Ethernet using DHCP");
#endif
Ethernet.begin(mac, ip);
}
#ifdef DEBUG
Serial.println("connecting...");
#endif
delay(1000);
mqttCon = 0; // reset connection flag
while(mqttCon != 1){ // retry MQTT connection every 2 seconds
#ifdef DEBUG
Serial.println("connection failed...");
#endif
mqttCon = mqttClient.connect(clientName); // retry connection to broker
delay(2000); // every 2 seconds
}
if(mqttCon){ // Connected !
#ifdef DEBUG
Serial.println("got connection with MQTT server");
#endif
digitalWrite(MQCON, HIGH); // switch on MQTT connection indicator
mqttClient.subscribe(subTopic); // subscribe to all southbound messages
}
#ifdef DEBUG
else Serial.println("no connection with MQTT server");
#endif
} // end setup
//
//============== MAIN
//
void loop() {
#ifdef DEBUG
if (!mqttToSend && Serial.available() > 0) // do not interfere with mqtt traffic
{ msgToSend = serialInput();} // get any human input in debug mode
if (msgToSend) {sendMsg(dest);} // send serial input instruction packets
#endif
if (Rstat) { // turn off radio LED after 100 msec
if (millis() - onMillis > 100) {
Rstat = false;
digitalWrite(R_LED, LOW);
}
}
if (mqttToSend) {sendMsg(dest);} // send MQTT instruction packets over the radio network
if (radio.receiveDone()) { processPacket();} // check for received radio packets and construct MQTT message
if (!mqttClient.loop()) { // check connection MQTT server and process MQTT subscription input
mqttCon = 0;
digitalWrite(MQCON, LOW);
while(mqttCon != 1){ // try to reconnect every 2 seconds
mqttCon = mqttClient.connect(clientName);
delay(2000);
}
if(mqttCon){ // Yes, we have a link so,
digitalWrite(MQCON, mqttCon); // turn on MQTT link indicator and
mqttClient.subscribe(subTopic); // re-subscribe to mqtt topic
}
}
} // end loop
//
//============== SENDMSG
//
// sends messages over the radio network
void sendMsg(int target) {
Rstat = true; // radio indicator on
digitalWrite(R_LED, HIGH); // turn on radio LED
onMillis = millis(); // store timestamp
int i = 5; // number of transmission retries
while (respNeeded && i>0) { // first try to send packets
if (radio.sendWithRetry(target, (const void*)(&mes), sizeof(mes),5)) {
respNeeded = false;
#ifdef DEBUG
Serial.print("Message sent to node " );
Serial.println(target);
#endif
} else delay(500); // half a second delay between retries
i--;
}
if (respNeeded) { // if not succeeded in sending packets after 5 retries
sprintf(buff_topic, "home/rfm_gw/nb/node%02d/dev00", target); // construct MQTT topic for radio loss (device 0)
sprintf(buff_mess, "connection lost node %d", target); // and message
mqttClient.publish(buff_topic,buff_mess); // publish ...
respNeeded = false; // reset response needed flag
#ifdef DEBUG
Serial.println("No connection....");
#endif
}
if (mqttToSend) mqttToSend = false; // reset send trigger
#ifdef DEBUG
if (msgToSend) msgToSend = false; // reset debug send trigger
#endif
} // end sendMsg
//
//============== PROCESSPACKET
//
// receives data from the wireless network, parses the contents and constructs MQTT topic and value
void processPacket() {
Rstat = true; // set radio indicator flag
digitalWrite(R_LED, HIGH); // turn on radio LED
onMillis = millis(); // store timestamp
if (radio.DATALEN != sizeof(mes)) // wrong message size means trouble
#ifdef DEBUG
Serial.println("invalid message structure..")
#endif
;
else // message size OK...
{
mes = *(Message*)radio.DATA; // copy radio packet
// and construct MQTT northbound topic
sprintf(buff_topic, "home/rfm_gw/nb/node%02d/dev%02d", radio.SENDERID, mes.devID);
#ifdef DEBUG
Serial.print(radio.SENDERID); Serial.print(", ");
Serial.print(mes.devID); Serial.print(", ");
Serial.print(mes.cmd); Serial.print(", ");
Serial.print(mes.intVal); Serial.print(", ");
Serial.print(mes.fltVal); Serial.print(", RSSI= ");
Serial.println(radio.RSSI); Serial.print("Node: ");
Serial.print(mes.nodeID); Serial.print(" Version: ");
for (int i=0; i<sizeof(mes.payLoad); i++) Serial.print(mes.payLoad[i]);
Serial.println();
#endif
}
switch (mes.devID) // construct MQTT message, according to device ID
{
case (1): // Transmission interval
{ sprintf(buff_mess, "%d",mes.intVal);
}
break;
case (2): // Signal strength
{ sprintf(buff_mess, "%d", radio.RSSI);
}
break;
case (3): // Node software version
{int i; for (i=0; i<sizeof(mes.payLoad); i++){
buff_mess[i] = (mes.payLoad[i]);
}
}
break;
case (4): // Node voltage
{ dtostrf(mes.fltVal, 5,2, buff_mess);
}
break;
case (5): // ACK status
{ if (mes.intVal == 1 )sprintf(buff_mess, "ON");
if (mes.intVal == 0 )sprintf(buff_mess, "OFF");
}
break;
case (6): // Toggle status
{ if (mes.intVal == 1 )sprintf(buff_mess, "ON");
if (mes.intVal == 0 )sprintf(buff_mess, "OFF");
}
break;
case (7): // Timer interval
{ sprintf(buff_mess, "%d",mes.intVal);
}
break;
case (8): // Button ack status
{ if (mes.intVal == 1 )sprintf(buff_mess, "ON");
if (mes.intVal == 0 )sprintf(buff_mess, "OFF");
}
break;
case (10): // Actuator
{ if (mes.intVal == 1 )sprintf(buff_mess, "ON");
if (mes.intVal == 0 )sprintf(buff_mess, "OFF");
}
break;
case (20): // Button pressed message
{ sprintf(buff_mess, "BUTTON PRESSED");
}
break;
case (31): // temperature
{ dtostrf(mes.fltVal, 5,2, buff_mess);
}
break;
case (32): // humidity
{ dtostrf(mes.fltVal, 5,2, buff_mess);
}
break;
case (40): // ultrasonic sensor
{ dtostrf(mes.fltVal, 5,2, buff_mess);
}
break;
case (99): // wakeup message
{ sprintf(buff_mess, "NODE %d WAKEUP", mes.nodeID);
}
break;
} // end switch
#ifdef DEBUG
Serial.print("MQTT message: ");
Serial.print(buff_topic);
Serial.print(": ");
Serial.println(buff_mess);
#endif
mqttClient.publish(buff_topic,buff_mess); // publish MQTT message in northbound topic
if (radio.ACKRequested()) radio.sendACK(); // reply to any radio ACK requests
} // end processPacket
//
//============== MQTT_SUBS
//
// receive messages from subscribed topics
// parse MQTT topic / message and construct radio message
//
// The values in the MQTT topic/message are converted to corresponding values on the Radio network
//
void mqtt_subs(char* topic, byte* payload, unsigned int length) {
mes.nodeID = NODEID; // gateway is node 1
mes.fltVal = 0;
mes.intVal = 0;
mqttToSend = false; // not a valid request yet...
#ifdef DEBUG
Serial.println("Message received from Mosquitto...");
Serial.println(topic);
#endif
if (strlen(topic) == 27) { // correct topic length ?
dest = (topic[19]-'0')*10 + topic[20]-'0'; // extract target node ID from MQTT topic
int DID = (topic[25]-'0')*10 + topic[26]-'0'; // extract device ID from MQTT topic
payload[length] = '\0'; // terminate string with '0'
String strPayload = String((char*)payload); // convert to string
mes.devID = DID;
mes.cmd = 0; // default is 'SET' value
if (strPayload == "READ") mes.cmd = 1; // in this case 'READ' value
if (DID == 1) {
mes.intVal = strPayload.toInt(); // polling interval is in MQTT message
mqttToSend = true;
}
if (DID == 2 && mes.cmd ==1) mqttToSend = true; // 'READ' request for signal strength
if (DID == 3 && mes.cmd ==1) mqttToSend = true; // 'READ' request for node software version
if (DID == 4 && mes.cmd ==1) mqttToSend = true; // 'READ' request for node supply voltage
if (DID == 5) { // 'SET' or 'READ' ACK status
mqttToSend = true;
if (strPayload == "ON") mes.intVal = 1; // payload value is state
else if (strPayload == "OFF") mes.intVal = 0;
else if (strPayload != "READ") mqttToSend = false; // invalid payload; do not process
}
if (DID == 6) { // 'SET' or 'READ' toggle status
mqttToSend = true;
if (strPayload == "ON") mes.intVal = 1; // payload value is state
else if (strPayload == "OFF") mes.intVal = 0;
else if (strPayload != "READ") mqttToSend = false; // invalid payload; do not process
}
if (DID == 7) {
mes.intVal = strPayload.toInt(); // timer interval is in MQTT message
mqttToSend = true;
}
if (DID == 8) { // 'SET' or 'READ' button ACK status
mqttToSend = true;
if (strPayload == "ON") mes.intVal = 1; // payload value is state
else if (strPayload == "OFF") mes.intVal = 0;
else if (strPayload != "READ") mqttToSend = false; // invalid payload; do not process
}
if (DID >= 10 && DID <= 20) { // device 10->20 are actuators
mqttToSend = true;
if (strPayload == "ON") mes.intVal = 1; // payload value is state
else if (strPayload == "OFF") mes.intVal = 0;
else if (strPayload != "READ") mqttToSend = false; // invalid payload; do not process
}
if (DID == 31 && mes.cmd ==1) mqttToSend = true; // 'READ' request for temperature
if (DID == 32 && mes.cmd ==1) mqttToSend = true; // 'READ' request for humidity
if (DID == 40 && mes.cmd ==1) mqttToSend = true; // 'READ' request for ultrasonic sensor
respNeeded = mqttToSend; // valid request needs radio response
#ifdef DEBUG
Serial.println(strPayload);
Serial.print("Value is: ");
Serial.println(mes.intVal);
#endif
}
#ifdef DEBUG
else Serial.println("wrong message format in MQTT subscription.");
#endif
} // end mqttSubs
//
// SERIALINPUT, only used in debugging mode
//
// some commands can be given thru the terminal to simulate actions
// 'l' will toggle LED
// 's' will toggle tranmsission interval between 4 states
// 'v' will request voltage to be sent
//
#ifdef DEBUG
bool serialInput() { // get and process manual input
bool msgAvail = false;
char input = Serial.read();
dest = 2; // debug against fixed node 2
// comment out to debug against node that last sent respons
mes.nodeID = radio.SENDERID; // initialize
mes.devID = 0;
mes.intVal = 0;
mes.fltVal = 0;
mes.cmd = 0;
if (input == 'l') // toggle LED
{
msgAvail = true;
respNeeded = true;
act1Stat = !act1Stat;
mes.devID = 10;
if (act1Stat) mes.intVal = 1; else mes.intVal =0;
Serial.print("Current LED status is ");
Serial.println(act1Stat);
}
if (input == 'v') // read voltage
{
msgAvail = true;
respNeeded = true;
mes.cmd = 1;
mes.devID = 4;
Serial.println("Voltage requested ");
}
if (input == 's') // set polling interval
{
msgAvail = true;
respNeeded = true;
curstat++;
if (curstat == 4) curstat = 0;
mes.devID = 1;
mes.intVal = stat[curstat];
Serial.print("Current polling interval is ");
Serial.println(mes.intVal);
}
return msgAvail;
} // end serialInput
#endif
You have to modify the gateway code to handle each new device, though once a device is defined, it works with any node. I think it's possible to define a range (say, 40-49) of devices in the gateway sketch for future additions in your nodes to use, without having to reprogram the gateway. If I replace line 443 with if (DID >= 40 && DID <= 49 && mes.cmd ==1) mqttToSend = true; // 'READ' request for sensor devices 40-49 then the gateway passes a read request to, for example, device 42 and publishes the returned sensor data on MQTT; without having to define it individually. -Demondreamer
|
|
|
Post by camblonie on Jan 25, 2015 4:31:45 GMT
Thanks Deamondreamer. I was trying to dig into the button situation with a little less luck. I've got your sketches running though you were missing a semi colon or two to get it to compile. Does the sonar require 5v? I'm getting zeros but i haven't rigged up 5v. Scott
|
|
|
Post by demondreamer on Jan 25, 2015 8:17:16 GMT
Thanks Deamondreamer. I was trying to dig into the button situation with a little less luck. I've got your sketches running though you were missing a semi colon or two to get it to compile. Does the sonar require 5v? I'm getting zeros but i haven't rigged up 5v. Scott I was afraid of that. I modified the sketch after I cut and pasted it on the forum to remove a few unrelated things I was working on. Of course I goofed. I've modified the original post to fix the typos and tested it on my gateway and node. The typo in the gateway sketch is probably why you were getting only get 0s. Somehow I put dev34 instead of dev40. I have my ultrasonic sensors running off the 5v output on the Arduino. If you still get 0 readings, try reversing the trigger and echo wires. -Demondreamer
|
|
|
Post by computourist on Jan 26, 2015 9:59:26 GMT
@ Demondreamer & Camblonie
You guys have been busy over the weekend ! I would like to propose a convention on device types. In that way the gateway code remains fixed, whilst function definition would only be in the end node. Also some error checking could be added. I would propose:
dev 0 - 10 : system devices, as already defined dev 10 - 20: digital outputs (LED's) dev 20 - 30: digital inputs (buttons) dev 30 - 50: sensors readings dev 50 - 90: specials dev 90 - 99: error reporting
Any suggestions ?
|
|
|
Post by demondreamer on Jan 26, 2015 15:37:14 GMT
@ Demondreamer & Camblonie You guys have been busy over the weekend ! I would like to propose a convention on device types. In that way the gateway code remains fixed, whilst function definition would only be in the end node. Also some error checking could be added. I would propose: dev 0 - 10 : system devices, as already defined dev 10 - 20: digital outputs (LED's) dev 20 - 30: digital inputs (buttons) dev 30 - 50: sensors readings dev 50 - 90: specials dev 90 - 99: error reporting Any suggestions ? It's a good idea to conventionalize the device address space. Pre-allocating ranges like you proposed would remove the need to modify the gateway for the most part and cover most use case scenarios. How will the specials behave in the gateway? Some suggestions for system devices: I've added a simple uptime counter as dev9 uptime = (millis() / 1000) / 60; // current millis converted to minutes
and the RFM69's onboard temperature reading as dev30, but it also should probably be in the system devices range. All RFM69s have this so why not use it? temperature = radio.readTemperature(+5); // Degrees Celsuis +5 = user cal factor, adjust for correct ambient
fTemp = 1.8 * temperature + 32; // Convert Celsius to Farenhight 9/5=1.8 Is there a way to tell the Arduino to perform a reset? -Demondreamer
|
|
|
Post by camblonie on Jan 26, 2015 16:16:09 GMT
Since we're at the start of this I suggest increasing each of the I/O allotments by ten and reduce the specials accordingly. It' would be a pretty complex Node but I can imagine my fish tank node pushing the limit. Saltwater tanks are crazy. Dd thanks for reading the data sheet. Great find!
|
|
|
Post by computourist on Jan 27, 2015 20:01:39 GMT
OK, so this will be a major rewrite:
dev 0 - 20 : system devices, as already defined and more dev 20 - 40: digital outputs (LED's) dev 40 - 60: digital inputs (buttons) dev 60 - 80: sensors readings dev 80 - 90: specials dev 90 - 99: error reporting
I will also increase the size of the character array payload (now 10 chars) to 64, so it could be used for displays etc. This will mean gateway AND node software needs upgrading, so I suggest you keep track of your 'personal' changes...
BTW: Some of the proposals you made are end-node changes (timer, temperature). I see little benefit in measuring temperature of the gateway. The gateway code already fills up most of the memory. I'll leave these changes for now and see how much memory is left after the proposed changes.
- Computourist
|
|
|
Post by iotbrau on Jan 27, 2015 22:51:03 GMT
Hi,
I just start to use computourist code and I got some problems and I don't know if anyone of you is facing the same issues...
At the RFM_DHT_NODE_17 when I send the voltage level, it arrives at the OpenHAB with a space before the number, for example, if the voltage is 4.75V at OpenHAB it is received as _4.75 and OpenHAB don't recognise it as a number. To fix that I replaced "dtostrf(mes.fltVal, 5,2, buff_mess);" by "dtostrf(mes.fltVal, 4,2, buff_mess);" at the RFM_DHT_NODE_19 line 314 and it is working now, but what will happens if the voltage is higher than 9.99?
Second problem is with the Gateway's stability. It is working only for few minutes and then freezes, sometimes after receiving 2 transmissions, sometimes after 20 with no apparent reason. I used 3 completely different sets of hardware and tested the gateway hardware as a node, and it appears that the hardware is working fine. I am using the power through the USB for the initial tests could it be the problem?
Congrats for your efforts!
EDIT: I forgot to mention that I added a simple counter at the loop() and for any reason it extended a lot the duration until it freezes, the first time it took 208,944 cycles, the second time 609,249, the third time 1,203,835, the forth time 43,975 and I just press the reset button and it starts to work again.
|
|
|
Post by demondreamer on Jan 28, 2015 1:28:53 GMT
Second problem is with the Gateway's stability. It is working only for few minutes and then freezes, sometimes after receiving 2 transmissions, sometimes after 20 with no apparent reason. I used 3 completely different sets of hardware and tested the gateway hardware as a node, and it appears that the hardware is working fine. I am using the power through the USB for the initial tests could it be the problem? Congrats for your efforts! EDIT: I forgot to mention that I added a simple counter at the loop() and for any reason it extended a lot the duration until it freezes, the first time it took 208,944 cycles, the second time 609,249, the third time 1,203,835, the forth time 43,975 and I just press the reset button and it starts to work again. That sounds a lot like the problem I had initially. Read through the Raspberry Pi <-> RFM69 (Sensor Node Gateway) thread for that story, or just click HERE for the results. -Demondreamer
|
|
|
Post by demondreamer on Jan 28, 2015 1:58:00 GMT
OK, so this will be a major rewrite: dev 0 - 20 : system devices, as already defined and more dev 20 - 40: digital outputs (LED's) dev 40 - 60: digital inputs (buttons) dev 60 - 80: sensors readings dev 80 - 90: specials dev 90 - 99: error reporting I will also increase the size of the character array payload (now 10 chars) to 64, so it could be used for displays etc. This will mean gateway AND node software needs upgrading, so I suggest you keep track of your 'personal' changes... BTW: Some of the proposals you made are end-node changes (timer, temperature). I see little benefit in measuring temperature of the gateway. The gateway code already fills up most of the memory. I'll leave these changes for now and see how much memory is left after the proposed changes. - Computourist Apologies, I think I misunderstood the definition of system devices. I wasn't suggesting adding the uptime and temp code to the gateway, rather just the code and assignation of a device number that passes the messages back and forth to the nodes, posted below. case (9): // end node uptime { dtostrf(mes.fltVal, 5,2, buff_mess);
}
break;
if (DID == 9 && mes.cmd ==1) mqttToSend = true; // 'READ' request for current uptime
case (30): // RFM69 temperature
{ dtostrf(mes.fltVal, 5,2, buff_mess);
}
break;
if (DID == 30 && mes.cmd ==1) mqttToSend = true; // 'READ' request for RFM69 temperature
-Demondreamer
|
|
|
Post by camblonie on Jan 28, 2015 3:11:35 GMT
iotbrau, thanks for the tip on the voltage. Your change to the GW_19 for voltage monitor got it going and all my power is 9v.
Deamon, the RFM temp sensor doesn't look too promising. The one implementation I have is pretty far off. Is the parameter required to calibrate each part individually?
I've been struggling with an RPi Cam. Looks like I've got a start using motion and calling a webpage in the sitemap rather than actual video or image. That was a tip on the Openhab site. Anyone else using a RPi Cam?
|
|
|
Post by iotbrau on Jan 28, 2015 22:33:30 GMT
Hi Demondreamer, I had exactly the same problem. I did the change at the W5100.h library and the gateway is running for the last 10 hours without any freezing. Thanks for that! Now it's time to add sensors. A photosensor to the DHT will be the next one. Cheers,
|
|
|
Post by camblonie on Feb 2, 2015 16:55:58 GMT
Computourist, Have you been able to update the sketches yet? Thanks, Scott
|
|