Skip to content

Commit bf79d13

Browse files
WaterPulseSensor example improvements (mysensors#1540)
1 parent 093afa0 commit bf79d13

File tree

1 file changed

+159
-25
lines changed

1 file changed

+159
-25
lines changed

examples/WaterMeterPulseSensor/WaterMeterPulseSensor.ino

Lines changed: 159 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@
2121
* REVISION HISTORY
2222
* Version 1.0 - Henrik Ekblad
2323
* 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
2433
*
2534
* DESCRIPTION
2635
* Use this sensor to measure volume and flow of your house water meter.
@@ -36,34 +45,68 @@
3645

3746
// Enable debug prints to serial monitor
3847
#define MY_DEBUG
48+
#define APP_DEBUG
49+
50+
// Enable factory reset to REINIT the board with a different ID
51+
//#define FORCE_FACTORY_RESET
52+
53+
// uncomment to rejoin to a previous assigned node id
54+
//#define MY_NODE_ID 58
3955

4056
// Enable and select radio type attached
4157
#define MY_RADIO_RF24
4258
//#define MY_RADIO_NRF5_ESB
4359
//#define MY_RADIO_RFM69
4460
//#define MY_RADIO_RFM95
4561
//#define MY_PJON
62+
#define MY_SPLASH_SCREEN_DISABLED
4663

4764
#include <MySensors.h>
4865

49-
#define DIGITAL_INPUT_SENSOR 3 // The digital input you attached your sensor. (Only 2 and 3 generates interrupt!)
66+
// The digital input you attached your sensor. (Only 2 and 3 generates interrupt!)
67+
#define DIGITAL_INPUT_SENSOR 3
68+
69+
// Arduino Uno/Nano: INTF0 for DIGITAL_INPUT_SENSOR = 2; INTF1 for DIGITAL_INPUT_SENSOR = 3
70+
// Arduino AtMega2560: INTF0->INTF7 see datasheet based on the input you want to attach
71+
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
72+
#define DIGITAL_INPUT_SENSOR_INTF INTF1
73+
#endif
74+
75+
// Number of blinks per m3 of your meter (One rotation/liter)
76+
#define PULSE_FACTOR 1000.0d
77+
78+
// flowvalue can only be reported when sleep mode is false.
79+
#define SLEEP_MODE false
80+
81+
// Max flow (l/min) value to report. This filters outliers.
82+
#define MAX_FLOW 40
5083

51-
#define PULSE_FACTOR 1000 // Number of blinks per m3 of your meter (One rotation/liter)
84+
// Timeout (in milliseconds) to reset to 0 the flow
85+
// information (assuming no pulses if no flow)
86+
#define FLOW_RESET_TO_ZERO_TIMEOUT 120000
5287

53-
#define SLEEP_MODE false // flowvalue can only be reported when sleep mode is false.
88+
// Id of the sensor child
89+
#define CHILD_ID 1
5490

55-
#define MAX_FLOW 40 // Max flow (l/min) value to report. This filters outliers.
91+
// Id of the sensor child for counter pulse addition
92+
#define CHILD_ID_VAR1 2
5693

57-
#define CHILD_ID 1 // Id of the sensor child
94+
// Minimum time between send (in milliseconds). We don't want to spam the gateway.
95+
#define SEND_FREQUENCY 30000
5896

59-
uint32_t SEND_FREQUENCY =
60-
30000; // Minimum time between send (in milliseconds). We don't want to spam the gateway.
97+
// Save on board if the home assistant counters have been initialized
98+
// (sufficient condition to see the entity in hass)
99+
#define FIRST_VALUE_SENT_FLAG_POSITION 0
100+
#define FIRST_VALUE_SENT_FLAG_YES 1
101+
#define FIRST_VALUE_SENT_FLAG_NO 255
61102

62103
MyMessage flowMsg(CHILD_ID,V_FLOW);
63104
MyMessage volumeMsg(CHILD_ID,V_VOLUME);
64105
MyMessage lastCounterMsg(CHILD_ID,V_VAR1);
106+
MyMessage volumeAdd(CHILD_ID_VAR1,V_TEXT);
65107

66-
double ppl = ((double)PULSE_FACTOR)/1000; // Pulses per liter
108+
// Pulses per liter
109+
double ppl = ((double)PULSE_FACTOR)/1000;
67110

68111
volatile uint32_t pulseCount = 0;
69112
volatile uint32_t lastBlink = 0;
@@ -74,6 +117,7 @@ double oldflow = 0;
74117
double oldvolume =0;
75118
uint32_t lastSend =0;
76119
uint32_t lastPulse =0;
120+
bool firstValuesMessageSent = false;
77121

78122
void IRQ_HANDLER_ATTR onPulse()
79123
{
@@ -84,7 +128,8 @@ void IRQ_HANDLER_ATTR onPulse()
84128
if (interval!=0) {
85129
lastPulse = millis();
86130
if (interval<500000L) {
87-
// Sometimes we get interrupt on RISING, 500000 = 0.5 second debounce ( max 120 l/min)
131+
// Sometimes we get interrupt on RISING,
132+
// 500000 = 0.5 second debounce ( max 120 l/min)
88133
return;
89134
}
90135
flow = (60000000.0 /interval) / ppl;
@@ -96,26 +141,51 @@ void IRQ_HANDLER_ATTR onPulse()
96141

97142
void setup()
98143
{
99-
// initialize our digital pins internal pullup resistor so one pulse switches from high to low (less distortion)
144+
#ifdef FORCE_FACTORY_RESET
145+
// got from mysensors clear e2p sketch
146+
for (uint16_t i=0; i<EEPROM_LOCAL_CONFIG_ADDRESS; i++) {
147+
hwWriteConfig(i,0xFF);
148+
}
149+
// reset state for this sensor
150+
forceFactoryReset();
151+
Serial.println("Factory reset complete");
152+
while (true);
153+
#endif
154+
155+
// initialize our digital pins internal pullup resistor so one pulse
156+
// switches from high to low (less distortion)
100157
pinMode(DIGITAL_INPUT_SENSOR, INPUT_PULLUP);
101158

102159
pulseCount = oldPulseCount = 0;
103160

161+
// initialize home assistant values
162+
checkAndFirstTimeInitValuesOnHomeAssistant();
163+
104164
// Fetch last known pulse count value from gw
105165
request(CHILD_ID, V_VAR1);
106166

107167
lastSend = lastPulse = millis();
108168

169+
// fix for arduino library bug calling ISR function
170+
// at startup see https://github.com/arduino/ArduinoCore-avr/issues/244
171+
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
172+
EIFR |= (1 << DIGITAL_INPUT_SENSOR_INTF);
173+
#endif
174+
109175
attachInterrupt(digitalPinToInterrupt(DIGITAL_INPUT_SENSOR), onPulse, FALLING);
110176
}
111177

112178
void presentation()
113179
{
114180
// Send the sketch version information to the gateway and Controller
115-
sendSketchInfo("Water Meter", "1.1");
181+
sendSketchInfo("Water Meter", "1.2");
116182

117183
// Register this device as Water flow sensor
118184
present(CHILD_ID, S_WATER);
185+
186+
// Add info entity to manage corrections on pulse counter
187+
// (on Home Assistant this can be hidden)
188+
present(CHILD_ID_VAR1, S_INFO);
119189
}
120190

121191
void loop()
@@ -127,46 +197,51 @@ void loop()
127197
lastSend=currentTime;
128198

129199
if (!pcReceived) {
130-
//Last Pulsecount not yet received from controller, request it again
200+
// Last Pulsecount not yet received from controller,
201+
// request it again
131202
request(CHILD_ID, V_VAR1);
132203
return;
133204
}
134205

135206
if (!SLEEP_MODE && flow != oldflow) {
136207
oldflow = flow;
137-
208+
#ifdef APP_DEBUG
138209
Serial.print("l/min:");
139210
Serial.println(flow);
140-
211+
#endif
141212
// Check that we don't get unreasonable large flow value.
142213
// could happen when long wraps or false interrupt triggered
143214
if (flow<((uint32_t)MAX_FLOW)) {
144-
send(flowMsg.set(flow, 2)); // Send flow value to gw
215+
// Send flow value to gw
216+
send(flowMsg.set(flow, 2));
145217
}
146218
}
147219

148-
// No Pulse count received in 2min
149-
if(currentTime - lastPulse > 120000) {
220+
// No Pulse count received for a defined time
221+
if(currentTime - lastPulse > FLOW_RESET_TO_ZERO_TIMEOUT) {
150222
flow = 0;
151223
}
152224

153225
// Pulse count has changed
154226
if ((pulseCount != oldPulseCount)||(!SLEEP_MODE)) {
155227
oldPulseCount = pulseCount;
156-
228+
#ifdef APP_DEBUG
157229
Serial.print("pulsecount:");
158230
Serial.println(pulseCount);
231+
#endif
232+
// Send pulsecount value to gw in VAR1
233+
send(lastCounterMsg.set(pulseCount));
159234

160-
send(lastCounterMsg.set(pulseCount)); // Send pulsecount value to gw in VAR1
161-
162-
double volume = ((double)pulseCount/((double)PULSE_FACTOR));
235+
double volume = pulseCount / PULSE_FACTOR;
163236
if ((volume != oldvolume)||(!SLEEP_MODE)) {
164237
oldvolume = volume;
165238

239+
#ifdef APP_DEBUG
166240
Serial.print("volume:");
167241
Serial.println(volume, 3);
168-
169-
send(volumeMsg.set(volume, 3)); // Send volume value to gw
242+
#endif
243+
// Send volume value to gw
244+
send(volumeMsg.set(volume, 3));
170245
}
171246
}
172247
}
@@ -175,15 +250,74 @@ void loop()
175250
}
176251
}
177252

253+
// clearing eeprom with mysensors sketch is not enough since this
254+
// doesn't cover the saved state values. Call this function once
255+
// to force factory reset
256+
void forceFactoryReset() {
257+
saveState(FIRST_VALUE_SENT_FLAG_POSITION, FIRST_VALUE_SENT_FLAG_NO);
258+
}
259+
260+
void checkAndFirstTimeInitValuesOnHomeAssistant() {
261+
// check local e2p if this sensor has already sent
262+
// initial value (0). If not will send the init value
263+
uint8_t state = loadState(FIRST_VALUE_SENT_FLAG_POSITION);
264+
if (state==FIRST_VALUE_SENT_FLAG_NO) {
265+
// never sent anything. No value in HASS is assumed
266+
#ifdef APP_DEBUG
267+
Serial.println("First time init");
268+
#endif
269+
firstValuesMessageSent = true;
270+
// Send flow value to gw
271+
send(flowMsg.set(0.0d, 2));
272+
// Send volume value to gw
273+
send(volumeMsg.set(0.0d, 3));
274+
// Send pulsecount value to gw in VAR1
275+
send(lastCounterMsg.set((uint32_t)0));
276+
// Send volumeAdd value to gw in V_TEXT
277+
send(volumeAdd.set(""));
278+
}
279+
}
280+
178281
void receive(const MyMessage &message)
179282
{
180283
if (message.getType()==V_VAR1) {
284+
if (firstValuesMessageSent) {
285+
// ack saving value on board that HomeAssistant
286+
// got first init values. Will never be sent again
287+
saveState(FIRST_VALUE_SENT_FLAG_POSITION,
288+
FIRST_VALUE_SENT_FLAG_YES);
289+
}
290+
181291
uint32_t gwPulseCount=message.getULong();
182292
pulseCount += gwPulseCount;
183-
flow=oldflow=0;
293+
flow=oldflow=0.0d;
294+
295+
#ifdef APP_DEBUG
184296
Serial.print("Received last pulse count from gw:");
185297
Serial.println(pulseCount);
298+
#endif
299+
186300
pcReceived = true;
187301
}
188-
}
189302

303+
// incoming message to correct pulsecount
304+
// used to add or remove pulses to the current reading
305+
if (message.getType()==V_TEXT) {
306+
long val = atol(message.getString());
307+
if ((val+(int32_t)pulseCount)<0) {
308+
// TODO: this is not covering all the ranges problems
309+
#ifdef APP_DEBUG
310+
Serial.println("out of range. Won't add");
311+
#endif
312+
}
313+
else {
314+
pulseCount+=val;
315+
// reset only if ok
316+
send(volumeAdd.set(""));
317+
#ifdef APP_DEBUG
318+
Serial.print("New pulseCount: ");
319+
Serial.println(pulseCount);
320+
#endif
321+
}
322+
}
323+
}

0 commit comments

Comments
 (0)