MySensors Library & Examples  2.3.2
EnergyMeterPulseSensor.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-2019 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 - Peter Andersson added millis watt calculation if time between pulses > 1h
24 
25  DESCRIPTION
26  This sketch provides an example how to implement a LM393 PCB
27  Use this sensor to measure kWh and Watt of your house meter
28  You need to set the correct pulsefactor of your meter (blinks per kWh).
29  The sensor starts by fetching current kWh value from gateway.
30  Reports both kWh and Watt back to gateway.
31 
32  Unfortunately millis() won't increment when the Arduino is in
33  sleepmode. So we cannot make this sensor sleep if we also want
34  to calculate/report watt value.
35  http://www.mysensors.org/build/pulse_power
36 */
37 
38 // Enable debug prints
39 #define MY_DEBUG
40 
41 // Enable and select radio type attached
42 #define MY_RADIO_RF24
43 //#define MY_RADIO_NRF5_ESB
44 //#define MY_RADIO_RFM69
45 //#define MY_RADIO_RFM95
46 
47 #include <MySensors.h>
48 
49 #define DIGITAL_INPUT_SENSOR 3 // The digital input you attached your light sensor. (Only 2 and 3 generates interrupt!)
50 #define PULSE_FACTOR 1000 // Number of blinks per kWh of your meter. Normally 1000.
51 #define SLEEP_MODE false // Watt value can only be reported when sleep mode is false.
52 #define MAX_WATT 10000 // Max watt value to report. This filters outliers.
53 #define CHILD_ID 1 // Id of the sensor child
54 
55 uint32_t SEND_FREQUENCY =
56  20000; // Minimum time between send (in milliseconds). We don't want to spam the gateway.
57 double ppwh = ((double)PULSE_FACTOR) / 1000; // Pulses per watt hour
58 bool pcReceived = false;
59 volatile uint32_t pulseCount = 0;
60 volatile uint32_t lastBlinkmicros = 0;
61 volatile uint32_t lastBlinkmillis = 0;
62 volatile uint32_t watt = 0;
63 uint32_t oldPulseCount = 0;
64 uint32_t oldWatt = 0;
65 double oldkWh;
66 uint32_t lastSend;
67 MyMessage wattMsg(CHILD_ID, V_WATT);
68 MyMessage kWhMsg(CHILD_ID, V_KWH);
69 MyMessage pcMsg(CHILD_ID, V_VAR1);
70 
71 #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
72 #define IRQ_HANDLER_ATTR ICACHE_RAM_ATTR
73 #else
74 #define IRQ_HANDLER_ATTR
75 #endif
76 
77 void IRQ_HANDLER_ATTR onPulse()
78 {
79  if (!SLEEP_MODE) {
80  uint32_t newBlinkmicros = micros();
81  uint32_t newBlinkmillis = millis();
82  uint32_t intervalmicros = newBlinkmicros - lastBlinkmicros;
83  uint32_t intervalmillis = newBlinkmillis - lastBlinkmillis;
84  if (intervalmicros < 10000L && intervalmillis < 10L) { // Sometimes we get interrupt on RISING
85  return;
86  }
87  if (intervalmillis < 360000) { // Less than an hour since last pulse, use microseconds
88  watt = (3600000000.0 / intervalmicros) / ppwh;
89  } else {
90  watt = (3600000.0 / intervalmillis) /
91  ppwh; // more thAn an hour since last pulse, use milliseconds as micros will overflow after 70min
92  }
93  lastBlinkmicros = newBlinkmicros;
94  lastBlinkmillis = newBlinkmillis;
95  }
96  pulseCount++;
97 }
98 
99 void setup()
100 {
101  // Fetch last known pulse count value from gw
102  request(CHILD_ID, V_VAR1);
103 
104  // Use the internal pullup to be able to hook up this sketch directly to an energy meter with S0 output
105  // If no pullup is used, the reported usage will be too high because of the floating pin
106  pinMode(DIGITAL_INPUT_SENSOR, INPUT_PULLUP);
107 
108  attachInterrupt(digitalPinToInterrupt(DIGITAL_INPUT_SENSOR), onPulse, RISING);
109  lastSend = millis();
110 }
111 
113 {
114  // Send the sketch version information to the gateway and Controller
115  sendSketchInfo(F("Energy Meter"), F("1.1"));
116 
117  // Register this device as power sensor
118  present(CHILD_ID, S_POWER);
119 }
120 
121 void loop()
122 {
123  uint32_t now = millis();
124  // Only send values at a maximum frequency or woken up from sleep
125  bool sendTime = now - lastSend > SEND_FREQUENCY;
126  if (pcReceived && (SLEEP_MODE || sendTime)) {
127  // New watt value has been calculated
128  if (!SLEEP_MODE && watt != oldWatt) {
129  // Check that we don't get unreasonable large watt value, which
130  // could happen when long wraps or false interrupt triggered
131  if (watt < ((uint32_t)MAX_WATT)) {
132  send(wattMsg.set(watt)); // Send watt value to gw
133  }
134  Serial.print("Watt:");
135  Serial.println(watt);
136  oldWatt = watt;
137  }
138 
139  // Pulse count value has changed
140  if (pulseCount != oldPulseCount) {
141  send(pcMsg.set(pulseCount)); // Send pulse count value to gw
142  double kWh = ((double)pulseCount / ((double)PULSE_FACTOR));
143  oldPulseCount = pulseCount;
144  if (kWh != oldkWh) {
145  send(kWhMsg.set(kWh, 4)); // Send kWh value to gw
146  oldkWh = kWh;
147  }
148  }
149  lastSend = now;
150  } else if (sendTime && !pcReceived) {
151  // No pulse count value received from controller. Try requesting it again.
152  request(CHILD_ID, V_VAR1);
153  lastSend = now;
154  }
155 
156  if (SLEEP_MODE) {
157  sleep(SEND_FREQUENCY, false);
158  }
159 }
160 
161 void receive(const MyMessage &message)
162 {
163  if (message.getType()==V_VAR1) {
164  pulseCount = oldPulseCount = message.getLong();
165  Serial.print("Received last pulse count value from gw:");
166  Serial.println(pulseCount);
167  pcReceived = true;
168  }
169 }
bool request(const uint8_t childSensorId, const uint8_t variableType, const uint8_t destination=GATEWAY_ADDRESS)
MyMessage is used to create, manipulate, send and read MySensors messages.
Definition: MyMessage.h:289
bool sendSketchInfo(const char *name, const char *version, const bool requestEcho=false)
API declaration for MySensors.
int32_t getLong(void) const
Get signed 32-bit integer payload.
bool send(MyMessage &msg, const bool requestEcho=false)
bool present(const uint8_t sensorId, const mysensors_sensor_t sensorType, const char *description="", const bool requestEcho=false)
void setup()
Called after node initialises but before main loop.
int8_t sleep(const uint32_t sleepingMS, const bool smartSleep=false)
void receive(const MyMessage &message)
Callback for incoming messages.
void presentation()
Node presentation.
uint8_t getType(void) const
Get message type.
#define IRQ_HANDLER_ATTR
ESP8266/ESP32 IRQ handlers need to be stored in IRAM.
Definition: MyHwHAL.h:48
MyMessage & set(const void *payload, const size_t length)
Set entire payload.
void loop()
Main loop.