forked from oh2mp/esp32_energymeter
-
Notifications
You must be signed in to change notification settings - Fork 0
/
esp32_energymeter.ino
296 lines (253 loc) · 10.1 KB
/
esp32_energymeter.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
/*
* ESP32 based BLE beacon for energy meters
*
* See https://github.com/oh2mp/esp32_energymeter
*
*/
#include "BLEDevice.h"
#include "BLEUtils.h"
#include "BLEBeacon.h"
#include <Preferences.h>
extern "C" {
#include "esp_partition.h"
#include "esp_err.h"
#include "nvs_flash.h"
#include "nvs.h"
}
#define ENERGY_PULSE_PIN 13 // Define the pin for the energy pulse input
#define LED 2 // Onboard led pin on devkit
#define PULSE 13 // Pin where meter S0+ is connected
#define RESETBUTTON 15 // Pin where reset button is connected
#define PULSE_FACTOR 1000 // Number of blinks per kwh of your meter. Normally 1000.
#define MAX_WATT 3680 // Theoretical max watt value you will consume (230V@16A=3680W) for filtering.
#define SEND_FREQ 5000 // milliseconds how often to advertise
#define SAVE_FREQ 60000 // milliseconds how often save pulse counter to flash (see README)
#define DEEP_SLEEP_TIME 600 // Define the deep sleep time in seconds
#define PULSE_AWAKE_TIME 600000 // Define the time in milliseconds to stay awake after a pulse
#define FIRST_PULSE_TIMEOUT 10000 // Define the time to wait for the first pulse after boot
nvs_handle nvsh;
double ppwh = ((double)PULSE_FACTOR) / 1000; // Pulses per watt hour
volatile uint32_t pulse_total = 0;
volatile uint32_t pulse_reset = 0;
volatile uint32_t last_pulse = 0;
uint32_t old_pulse_total = 0;
uint32_t savetime_pulse_total = 0;
float kwh;
float old_kwh;
float curr_kwh;
uint32_t last_sent = 0;
uint32_t last_saved = 0;
// Sometimes we get pulse interrupt on wrong edge. Calculate theoretical minimum pulse time to filter them out.
const uint32_t min_pulsetime = int(MAX_WATT/3600.0/PULSE_FACTOR*10000)*100;
bool buttonpressed = false;
uint32_t buttontime = 0;
char serialbuffer[12];
BLEAdvertising *advertising;
std::string mfdata = "";
/* ----------------------------------------------------------------------------------
* Set up data packet for advertising
*/
void set_beacon() {
BLEBeacon beacon = BLEBeacon();
BLEAdvertisementData advdata = BLEAdvertisementData();
BLEAdvertisementData scanresponse = BLEAdvertisementData();
advdata.setFlags(0x06); // BR_EDR_NOT_SUPPORTED 0x04 & LE General discoverable 0x02
uint32_t wh = int(kwh*1000);
mfdata = "";
mfdata += (char)0xE5; mfdata += (char)0x02; // Espressif Incorporated Vendor ID = 0x02E5
mfdata += (char)0xDC; mfdata += (char)0xAC; // Identifier for this sketch is 0xACDC
mfdata += (char)wh & 0xFF;
mfdata += (char)(wh >> 8) & 0xFF;
mfdata += (char)(wh >> 16) & 0xFF;
mfdata += (char)(wh >> 24) & 0xFF;
wh = int(curr_kwh*1000);
mfdata += (char)wh & 0xFF;
mfdata += (char)(wh >> 8) & 0xFF;
mfdata += (char)(wh >> 16) & 0xFF;
mfdata += (char)(wh >> 24) & 0xFF;
mfdata += (char)0xBE; mfdata += (char)0xEF; // Beef is always good nutriment
advdata.setManufacturerData(mfdata);
advertising->setAdvertisementData(advdata);
advertising->setScanResponseData(scanresponse);
}
/* ---------------------------------------------------------------------------------- */
// Initialize ESP non-volatile storage (NVS)
bool nvs_init() {
esp_err_t err = nvs_flash_init();
if (err != ESP_OK) {
Serial.println("NVS init failed");
if (err != ESP_ERR_NVS_NO_FREE_PAGES) return false;
Serial.println("Reformat NVS");
const esp_partition_t *nvs_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, NULL);
if (nvs_partition == NULL)
return false;
err = esp_partition_erase_range(nvs_partition, 0, nvs_partition->size);
err = nvs_flash_init();
if (err)
return false;
Serial.println("NVS-partition re-formatted");
err = nvs_set_u32(nvsh, "pulsetotal", (uint32_t)pulse_total);
err = nvs_set_u32(nvsh, "pulsereset", (uint32_t)pulse_reset);
err = nvs_commit(nvsh);
}
err = nvs_open("nvs", NVS_READWRITE, &nvsh);
if (err != ESP_OK) return false;
return true;
}
/* ---------------------------------------------------------------------------------- */
void ICACHE_RAM_ATTR onPulse() {
uint32_t now = micros();
if (now - last_pulse < min_pulsetime) return; // Sometimes we get interrupt on wrong edge
last_pulse = now;
pulse_total++;
}
/* ---------------------------------------------------------------------------------- */
void blinker() {
for (int i = 0; i < 5; i++) {
digitalWrite(LED, HIGH);
delay(100);
digitalWrite(LED, LOW);
delay(100);
}
}
/* ---------------------------------------------------------------------------------- */
void setup() {
pinMode(LED, OUTPUT);
digitalWrite(LED, LOW); // LED off
pinMode(PULSE, INPUT_PULLUP);
attachInterrupt(PULSE, onPulse, RISING);
pinMode(RESETBUTTON, INPUT_PULLUP);
Serial.begin(115200);
Serial.println("ESP32 energymeter");
memset(serialbuffer, 0, sizeof(serialbuffer));
nvs_init();
uint32_t foo; // pulse_total and reset are volatile, so we must do it this way
esp_err_t err = nvs_get_u32(nvsh, "pulsetotal", &foo);
if (err == ESP_OK) {pulse_total = foo;}
err = nvs_get_u32(nvsh, "pulsereset", &foo);
if (err == ESP_OK) {pulse_reset = foo;}
BLEDevice::init("ESP32+energymeter");
advertising = BLEDevice::getAdvertising();
last_sent = millis();
// fake values for debugging
// pulse_total = 900;
// pulse_reset = 0;
}
/* ---------------------------------------------------------------------------------- */
void loop() {
if (Serial.available() > 0) {
char c = Serial.read();
if (c == '\n' && strlen(serialbuffer) > 0) {
pulse_total = atoi(serialbuffer);
if (pulse_reset > pulse_total) pulse_reset = pulse_total;
Serial.printf("New total pulse count from serial: %d\n", pulse_total);
memset(serialbuffer, 0, sizeof(serialbuffer));
} else if (c >= '0' && c <= '9') {
serialbuffer[strlen(serialbuffer)] = c;
} else {
memset(serialbuffer, 0, sizeof(serialbuffer));
}
if (strlen(serialbuffer) > 10) {
memset(serialbuffer, 0, sizeof(serialbuffer));
}
}
if (digitalRead(RESETBUTTON) == LOW) {
if (buttonpressed == false) {
buttonpressed = true;
buttontime = millis();
}
if (millis() - buttontime > 5000 && buttonpressed == true) {
blinker();
pulse_reset = pulse_total;
Serial.printf("Reset. Pulse counter = %d\n",pulse_reset);
esp_err_t err = nvs_set_u32(nvsh, "pulsetotal", (uint32_t)pulse_total);
err = nvs_set_u32(nvsh, "pulsereset", (uint32_t)pulse_reset);
err = nvs_commit(nvsh);
ESP.restart();
}
} else {
buttonpressed = false;
}
if (millis() - last_sent > SEND_FREQ) {
if (pulse_total != old_pulse_total) {
kwh = pulse_total / (float)PULSE_FACTOR;
old_pulse_total = pulse_total;
if (kwh != old_kwh) {
old_kwh = kwh;
}
curr_kwh = (pulse_total - pulse_reset) / (float)PULSE_FACTOR;
}
last_sent = millis();
Serial.printf("Pulsetotal:%d Lastreset:%d kWh tot:%.1f kWh: %.1f\n", pulse_total, pulse_reset, kwh, curr_kwh);
set_beacon();
digitalWrite(LED, HIGH); // LED on during the advertising
advertising->start();
delay(100);
advertising->stop();
digitalWrite(LED, LOW); // LED off
}
if (millis() - last_saved > SAVE_FREQ) {
if (savetime_pulse_total != pulse_total) {
savetime_pulse_total = pulse_total;
esp_err_t err = nvs_set_u32(nvsh, "pulsetotal", (uint32_t)pulse_total);
err = nvs_set_u32(nvsh, "pulsereset", (uint32_t)pulse_reset);
err = nvs_commit(nvsh);
last_saved = millis();
Serial.printf("Saved pulse count to NVS: %d\n",pulse_total);
}
}
// Reboot once in hour to be sure and prevent micros() to overflow at about 70 minutes
if (millis() > 3.6E+6) {
esp_err_t err = nvs_set_u32(nvsh, "pulsetotal", (uint32_t)pulse_total);
err = nvs_set_u32(nvsh, "pulsereset", (uint32_t)pulse_reset);
err = nvs_commit(nvsh);
ESP.restart();
}
}
/* ---------------------------------------------------------------------------------- */
// Initialize the energy pulse counter
volatile unsigned long pulseCount = 0;
// Define the interrupt function for the energy pulse
void IRAM_ATTR onEnergyPulse() {
pulseCount++;
}
// Define the function to put the ESP32 into deep sleep
void goToDeepSleep() {
// Disable the energy pulse interrupt
detachInterrupt(ENERGY_PULSE_PIN);
// Print the pulse count before going to sleep
Serial.printf("Going to sleep. Total pulse count: %lu\n", pulseCount);
// Configure the timer to wake up from deep sleep
esp_sleep_enable_timer_wakeup(PULSE_AWAKE_TIME * 1000);
// Configure the ESP32 to go to deep sleep
esp_deep_sleep_start();
}
void setup() {
// Start the serial communication
Serial.begin(115200);
// Configure the energy pulse pin as an input
pinMode(ENERGY_PULSE_PIN, INPUT_PULLUP);
// Attach an interrupt to the energy pulse pin
attachInterrupt(digitalPinToInterrupt(ENERGY_PULSE_PIN), onEnergyPulse, RISING);
// Wait for the first pulse after boot
delay(FIRST_PULSE_TIMEOUT);
// Check if any pulses have been counted since boot
if (pulseCount == 0) {
// If no pulses have been counted, go to deep sleep
goToDeepSleep();
}
}
void loop() {
// Check if the pulse count has changed
if (pulseCount > 0) {
// If a pulse has been detected, print the pulse count
Serial.printf("Pulse detected. Total pulse count: %lu\n", pulseCount);
// Stay awake for the duration of the pulse
delay(PULSE_AWAKE_TIME);
// Clear the pulse count
pulseCount = 0;
} else {
// If no pulses have been detected, go to deep sleep
goToDeepSleep();
}
}