Long range pulse water meter sensor using Moteino/LoRa RF95/MySensors/Domoticz/RasPi3
-
My first arduino project. I work at a winery where water meters are have to be checked daily, and are far apart/ hard to get to. This will make it so manual checks only need to be performed weekly. I would love for you guys to let me know how I could refine my code (It's a bit of a patchwork of MySensor examples and Felix's moteino examples, with some additions and tweaking to work with the LoRa radios). I'm also looking for input on the best way to add more nodes, as right now the reciever/gateway is only setup for sensor. Thanks for being an awesome community!
-Max
// 915MHZ Wireless Pulse Water Meter Sensor // Stag's Leap Wine Cellars // Rev. 1.0 -- Max Kilpatrick #include <EEPROM.h> #include <TimerOne.h> #include <RH_RF95.h> #define NODEID 22 //network ID used for this unit #define NETWORKID 99 //the network ID we are on #define GATEWAYID 1 //the node ID we're sending to #define LED 9 #define INPUTPIN 1 //INT1 = digital pin 3 (must be an interrupt pin!) #define RFM95_CS 10 #define RFM95_RST 9 #define RFM95_INT 2 #define SERIAL_EN //uncomment this line to enable serial IO (when you debug Moteino and need serial output) #define SERIAL_BAUD 57600 #define RF95_FREQ 915.0 RH_RF95 rf95(RFM95_CS, RFM95_INT); // Initialize radio int ledState = LOW; volatile unsigned long PulseCounterVolatile = 0; // use volatile for shared variables unsigned long PulseCounter = 0; float GAL; byte PulsesPerGAL = 1; volatile unsigned long NOW = 0; unsigned long LASTMINUTEMARK = 0; unsigned long PULSECOUNTLASTMINUTEMARK = 0; //keeps pulse count at the last minute mark byte COUNTEREEPROMSLOTS = 10; unsigned long COUNTERADDRBASE = 8; //address in EEPROM that points to the first possible slot for a counter unsigned long COUNTERADDR = 0; //address in EEPROM that points to the latest Counter in EEPROM byte secondCounter = 0; unsigned long TIMESTAMP_pulse_prev = 0; unsigned long TIMESTAMP_pulse_curr = 0; int GPMthreshold = 8000; // GPM will reset after this many MS if no pulses are registered int XMIT_Interval = 5000; // GPMthreshold should be less than 2*XMIT_Interval int pulseAVGInterval = 0; int pulsesPerXMITperiod = 0; float GPM=0; char sendBuf[RH_RF95_MAX_MESSAGE_LEN]; // RH_RF95_MAX_MESSAGE_LEN byte sendLen; long lastDebounce0 = 0; long debounceDelay = 1000; // Ensure accurate pulse count void setup() { pinMode(LED, OUTPUT); pinMode(RFM95_RST, OUTPUT); digitalWrite(RFM95_RST, HIGH); //initialize counter from EEPROM unsigned long savedCounter = EEPROM_Read_Counter(); // Unsigned long EEPROM_Read_Counter() if (savedCounter <=0) savedCounter = 1; // Avoid division by 0 PulseCounterVolatile = PulseCounter = PULSECOUNTLASTMINUTEMARK = savedCounter; attachInterrupt(INPUTPIN, pulseCounter, RISING); // Interrupt when rising edge of pulse is sensed Timer1.initialize(XMIT_Interval * 1000L); Timer1.attachInterrupt(XMIT); digitalWrite(RFM95_RST, LOW); delay(10); digitalWrite(RFM95_RST, HIGH); delay(10); if (!rf95.setFrequency(RF95_FREQ)) { Serial.println("setFrequency failed"); while (1); } Serial.print("Set Freq to: "); Serial.println(RF95_FREQ); while (!rf95.init()) { Serial.println("LoRa radio init failed"); while (1); } Serial.println("LoRa radio init OK!"); rf95.setTxPower(23); // Set power of radio #ifdef SERIAL_EN // Is serial output enabled? Serial.begin(SERIAL_BAUD); // Then set baud rate Serial.println("\nTransmitting..."); #endif } void XMIT(void) { noInterrupts(); PulseCounter = PulseCounterVolatile; interrupts(); if (millis() - TIMESTAMP_pulse_curr >= 5000) // Change LED state if over 5 seconds { ledState = !ledState; digitalWrite(LED, ledState); } // Calculate Gallons counter GAL = ((float)PulseCounter)/PulsesPerGAL; // Print current pulse and gallon count to serial Serial.print("PulseCounter:");Serial.print(PulseCounter);Serial.print(", GAL: "); Serial.println(GAL); String tempstr = String(""); // tempstr eventually gets sent through radio to reciever tempstr += (unsigned long)GAL; // It contains the flow and volume date in the form of a string tempstr += '.'; tempstr += ((unsigned long)(GAL * 100)) % 100; tempstr += ','; // Calculate & output GPM GPM = pulseAVGInterval > 0 ? 60.0 * 1000 * (1.0/PulsesPerGAL)/(pulseAVGInterval/pulsesPerXMITperiod) : 0; pulsesPerXMITperiod = 0; pulseAVGInterval = 0; tempstr += " "; tempstr += (int)GPM; tempstr += '.'; tempstr += ((int)(GPM * 100)) % 100; tempstr += '!'; secondCounter += XMIT_Interval/1000; // Once per minute, output a GallonsLastMinute count // This is not currently needed, keeping for later possible use if (secondCounter>=60) { secondCounter=0; //tempstr += " GLM:"; float GLM = ((float)(PulseCounter - PULSECOUNTLASTMINUTEMARK))/PulsesPerGAL; // tempstr += (int)GLM; // tempstr += '.'; // tempstr += ((int)(GLM * 100)) % 100; PULSECOUNTLASTMINUTEMARK = PulseCounter; EEPROM_Write_Counter(PulseCounter); } // Make char array from tempstr (Flow and Vol data) tempstr.toCharArray(sendBuf, RH_RF95_MAX_MESSAGE_LEN); sendLen = tempstr.length(); // Send data to reciever and print to serial rf95.send(sendBuf, sizeof(sendBuf)); rf95.waitPacketSent(); Serial.println(tempstr); } void loop() {} void pulseCounter(void) { if( (millis() - lastDebounce0) > debounceDelay){ // Make sure we aren't double counting pulses noInterrupts(); ledState = !ledState; PulseCounterVolatile++; // increase when LED turns on digitalWrite(LED, ledState); NOW = millis(); //remember how long between pulses (sliding window) TIMESTAMP_pulse_prev = TIMESTAMP_pulse_curr; TIMESTAMP_pulse_curr = NOW; if (TIMESTAMP_pulse_curr - TIMESTAMP_pulse_prev > GPMthreshold) //more than 'GPMthreshold' seconds passed since last pulse... resetting GPM pulsesPerXMITperiod=pulseAVGInterval=0; else { pulsesPerXMITperiod++; pulseAVGInterval += TIMESTAMP_pulse_curr - TIMESTAMP_pulse_prev; } lastDebounce0 = millis(); interrupts(); } } // EEPROM deals with writing to memory, keep in mind there are a limited amount of writes unsigned long EEPROM_Read_Counter() { return EEPROM_Read_ULong(EEPROM_Read_ULong(COUNTERADDR)); } void EEPROM_Write_Counter(unsigned long counterNow) { if (counterNow == EEPROM_Read_Counter()) { Serial.print("{EEPROM-SKIP(no changes)}"); return; // Skip write if nothing changed, extends life of memory } Serial.print("{EEPROM-SAVE("); Serial.print(EEPROM_Read_ULong(COUNTERADDR)); Serial.print(")="); Serial.print(PulseCounter); Serial.print("}"); unsigned long CounterAddr = EEPROM_Read_ULong(COUNTERADDR); if (CounterAddr == COUNTERADDRBASE+8*(COUNTEREEPROMSLOTS-1)) CounterAddr = COUNTERADDRBASE; else CounterAddr += 8; EEPROM_Write_ULong(CounterAddr, counterNow); EEPROM_Write_ULong(COUNTERADDR, CounterAddr); } unsigned long EEPROM_Read_ULong(int address) { unsigned long temp; for (byte i=0; i<8; i++) temp = (temp << 8) + EEPROM.read(address++); return temp; } void EEPROM_Write_ULong(int address, unsigned long data) { for (byte i=0; i<8; i++) { EEPROM.write(address+7-i, data); data = data >> 8; } }``` ```// 915MHZ Wireless Sensor Reciever // Stag's Leap Wine Cellars // Rev. 1.0 -- Max Kilpatrick #define MY_GATEWAY_SERIAL // MyMessage is sent through serial #define NODE_ID 1 // Which node is this #include <MySensors.h> #include <SPI.h> #include <RH_RF95.h> MyMessage msg(NODE_ID, V_FLOW); // Set's up mesaaging protocol to reciever, lets it know what variables are used MyMessage vmsg(NODE_ID,V_VOLUME); // V_FLOW means that the data being sent is a flowrate, etc. RH_RF95 rf95; // Initialize radio String str; // Strings for parsing data String str1; String str2; int led = 9; // Location of LED char array[20]; // Array for parsing data float vol; // Volume data stored here float flow; // Flow rate data stored here void setup() // Setup serial output, etc. { pinMode(led, OUTPUT); // Make LED blink Serial.begin(115200); // Sets baud rate while (!Serial) ; // Wait for serial port to be available if (!rf95.init()) // Checks if radio is working Serial.println("init failed"); } void presentation() // Tell controller how to handle input { sendSketchInfo("Water Meter", "1.1"); // Tells controller (domoticz) name of program present(NODE_ID, S_WATER); // Tells controller which node data is coming from, as well as how to view the data } void loop() { String str1 = ""; // Strings for parsing data from sensor String str2 = ""; if (rf95.available()) // When input from radio comes in { // Should be a message for us now uint8_t buf[RH_RF95_MAX_MESSAGE_LEN]; // Load incoming radio data to buffer uint8_t len = sizeof(buf); // Determine size of buffer char array[len]; // Array for parsing incoming data strcpy(array, (char*)buf); // Copy buffer into array for(int i = 0; array[i] != 44; i++){ // Copy data from array into a string until a comma is reached str1 += array[i]; // This string has the data for the total gallons used } int str_len = str1.length() + 1; // Determine size of first string (where to start reading next data) for(int i = str_len + 1; array[i] != 33; i++){ // Copy data from array into another string until end of flow data (denote end of data with !) str2 += array[i]; // This string has data for current flow rate } float vol = str1.toFloat(); // Converts data from string to floating point number send(vmsg.set(vol, 2)); // Send volume data to controller with 2 decimal points float flow = str2.toFloat(); send(msg.set(flow, 2)); // Serial.println(flow); <-----// Prints flow and vol to serial: uncomment for debugging purposes // Serial.println(vol); if (rf95.recv(buf, &len)) { digitalWrite(led, HIGH); // Turns on LED // RH_RF95::printBuffer("request: ", buf, len); // Acknowledge request from reciever, not necessary, but leaving for futur use // Serial.print("got request: "); // Serial.println((char*)buf); // Prints what is in buffer // Serial.print("RSSI: "); // Signal strength, uncomment for testing (along with line below) // Serial.println(rf95.lastRssi(), DEC); // Send a reply uint8_t data[] = "Yo, I got the data."; //Sends reply to sensor, leaving for testing purposes rf95.send(data, sizeof(data)); rf95.waitPacketSent(); Serial.println("Sent a reply"); digitalWrite(led, LOW); // Turns off the LED } else { Serial.println("recv failed"); // Lets us know if not recieving messages } } }
-
Welcome @maxk1236
Try to inline the code in the post instead of adding it as an attachment. To messy for people to pick it up and read.
-
@hek Fixed, thanks. Forgot about the nice formatting here,
-
To refine your code:
Try to use arduino IDEs "Tab"-Function. Add few tabs to organise your code.
e.g. Tab "Network" with functions like init_rf() and just call this function from your setup() or before() method. Same with your actual sensors. Consider making an extra tab to put all the relevant functions there.I try to keep my "Main" tab as clean as possible.
As I understand your code right your rf95 receiver acts as a gateway to mysensors
MyMessage msg(NODE_ID, V_FLOW); // Set's up mesaaging protocol to reciever, lets it know what variables are used MyMessage vmsg(NODE_ID,V_VOLUME); // V_FLOW means that the data being sent is a flowrate, etc.
All your Sensor-Nodes who send to your receiver-serial-gateway should have different IDs and your gateway should use these IDs to send the sensor date to your controller.
So NODE_ID should be a variable depending on "from which node" you got your sensor date.
Have a look at "MyMessage.h"
// Setters for building message "on the fly" MyMessage& setType(uint8_t type); MyMessage& setSensor(uint8_t sensor); MyMessage& setDestination(uint8_t destination);
You can change the message Sensor (your actual sensor no the gateway) dynamically on your gateway depending on the payload you get and from which sensor.
Suggested Topics
-
Welcome
Announcements • • hek