MySensors Library & Examples  2.3.2-62-ge298769
WaterMeterPulseSensor.ino
1 /*
2  * The MySensors Arduino library handles the wireless radio link and protocol
3  * between your home built sensors/actuators and HA controller of choice.
4  * The sensors forms a self healing radio network with optional repeaters. Each
5  * repeater and gateway builds a routing tables in EEPROM which keeps track of the
6  * network topology allowing messages to be routed to nodes.
7  *
8  * Created by Henrik Ekblad <[email protected]>
9  * Copyright (C) 2013-2022 Sensnology AB
10  * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors
11  *
12  * Documentation: http://www.mysensors.org
13  * Support Forum: http://forum.mysensors.org
14  *
15  * This program is free software; you can redistribute it and/or
16  * modify it under the terms of the GNU General Public License
17  * version 2 as published by the Free Software Foundation.
18  *
19  *******************************
20  *
21  * REVISION HISTORY
22  * Version 1.0 - Henrik Ekblad
23  * Version 1.1 - GizMoCuz
24  * Version 1.2 - Paolo Rendano
25  * * factory reset
26  * * automatic home assistant entities creation
27  * * counter correction from home assistant using
28  * service notify.mysensors
29  * * fixed counter automatically incremented by 1
30  * at each device restart (due to arduino library
31  * interrupt bug)
32  * * other tiny improvements
33  * Version 1.3 - Paolo Rendano
34  * * change flow measurement unit to m3/h to adopt
35  * in home assistant as standard measurement unit
36  * for V_FLOW
37  *
38  * DESCRIPTION
39  * Use this sensor to measure volume and flow of your house water meter.
40  * You need to set the correct pulsefactor of your meter (pulses per m3).
41  * The sensor starts by fetching current volume reading from gateway (VAR 1).
42  * Reports both volume and flow back to gateway.
43  *
44  * Unfortunately millis() won't increment when the Arduino is in
45  * sleepmode. So we cannot make this sensor sleep if we also want
46  * to calculate/report flow.
47  * http://www.mysensors.org/build/pulse_water
48  */
49 
50 // Enable debug prints to serial monitor
51 #define MY_DEBUG
52 #define APP_DEBUG
53 
54 // Enable factory reset to REINIT the board with a different ID
55 //#define FORCE_FACTORY_RESET
56 
57 // uncomment to rejoin to a previous assigned node id
58 //#define MY_NODE_ID 58
59 
60 // Enable and select radio type attached
61 #define MY_RADIO_RF24
62 //#define MY_RADIO_NRF5_ESB
63 //#define MY_RADIO_RFM69
64 //#define MY_RADIO_RFM95
65 //#define MY_PJON
66 #define MY_SPLASH_SCREEN_DISABLED
67 
68 #include <MySensors.h>
69 
70 // The digital input you attached your sensor. (Only 2 and 3 generates interrupt!)
71 #define DIGITAL_INPUT_SENSOR 3
72 
73 // Arduino Uno/Nano: INTF0 for DIGITAL_INPUT_SENSOR = 2; INTF1 for DIGITAL_INPUT_SENSOR = 3
74 // Arduino AtMega2560: INTF0->INTF7 see datasheet based on the input you want to attach
75 #if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
76 #define DIGITAL_INPUT_SENSOR_INTF INTF1
77 #endif
78 
79 // Number of blinks per m3 of your meter (One rotation/liter)
80 #define PULSE_FACTOR 1000.0d
81 
82 // flowvalue can only be reported when sleep mode is false.
83 #define SLEEP_MODE false
84 
85 // Max flow (m3/h) value to report. This filters outliers.
86 #define MAX_FLOW 2.4d
87 
88 // Timeout (in milliseconds) to reset to 0 the flow
89 // information (assuming no pulses if no flow)
90 #define FLOW_RESET_TO_ZERO_TIMEOUT 120000
91 
92 // Id of the sensor child
93 #define CHILD_ID 1
94 
95 // Id of the sensor child for counter pulse addition
96 #define CHILD_ID_VAR1 2
97 
98 // Minimum time between send (in milliseconds). We don't want to spam the gateway.
99 #define SEND_FREQUENCY 30000
100 
101 // Save on board if the home assistant counters have been initialized
102 // (sufficient condition to see the entity in hass)
103 #define FIRST_VALUE_SENT_FLAG_POSITION 0
104 #define FIRST_VALUE_SENT_FLAG_YES 1
105 #define FIRST_VALUE_SENT_FLAG_NO 255
106 
107 MyMessage flowMsg(CHILD_ID,V_FLOW);
108 MyMessage volumeMsg(CHILD_ID,V_VOLUME);
109 MyMessage lastCounterMsg(CHILD_ID,V_VAR1);
110 MyMessage volumeAdd(CHILD_ID_VAR1,V_TEXT);
111 
112 // Pulses per liter
113 double ppl = ((double)PULSE_FACTOR)/1000;
114 
115 volatile uint32_t pulseCount = 0;
116 volatile uint32_t lastBlink = 0;
117 volatile double flow = 0;
118 bool pcReceived = false;
119 uint32_t oldPulseCount = 0;
120 double oldflow = 0;
121 double oldvolume =0;
122 uint32_t lastSend =0;
123 uint32_t lastPulse =0;
124 bool firstValuesMessageSent = false;
125 
126 void IRQ_HANDLER_ATTR onPulse()
127 {
128  if (!SLEEP_MODE) {
129  uint32_t newBlink = micros();
130  uint32_t interval = newBlink-lastBlink;
131 
132  if (interval!=0) {
133  lastPulse = millis();
134  if (interval<500000L) {
135  // Sometimes we get interrupt on RISING,
136  // 500000 = 0.5 second debounce ( max 7.2 m3/h)
137  return;
138  }
139  flow = (3600000.0 /interval) / ppl;
140  }
141  lastBlink = newBlink;
142  }
143  pulseCount++;
144 }
145 
146 void setup()
147 {
148  #ifdef FORCE_FACTORY_RESET
149  // got from mysensors clear e2p sketch
150  for (uint16_t i=0; i<EEPROM_LOCAL_CONFIG_ADDRESS; i++) {
151  hwWriteConfig(i,0xFF);
152  }
153  // reset state for this sensor
154  forceFactoryReset();
155  Serial.println("Factory reset complete");
156  while (true);
157  #endif
158 
159  // initialize our digital pins internal pullup resistor so one pulse
160  // switches from high to low (less distortion)
161  pinMode(DIGITAL_INPUT_SENSOR, INPUT_PULLUP);
162 
163  pulseCount = oldPulseCount = 0;
164 
165  // initialize home assistant values
166  checkAndFirstTimeInitValuesOnHomeAssistant();
167 
168  // Fetch last known pulse count value from gw
169  request(CHILD_ID, V_VAR1);
170 
171  lastSend = lastPulse = millis();
172 
173  // fix for arduino library bug calling ISR function
174  // at startup see https://github.com/arduino/ArduinoCore-avr/issues/244
175 #if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
176  EIFR |= (1 << DIGITAL_INPUT_SENSOR_INTF);
177 #endif
178 
179  attachInterrupt(digitalPinToInterrupt(DIGITAL_INPUT_SENSOR), onPulse, FALLING);
180 }
181 
183 {
184  // Send the sketch version information to the gateway and Controller
185  sendSketchInfo("Water Meter", "1.3");
186 
187  // Register this device as Water flow sensor
188  present(CHILD_ID, S_WATER);
189 
190  // Add info entity to manage corrections on pulse counter
191  // (on Home Assistant this can be hidden)
192  present(CHILD_ID_VAR1, S_INFO);
193 }
194 
195 void loop()
196 {
197  uint32_t currentTime = millis();
198 
199  // Only send values at a maximum frequency or woken up from sleep
200  if (SLEEP_MODE || (currentTime - lastSend > SEND_FREQUENCY)) {
201  lastSend=currentTime;
202 
203  if (!pcReceived) {
204  // Last Pulsecount not yet received from controller,
205  // request it again
206  request(CHILD_ID, V_VAR1);
207  return;
208  }
209 
210  if (!SLEEP_MODE && flow != oldflow) {
211  oldflow = flow;
212 #ifdef APP_DEBUG
213  Serial.print("m3/h:");
214  Serial.println(flow);
215 #endif
216  // Check that we don't get unreasonable large flow value.
217  // could happen when long wraps or false interrupt triggered
218  if (flow<((uint32_t)MAX_FLOW)) {
219  // Send flow value to gw
220  send(flowMsg.set(flow, 4));
221  }
222  }
223 
224  // No Pulse count received for a defined time
225  if(currentTime - lastPulse > FLOW_RESET_TO_ZERO_TIMEOUT) {
226  flow = 0;
227  }
228 
229  // Pulse count has changed
230  if ((pulseCount != oldPulseCount)||(!SLEEP_MODE)) {
231  oldPulseCount = pulseCount;
232 #ifdef APP_DEBUG
233  Serial.print("pulsecount:");
234  Serial.println(pulseCount);
235 #endif
236  // Send pulsecount value to gw in VAR1
237  send(lastCounterMsg.set(pulseCount));
238 
239  double volume = pulseCount / PULSE_FACTOR;
240  if ((volume != oldvolume)||(!SLEEP_MODE)) {
241  oldvolume = volume;
242 
243 #ifdef APP_DEBUG
244  Serial.print("volume:");
245  Serial.println(volume, 3);
246 #endif
247  // Send volume value to gw
248  send(volumeMsg.set(volume, 3));
249  }
250  }
251  }
252  if (SLEEP_MODE) {
253  sleep(SEND_FREQUENCY, false);
254  }
255 }
256 
257 // clearing eeprom with mysensors sketch is not enough since this
258 // doesn't cover the saved state values. Call this function once
259 // to force factory reset
260 void forceFactoryReset() {
261  saveState(FIRST_VALUE_SENT_FLAG_POSITION, FIRST_VALUE_SENT_FLAG_NO);
262 }
263 
264 void checkAndFirstTimeInitValuesOnHomeAssistant() {
265  // check local e2p if this sensor has already sent
266  // initial value (0). If not will send the init value
267  uint8_t state = loadState(FIRST_VALUE_SENT_FLAG_POSITION);
268  if (state==FIRST_VALUE_SENT_FLAG_NO) {
269  // never sent anything. No value in HASS is assumed
270 #ifdef APP_DEBUG
271  Serial.println("First time init");
272 #endif
273  firstValuesMessageSent = true;
274  // Send flow value to gw
275  send(flowMsg.set(0.0d, 4));
276  // Send volume value to gw
277  send(volumeMsg.set(0.0d, 3));
278  // Send pulsecount value to gw in VAR1
279  send(lastCounterMsg.set((uint32_t)0));
280  // Send volumeAdd value to gw in V_TEXT
281  send(volumeAdd.set(""));
282  }
283 }
284 
285 void receive(const MyMessage &message)
286 {
287  if (message.getType()==V_VAR1) {
288  if (firstValuesMessageSent) {
289  // ack saving value on board that HomeAssistant
290  // got first init values. Will never be sent again
291  saveState(FIRST_VALUE_SENT_FLAG_POSITION,
292  FIRST_VALUE_SENT_FLAG_YES);
293  }
294 
295  uint32_t gwPulseCount=message.getULong();
296  pulseCount += gwPulseCount;
297  flow=oldflow=0.0d;
298 
299 #ifdef APP_DEBUG
300  Serial.print("Received last pulse count from gw:");
301  Serial.println(pulseCount);
302 #endif
303 
304  pcReceived = true;
305  }
306 
307  // incoming message to correct pulsecount
308  // used to add or remove pulses to the current reading
309  if (message.getType()==V_TEXT) {
310  long val = atol(message.getString());
311  if ((val+(int32_t)pulseCount)<0) {
312  // TODO: this is not covering all the ranges problems
313 #ifdef APP_DEBUG
314  Serial.println("out of range. Won't add");
315 #endif
316  }
317  else {
318  pulseCount+=val;
319  // reset only if ok
320  send(volumeAdd.set(""));
321 #ifdef APP_DEBUG
322  Serial.print("New pulseCount: ");
323  Serial.println(pulseCount);
324 #endif
325  }
326  }
327 }
sendSketchInfo
bool sendSketchInfo(const char *name, const char *version, const bool requestEcho=false)
receive
void receive(const MyMessage &message)
Callback for incoming messages.
Definition: WaterMeterPulseSensor.ino:285
loop
void loop()
Main loop.
Definition: WaterMeterPulseSensor.ino:195
loadState
uint8_t loadState(const uint8_t pos)
MyMessage::getULong
uint32_t getULong(void) const
Get unsigned 32-bit integer payload.
presentation
void presentation()
Node presentation.
Definition: WaterMeterPulseSensor.ino:182
saveState
void saveState(const uint8_t pos, const uint8_t value)
MyMessage::set
MyMessage & set(const void *payload, const size_t length)
Set entire payload.
MyMessage::getString
char * getString(char *buffer) const
Copy the payload into the supplied buffer.
send
bool send(MyMessage &msg, const bool requestEcho=false)
EEPROM_LOCAL_CONFIG_ADDRESS
#define EEPROM_LOCAL_CONFIG_ADDRESS
First free address for sketch static configuration.
Definition: MyEepromAddresses.h:92
MyMessage::getType
uint8_t getType(void) const
Get message type.
request
bool request(const uint8_t childSensorId, const uint8_t variableType, const uint8_t destination=GATEWAY_ADDRESS)
present
bool present(const uint8_t sensorId, const mysensors_sensor_t sensorType, const char *description="", const bool requestEcho=false)
setup
void setup()
Called after node initialises but before main loop.
Definition: WaterMeterPulseSensor.ino:146
sleep
int8_t sleep(const uint32_t sleepingMS, const bool smartSleep=false)
IRQ_HANDLER_ATTR
#define IRQ_HANDLER_ATTR
ESP8266/ESP32 IRQ handlers need to be stored in IRAM.
Definition: MyHwHAL.h:52
MySensors.h
API declaration for MySensors.
MyMessage
MyMessage is used to create, manipulate, send and read MySensors messages.
Definition: MyMessage.h:290