Subversion Repositories ESP32_P1_Meter

Rev

Rev 5 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
5 raymond 1
////////////////////////////////  Include libraries  /////////////////////////////////////////
2
#include <Arduino.h>
4 raymond 3
#include <ArduinoOTA.h>
4
#include <EEPROM.h>
5
#include <FS.h>
6
#include <LittleFS.h>
5 raymond 7
#include <ezButton.h>
8
#include <PubSubClient.h>              // https://github.com/knolleary/pubsubclient/
9
#include <AsyncFsWebServer.h>          //https://github.com/cotestatnt/async-esp-fs-webserver
6 raymond 10
#include <HardwareSerial.h>
4 raymond 11
 
5 raymond 12
////////////////////////////////  Include settings  /////////////////////////////////////////
4 raymond 13
#include "settings.h"
14
 
5 raymond 15
////////////////////////////////  Define constants  /////////////////////////////////////////
16
#ifndef LED_BUILTIN
17
#define LED_BUILTIN 2
18
#endif
4 raymond 19
#define FILESYSTEM LittleFS
5 raymond 20
#define MYTZ "CET-1CEST,M3.5.0,M10.5.0/3"          // Timezone definition to get properly time from NTP server
4 raymond 21
#ifndef Default_MQTT_Port
5 raymond 22
  #define Default_MQTT_Port "1883"
4 raymond 23
#endif
24
#define BTN_SAVE  5
5 raymond 25
// added labels for /setup webpage
26
#define USER_LABEL "Username"
27
#define PASSWORD_LABEL "Password"
28
#define EXTERNAL_ANTENNA_LABEL "External (Extra Internal) Antenna"
4 raymond 29
 
30
#define MQTT_ENABLED_LABEL "MQTT Enabled"
31
#define MQTT_CLIENT_LABEL "MQTT Client"
32
#define MQTT_SERVER_LABEL "MQTT Server"
33
#define MQTT_PORT_LABEL "MQTT Server Port"
34
#define MQTT_USER_LABEL "MQTT User"
35
#define MQTT_PASSWORD_LABEL "MQTT Password"
6 raymond 36
#define MQTT_PREFIX_LABEL "MQTT Prefix"
4 raymond 37
 
5 raymond 38
#define SHORT_PRESS_TIME_MIN 1000 // 1 second
39
#define SHORT_PRESS_TIME_MAX 2500 // 2.5 seconds
40
#define LONG_PRESS_TIME  10000 // 10 seconds
41
 
42
const uint8_t logo_png[] PROGMEM = {
43
  0x89, 0x50, 0x4E, 0x47, /* ... */
44
};
45
 
46
////////////////////////////////  Create Objects  /////////////////////////////////////////
4 raymond 47
struct tm Time;
6 raymond 48
 
49
HardwareSerial mySerial(0); // UART0 (Serial0)
50
 
5 raymond 51
AsyncFsWebServer server(FILESYSTEM, 80, "P1_Meter");
52
ezButton button(9); // create ezButton object that attach to pin GPIO9
4 raymond 53
 
6 raymond 54
WiFiClient espClient;
55
PubSubClient mqttClient(espClient);
4 raymond 56
 
6 raymond 57
////////////////////////////////  Define variables  /////////////////////////////////////////
5 raymond 58
bool captiveRun = false;
59
// Setup Settings
60
String Username = "admin";
61
String Password = "admin";
62
bool External_Antenna = true;
6 raymond 63
 
5 raymond 64
// MQTT Settings
65
bool MQTT_Enabled = false;
66
String MQTT_Client = "ESP32_P1_Meter";
67
String MQTT_Server = "";
68
String MQTT_Port = Default_MQTT_Port;
69
String MQTT_User = "";
70
String MQTT_Password = "";
6 raymond 71
String MQTT_Prefix = "P1_Meter";
5 raymond 72
int LEDSpeed = 0;
4 raymond 73
 
5 raymond 74
bool isPressing = false;
75
unsigned long pressedTime  = 0;
76
unsigned long releasedTime = 0;
4 raymond 77
 
6 raymond 78
char clientId[16];
79
char inTopic[24];
80
char outTopic[24];
81
 
4 raymond 82
////////////////////////////////  Filesystem  /////////////////////////////////////////
83
bool startFilesystem() {
84
  if (FILESYSTEM.begin()){
5 raymond 85
    server.printFileList(FILESYSTEM, "/", 2), Serial;
4 raymond 86
    return true;
87
  }
88
  else {
89
    Serial.println("ERROR on mounting filesystem. It will be reformatted!");
90
    FILESYSTEM.format();
91
    ESP.restart();
92
  }
93
  return false;
94
}
95
/*
96
* Getting FS info (total and free bytes) is strictly related to
97
* filesystem library used (LittleFS, FFat, SPIFFS etc etc) and ESP framework
98
*/
99
#ifdef ESP32
100
void getFsInfo(fsInfo_t* fsInfo) {
101
	fsInfo->fsName = "LittleFS";
102
	fsInfo->totalBytes = LittleFS.totalBytes();
103
	fsInfo->usedBytes = LittleFS.usedBytes();
104
}
105
#endif
106
 
6 raymond 107
// * Send a message to a broker topic
108
void send_mqtt_message(const char *topic, char *payload)
109
{
110
    Serial.printf("MQTT Outgoing on %s: ", topic);
111
    Serial.println(payload);
112
    if (MQTT_Enabled) {
113
 
114
      bool result = mqttClient.publish(topic, payload, false);
115
 
116
      if (!result)
117
      {
118
          Serial.printf("MQTT publish to topic %s failed\n", topic);
119
      }
120
    }
121
}
122
 
123
///////////////////////////  MQTT callback function  ///////////////////////////////////
124
void mqttCallback(char* topic, byte* payload, unsigned int length) {
125
  Serial.printf("Message arrived [%s] ", topic);
126
  for (int i = 0; i < length; i++) {
127
    Serial.print((char)payload[i]);
128
  }
129
  Serial.println();
130
}
131
 
132
///////////////////////////  MQTT reconnect function  ///////////////////////////////////
133
void mqttReconnect() {
134
 
135
  if (WiFi.status() != WL_CONNECTED)  // Check connection
136
    return;
137
 
138
  static uint32_t lastConnectionTime = 5000;
139
  if (millis() - lastConnectionTime < 5000)  // Wait 5 seconds before retrying
140
    return;
141
  lastConnectionTime = millis();
142
 
143
  if (MQTT_Enabled) {
144
    Serial.print("Attempting MQTT connection...");
145
    if (mqttClient.connect(MQTT_Client.c_str(), MQTT_User.c_str(), MQTT_Password.c_str())) {  // Attempt to connect
146
      Serial.println("connected to MQTT server.");
147
 
148
      String payload = "Hello World from ";
149
      payload += clientId;
150
      mqttClient.publish(outTopic, payload.c_str());  // Once connected, publish an announcement...
151
      mqttClient.subscribe(inTopic);
152
                      // ... and resubscribe
153
    } else {
154
      Serial.printf("failed, rc=%d, try again in 5 seconds\n", mqttClient.state());
155
    }
156
  }
157
}
158
 
5 raymond 159
////////////////////////////////   WebSocket Handler  /////////////////////////////
160
void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
161
  switch (type) {
162
    case WS_EVT_DISCONNECT:
163
      Serial.print("WebSocket client disconnected!\n");
164
      break;
165
    case WS_EVT_CONNECT:  {
166
        IPAddress ip = client->remoteIP();
167
        Serial.printf("WebSocket client %d.%d.%d.%d connected.\n", ip[0], ip[1], ip[2], ip[3]);
168
        client->printf("%s", "{\"Connected\": true}");
169
      }
170
      break;
171
    default:
172
        break;
173
  }
174
}
175
 
176
////////////////////////////////  NTP Time  /////////////////////////////////////
177
void getUpdatedtime(const uint32_t timeout)
178
{
179
  uint32_t start = millis();
180
  Serial.print("Sync time...");
181
  while (millis() - start < timeout  && Time.tm_year <= (1970 - 1900)) {
182
    time_t now = time(nullptr);
183
    Time = *localtime(&now);
184
    delay(5);
185
  }
186
  Serial.println(" done.");
187
}
188
 
4 raymond 189
////////////////////  Load application options from filesystem  ////////////////////
190
bool loadOptions() {
191
  if (FILESYSTEM.exists(server.getConfiFileName())) {
5 raymond 192
    server.getOptionValue(USER_LABEL, Username);
193
    server.getOptionValue(PASSWORD_LABEL, Password);
194
    server.getOptionValue(EXTERNAL_ANTENNA_LABEL, External_Antenna);
195
 
4 raymond 196
    server.getOptionValue(MQTT_ENABLED_LABEL, MQTT_Enabled);
197
    server.getOptionValue(MQTT_CLIENT_LABEL, MQTT_Client);
198
    server.getOptionValue(MQTT_SERVER_LABEL, MQTT_Server);
199
    server.getOptionValue(MQTT_PORT_LABEL, MQTT_Port);
200
    server.getOptionValue(MQTT_USER_LABEL, MQTT_User);
201
    server.getOptionValue(MQTT_PASSWORD_LABEL, MQTT_Password);
6 raymond 202
    server.getOptionValue(MQTT_PREFIX_LABEL, MQTT_Prefix);
4 raymond 203
 
204
    Serial.println("\nThis are the current values stored: \n");
205
    Serial.printf("MQTT Enabled: %s\n", MQTT_Enabled ? "true" : "false");
206
    Serial.printf("MQTT Client: %s\n", MQTT_Client.c_str());
207
    Serial.printf("MQTT Server: %s\n", MQTT_Server.c_str());
5 raymond 208
    Serial.printf("MQTT Server Port: %s\n", MQTT_Port.c_str());
4 raymond 209
    Serial.printf("MQTT User: %s\n", MQTT_User.c_str());
210
    Serial.printf("MQTT password: %s\n", MQTT_Password.c_str());
6 raymond 211
    Serial.printf("MQTT Prefix: %s\n", MQTT_Prefix.c_str());
4 raymond 212
    return true;
213
  }
214
  else
215
    Serial.println(F("Config file not exist"));
216
  return false;
217
}
218
 
219
////////////////////////////  HTTP Request Handlers  ////////////////////////////////////
220
void handleLoadOptions(AsyncWebServerRequest *request) {
221
  request->send(200, "text/plain", "Options loaded");
222
  loadOptions();
223
  Serial.println("Application option loaded after web request");
224
}
225
 
6 raymond 226
////////////////////////////  Send data to MQTT  ////////////////////////////////////
227
void send_metric(String name, long metric)
228
{
229
//    Serial.print(F("Sending metric to broker: "));
230
    Serial.print(name);
231
    Serial.print(F("="));
232
    Serial.println(metric);
4 raymond 233
 
6 raymond 234
    char output[10];
235
    ltoa(metric, output, sizeof(output));
5 raymond 236
 
6 raymond 237
    String topic = String(MQTT_Prefix) + "/" + name;
238
    send_mqtt_message(topic.c_str(), output);
239
}
5 raymond 240
 
6 raymond 241
////////////////////////////  Set data for MQTT  ////////////////////////////////////
242
void send_data_to_broker()
243
{
244
    send_metric("consumption_low_tarif", CONSUMPTION_LOW_TARIF);
245
    send_metric("consumption_high_tarif", CONSUMPTION_HIGH_TARIF);
246
    send_metric("returndelivery_low_tarif", RETURNDELIVERY_LOW_TARIF);
247
    send_metric("returndelivery_high_tarif", RETURNDELIVERY_HIGH_TARIF);
248
    send_metric("actual_consumption", ACTUAL_CONSUMPTION);
249
    send_metric("actual_returndelivery", ACTUAL_RETURNDELIVERY);
5 raymond 250
 
6 raymond 251
    send_metric("l1_instant_power_usage", L1_INSTANT_POWER_USAGE);
252
    send_metric("l2_instant_power_usage", L2_INSTANT_POWER_USAGE);
253
    send_metric("l3_instant_power_usage", L3_INSTANT_POWER_USAGE);
254
    send_metric("l1_instant_power_current", L1_INSTANT_POWER_CURRENT);
255
    send_metric("l2_instant_power_current", L2_INSTANT_POWER_CURRENT);
256
    send_metric("l3_instant_power_current", L3_INSTANT_POWER_CURRENT);
257
    send_metric("l1_voltage", L1_VOLTAGE);
258
    send_metric("l2_voltage", L2_VOLTAGE);
259
    send_metric("l3_voltage", L3_VOLTAGE);
260
 
261
    send_metric("gas_meter_m3", GAS_METER_M3);
5 raymond 262
 
6 raymond 263
    send_metric("actual_tarif_group", ACTUAL_TARIF);
264
    send_metric("short_power_outages", SHORT_POWER_OUTAGES);
265
    send_metric("long_power_outages", LONG_POWER_OUTAGES);
266
    send_metric("short_power_drops", SHORT_POWER_DROPS);
267
    send_metric("short_power_peaks", SHORT_POWER_PEAKS);
268
}
5 raymond 269
 
6 raymond 270
////////////////////////////  CRC16 Check  ////////////////////////////////////
271
unsigned int CRC16(unsigned int crc, unsigned char *buf, int len)
272
{
273
	for (int pos = 0; pos < len; pos++)
274
    {
275
		crc ^= (unsigned int)buf[pos];    // * XOR byte into least sig. byte of crc
276
                                          // * Loop over each bit
277
        for (int i = 8; i != 0; i--)
278
        {
279
            // * If the LSB is set
280
            if ((crc & 0x0001) != 0)
281
            {
282
                // * Shift right and XOR 0xA001
283
                crc >>= 1;
284
				crc ^= 0xA001;
285
			}
286
            // * Else LSB is not set
287
            else
288
                // * Just shift right
289
                crc >>= 1;
290
		}
291
	}
292
	return crc;
293
}
294
 
295
////////////////////////////  Check if data is Number  ////////////////////////////////////
296
bool isNumber(char *res, int len)
297
{
298
    for (int i = 0; i < len; i++)
299
    {
300
        if (((res[i] < '0') || (res[i] > '9')) && (res[i] != '.' && res[i] != 0))
301
            return false;
302
    }
303
    return true;
304
}
305
 
306
////////////////////////////  Char search in Array  ////////////////////////////////////
307
int FindCharInArrayRev(char array[], char c, int len)
308
{
309
    for (int i = len - 1; i >= 0; i--)
310
    {
311
        if (array[i] == c)
312
            return i;
313
    }
314
    return -1;
315
}
316
 
317
////////////////////////////  Get the value from the Telegram  ////////////////////////////////////
318
long getValue(char *buffer, int maxlen, char startchar, char endchar)
319
{
320
    int s = FindCharInArrayRev(buffer, startchar, maxlen - 2);
321
    int l = FindCharInArrayRev(buffer, endchar, maxlen - 2) - s - 1;
322
 
323
    char res[16];
324
    memset(res, 0, sizeof(res));
325
 
326
    if (strncpy(res, buffer + s + 1, l))
327
    {
328
        if (endchar == '*')
329
        {
330
            if (isNumber(res, l))
331
                // * Lazy convert float to long
332
                return (1000 * atof(res));
333
        }
334
        else if (endchar == ')')
335
        {
336
            if (isNumber(res, l))
337
                return atof(res);
338
        }
339
    }
340
    return 0;
341
}
342
 
343
////////////////////////////  Decode Telegram  ////////////////////////////////////
344
bool decode_telegram(int len)
345
{
346
    int startChar = FindCharInArrayRev(telegram, '/', len);
347
    int endChar = FindCharInArrayRev(telegram, '!', len);
348
//    bool validCRCFound = false;
349
    bool validCRCFound = true;
350
 
351
    for (int cnt = 0; cnt < len; cnt++) {
352
        Serial.print(telegram[cnt]);
353
    }
354
//    Serial.print("\n");
355
 
356
    if (startChar >= 0)
357
    {
358
        // * Start found. Reset CRC calculation
359
        currentCRC = CRC16(0x0000,(unsigned char *) telegram+startChar, len-startChar);
360
    }
361
    else if (endChar >= 0)
362
    {
363
        // * Add to crc calc
364
        currentCRC = CRC16(currentCRC,(unsigned char*)telegram+endChar, 1);
365
 
366
        char messageCRC[5];
367
        strncpy(messageCRC, telegram + endChar + 1, 4);
368
 
369
        messageCRC[4] = 0;   // * Thanks to HarmOtten (issue 5)
370
//        validCRCFound = (strtol(messageCRC, NULL, 16) == currentCRC);
371
 
372
        if (validCRCFound)
373
            Serial.println(F("CRC Valid!"));
374
        else
375
            Serial.println(F("CRC Invalid!"));
376
 
377
        currentCRC = 0;
378
    }
379
    else
380
    {
381
        currentCRC = CRC16(currentCRC, (unsigned char*) telegram, len);
382
    }
383
 
384
    // 1-0:1.8.1(000992.992*kWh)
385
    // 1-0:1.8.1 = Elektra verbruik laag tarief (DSMR v4.0)
386
    if (strncmp(telegram, "1-0:1.8.1", strlen("1-0:1.8.1")) == 0)
387
    {
388
        CONSUMPTION_LOW_TARIF = getValue(telegram, len, '(', '*');
389
    }
390
 
391
    // 1-0:1.8.2(000560.157*kWh)
392
    // 1-0:1.8.2 = Elektra verbruik hoog tarief (DSMR v4.0)
393
    if (strncmp(telegram, "1-0:1.8.2", strlen("1-0:1.8.2")) == 0)
394
    {
395
        CONSUMPTION_HIGH_TARIF = getValue(telegram, len, '(', '*');
396
    }
397
 
398
    // 1-0:2.8.1(000560.157*kWh)
399
    // 1-0:2.8.1 = Elektra teruglevering laag tarief (DSMR v4.0)
400
    if (strncmp(telegram, "1-0:2.8.1", strlen("1-0:2.8.1")) == 0)
401
    {
402
        RETURNDELIVERY_LOW_TARIF = getValue(telegram, len, '(', '*');
403
    }
404
 
405
    // 1-0:2.8.2(000560.157*kWh)
406
    // 1-0:2.8.2 = Elektra teruglevering hoog tarief (DSMR v4.0)
407
    if (strncmp(telegram, "1-0:2.8.2", strlen("1-0:2.8.2")) == 0)
408
    {
409
        RETURNDELIVERY_HIGH_TARIF = getValue(telegram, len, '(', '*');
410
    }
411
 
412
    // 1-0:1.7.0(00.424*kW) Actueel verbruik
413
    // 1-0:1.7.x = Electricity consumption actual usage (DSMR v4.0)
414
    if (strncmp(telegram, "1-0:1.7.0", strlen("1-0:1.7.0")) == 0)
415
    {
416
        ACTUAL_CONSUMPTION = getValue(telegram, len, '(', '*');
417
    }
418
 
419
    // 1-0:2.7.0(00.000*kW) Actuele teruglevering (-P) in 1 Watt resolution
420
    if (strncmp(telegram, "1-0:2.7.0", strlen("1-0:2.7.0")) == 0)
421
    {
422
        ACTUAL_RETURNDELIVERY = getValue(telegram, len, '(', '*');
423
    }
424
 
425
    // 1-0:21.7.0(00.378*kW)
426
    // 1-0:21.7.0 = Instantaan vermogen Elektriciteit levering L1
427
    if (strncmp(telegram, "1-0:21.7.0", strlen("1-0:21.7.0")) == 0)
428
    {
429
        L1_INSTANT_POWER_USAGE = getValue(telegram, len, '(', '*');
430
    }
431
 
432
    // 1-0:41.7.0(00.378*kW)
433
    // 1-0:41.7.0 = Instantaan vermogen Elektriciteit levering L2
434
    if (strncmp(telegram, "1-0:41.7.0", strlen("1-0:41.7.0")) == 0)
435
    {
436
        L2_INSTANT_POWER_USAGE = getValue(telegram, len, '(', '*');
437
    }
438
 
439
    // 1-0:61.7.0(00.378*kW)
440
    // 1-0:61.7.0 = Instantaan vermogen Elektriciteit levering L3
441
    if (strncmp(telegram, "1-0:61.7.0", strlen("1-0:61.7.0")) == 0)
442
    {
443
        L3_INSTANT_POWER_USAGE = getValue(telegram, len, '(', '*');
444
    }
445
 
446
    // 1-0:31.7.0(002*A)
447
    // 1-0:31.7.0 = Instantane stroom Elektriciteit L1
448
    if (strncmp(telegram, "1-0:31.7.0", strlen("1-0:31.7.0")) == 0)
449
    {
450
        L1_INSTANT_POWER_CURRENT = getValue(telegram, len, '(', '*');
451
    }
452
    // 1-0:51.7.0(002*A)
453
    // 1-0:51.7.0 = Instantane stroom Elektriciteit L2
454
    if (strncmp(telegram, "1-0:51.7.0", strlen("1-0:51.7.0")) == 0)
455
    {
456
        L2_INSTANT_POWER_CURRENT = getValue(telegram, len, '(', '*');
457
    }
458
    // 1-0:71.7.0(002*A)
459
    // 1-0:71.7.0 = Instantane stroom Elektriciteit L3
460
    if (strncmp(telegram, "1-0:71.7.0", strlen("1-0:71.7.0")) == 0)
461
    {
462
        L3_INSTANT_POWER_CURRENT = getValue(telegram, len, '(', '*');
463
    }
464
 
465
    // 1-0:32.7.0(232.0*V)
466
    // 1-0:32.7.0 = Voltage L1
467
    if (strncmp(telegram, "1-0:32.7.0", strlen("1-0:32.7.0")) == 0)
468
    {
469
        L1_VOLTAGE = getValue(telegram, len, '(', '*');
470
    }
471
    // 1-0:52.7.0(232.0*V)
472
    // 1-0:52.7.0 = Voltage L2
473
    if (strncmp(telegram, "1-0:52.7.0", strlen("1-0:52.7.0")) == 0)
474
    {
475
        L2_VOLTAGE = getValue(telegram, len, '(', '*');
476
    }   
477
    // 1-0:72.7.0(232.0*V)
478
    // 1-0:72.7.0 = Voltage L3
479
    if (strncmp(telegram, "1-0:72.7.0", strlen("1-0:72.7.0")) == 0)
480
    {
481
        L3_VOLTAGE = getValue(telegram, len, '(', '*');
482
    }
483
 
484
    // 0-1:24.2.1(150531200000S)(00811.923*m3)
485
    // 0-1:24.2.1 = Gas (DSMR v4.0) on Kaifa MA105 meter
486
    if (strncmp(telegram, "0-1:24.2.1", strlen("0-1:24.2.1")) == 0)
487
    {
488
        GAS_METER_M3 = getValue(telegram, len, '(', '*');
489
    }
490
 
491
    // 0-0:96.14.0(0001)
492
    // 0-0:96.14.0 = Actual Tarif
493
    if (strncmp(telegram, "0-0:96.14.0", strlen("0-0:96.14.0")) == 0)
494
    {
495
        ACTUAL_TARIF = getValue(telegram, len, '(', ')');
496
    }
497
 
498
    // 0-0:96.7.21(00003)
499
    // 0-0:96.7.21 = Aantal onderbrekingen Elektriciteit
500
    if (strncmp(telegram, "0-0:96.7.21", strlen("0-0:96.7.21")) == 0)
501
    {
502
        SHORT_POWER_OUTAGES = getValue(telegram, len, '(', ')');
503
    }
504
 
505
    // 0-0:96.7.9(00001)
506
    // 0-0:96.7.9 = Aantal lange onderbrekingen Elektriciteit
507
    if (strncmp(telegram, "0-0:96.7.9", strlen("0-0:96.7.9")) == 0)
508
    {
509
        LONG_POWER_OUTAGES = getValue(telegram, len, '(', ')');
510
    }
511
 
512
    // 1-0:32.32.0(00000)
513
    // 1-0:32.32.0 = Aantal korte spanningsdalingen Elektriciteit in fase 1
514
    if (strncmp(telegram, "1-0:32.32.0", strlen("1-0:32.32.0")) == 0)
515
    {
516
        SHORT_POWER_DROPS = getValue(telegram, len, '(', ')');
517
    }
518
 
519
    // 1-0:32.36.0(00000)
520
    // 1-0:32.36.0 = Aantal korte spanningsstijgingen Elektriciteit in fase 1
521
    if (strncmp(telegram, "1-0:32.36.0", strlen("1-0:32.36.0")) == 0)
522
    {
523
        SHORT_POWER_PEAKS = getValue(telegram, len, '(', ')');
524
    }
525
 
526
    return validCRCFound;
527
}
528
 
529
void read_p1_hardwareserial()
530
{
531
    if (mySerial.available())
532
    {
533
        memset(telegram, 0, sizeof(telegram));
534
 
535
        while (mySerial.available())
536
        {
537
            int len = mySerial.readBytesUntil('\n', telegram, P1_MAXLINELENGTH);
538
 
539
            processLine(len);
540
        }
541
    }
542
}
543
 
544
void processLine(int len) {
545
    telegram[len] = '\n';
546
    telegram[len + 1] = 0;
547
    yield();
548
 
549
    bool result = decode_telegram(len + 1);
550
    if (result) {
551
        send_data_to_broker();
552
        LAST_UPDATE_SENT = millis();
553
    }
554
}
555
 
5 raymond 556
////////////////////////////////  SETUP  /////////////////////////////////////////
4 raymond 557
void setup() {
558
  Serial.begin(115200);
5 raymond 559
  button.setDebounceTime(50); // set debounce time to 50 milliseconds
4 raymond 560
  pinMode(BTN_SAVE, INPUT_PULLUP);
5 raymond 561
  pinMode(LED_BUILTIN, OUTPUT);
4 raymond 562
 
6 raymond 563
      // Setup a hw serial connection for communication with the P1 meter and logging (not using inversion)
564
//    Serial.begin(BAUD_RATE, SERIAL_8N1, 115200);
565
    Serial.setRxBufferSize(1024);
566
    Serial.println("");
567
    Serial.println("Swapping UART0 RX to inverted");
568
    Serial.flush();
569
 
570
    // Invert the RX serialport by setting a register value, this way the TX might continue normally allowing the serial monitor to read println's
571
    mySerial.begin(BAUD,SERIAL_8N1,RX_PIN,TX_PIN, true);
572
    Serial.println("Serial port is ready to recieve.");
573
 
5 raymond 574
 
4 raymond 575
  // FILESYSTEM INIT
576
  if (startFilesystem()){
577
    // Load configuration (if not present, default will be created when webserver will start)
578
    if (loadOptions())
579
      Serial.println(F("Application option loaded"));
580
    else
581
      Serial.println(F("Application options NOT loaded!"));
582
  }
583
 
5 raymond 584
  server.setSetupPageTitle("Instellingen P1 Meter");
585
  server.setSetupPageLogo(logo_png, sizeof(logo_png), "image/png", false);
586
  server.closeSetupConfiguration();
587
 
4 raymond 588
  // Try to connect to stored SSID, start AP with captive portal if fails after timeout
589
  IPAddress myIP = server.startWiFi(15000);
590
  if (!myIP) {
591
    Serial.println("\n\nNo WiFi connection, start AP and Captive Portal\n");
592
    server.startCaptivePortal("ESP_AP", "123456789", "/setup");
593
    myIP = WiFi.softAPIP();
594
    captiveRun = true;
5 raymond 595
    LEDSpeed = 250;
596
  } else {
597
    LEDSpeed = 1000;
6 raymond 598
    captiveRun = true;
4 raymond 599
  }
600
 
601
  // Add custom page handlers to webserver
602
  server.on("/reload", HTTP_GET, handleLoadOptions);
603
 
604
  // Configure /setup page and start Web Server
5 raymond 605
  server.addOptionBox("Device Setup");
606
  server.addOption(USER_LABEL, Username);
607
  server.addOption(PASSWORD_LABEL, Password);
608
  server.addOption(EXTERNAL_ANTENNA_LABEL, External_Antenna);
609
  server.addComment(EXTERNAL_ANTENNA_LABEL, "Restart is necessary after changing values.");
610
 
611
  server.addOptionBox("MQTT Setup");
4 raymond 612
  server.addOption(MQTT_ENABLED_LABEL, MQTT_Enabled);
5 raymond 613
  server.addOption(MQTT_CLIENT_LABEL, MQTT_Client);
4 raymond 614
  server.addOption(MQTT_SERVER_LABEL, MQTT_Server);
615
  server.addOption(MQTT_PORT_LABEL, MQTT_Port);
616
  server.addOption(MQTT_USER_LABEL, MQTT_User);
617
  server.addOption(MQTT_PASSWORD_LABEL, MQTT_Password);
6 raymond 618
  server.addOption(MQTT_PREFIX_LABEL, MQTT_Prefix);
619
  server.addComment(MQTT_PREFIX_LABEL, "Restart is necessary after changing values.");
4 raymond 620
 
621
  // Enable ACE FS file web editor and add FS info callback function
622
  server.enableFsCodeEditor();
623
  #ifdef ESP32
624
  server.setFsInfoCallback(getFsInfo);
625
  #endif
626
 
627
  // set /setup and /edit page authentication
5 raymond 628
  server.setAuthentication(Username.c_str(), Password.c_str());
4 raymond 629
 
5 raymond 630
   // Start server with custom websocket event handler
631
  server.init(onWsEvent);
4 raymond 632
  Serial.print(F("ESP Web Server started on IP Address: "));
5 raymond 633
  Serial.println(server.getServerIP());
4 raymond 634
 
5 raymond 635
  if (External_Antenna) {
636
    pinMode(3, OUTPUT);    // RF switch power on
637
    digitalWrite(3, LOW);
638
    pinMode(14, OUTPUT);   // select external antenna
639
    digitalWrite(14, HIGH);
640
    Serial.println("External antenna selected.");
641
 } else {
642
    pinMode(14, INPUT);    // select on-board antenna
643
    pinMode(3, INPUT);   // RF switch power off
644
    Serial.println("Internal antenna selected.");
645
 }
6 raymond 646
 // Get and print Wifi strenght
5 raymond 647
 long rssi = WiFi.RSSI();
648
 Serial.print("WiFi Strength (RSSI): ");
649
 if ((rssi <= -30) && (rssi > -50)) {
650
   Serial.print("***** (");
651
   Serial.print(rssi);
652
   Serial.print("dBm.)");
653
 } else   if ((rssi <= -50) && (rssi > -60)) {
654
   Serial.print("****. (");
655
   Serial.print(rssi);
656
   Serial.print("dBm.)");
657
 } else   if ((rssi <= -60) && (rssi > -70)) {
658
   Serial.print("***.. (");
659
   Serial.print(rssi);
660
   Serial.print("dBm.)");
661
 } else   if ((rssi <= -70) && (rssi > -80)) {
662
   Serial.print("**... (");
663
   Serial.print(rssi);
664
   Serial.print("dBm.)");
665
 } else   if ((rssi <= -80) && (rssi > -90)) {
666
   Serial.print("*.... (");
667
   Serial.print(rssi);
668
   Serial.print("dBm.)");
669
 } else   if ((rssi <= -90)) {
670
   Serial.print("..... (");
671
   Serial.print(rssi);
672
   Serial.print("dBm.)");
673
 }
674
 
6 raymond 675
   // Create a unique mqttClient ID and in/out topics
676
  snprintf(clientId, sizeof(clientId), "ESP-%llX", ESP.getEfuseMac());
677
  snprintf(inTopic, sizeof(inTopic), "%s/input", MQTT_Prefix.c_str());
678
  snprintf(outTopic, sizeof(outTopic), "%s/output", MQTT_Prefix.c_str());
679
 
680
  Serial.print("MQTT CLiend ID: ");
681
  Serial.println(clientId);
682
  Serial.print("Publish output topic: ");
683
  Serial.println(outTopic);
684
  Serial.print("Subscribe input topic: ");
685
  Serial.println(inTopic);
686
 
687
  // Set MQTT server and callback function
688
  mqttClient.setServer(MQTT_Server.c_str(), MQTT_Port.toInt());
689
  mqttClient.setCallback(mqttCallback);
4 raymond 690
}
691
 
5 raymond 692
////////////////////////////////  LOOP  /////////////////////////////////////////
4 raymond 693
void loop() {
5 raymond 694
 
6 raymond 695
  long now = millis();
696
 
697
      // Send ESP system time (epoch) and heap stats to WS client
5 raymond 698
  static uint32_t sendToClientTime;
699
  if (millis() - sendToClientTime > LEDSpeed ) {
700
    sendToClientTime = millis();
701
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
702
 
703
    time_t now = time(nullptr);
704
    using namespace AsyncFSWebServer;
705
    CJSON::Json doc;
706
    doc.setBool("addPoint", true);
707
    doc.setNumber("timestamp", (double)now);
708
#ifdef ESP32
6 raymond 709
    ACTUAL_SUMPOWER = ACTUAL_CONSUMPTION - ACTUAL_RETURNDELIVERY;
710
    doc.setNumber("ACTUAL_CONSUMPTION", (double)ACTUAL_CONSUMPTION);
711
    doc.setNumber("ACTUAL_RETURNDELIVERY", (double)ACTUAL_RETURNDELIVERY);
712
    doc.setNumber("ACTUAL_SUMPOWER", (double)ACTUAL_SUMPOWER);
5 raymond 713
#elif defined(ESP8266)
714
    uint32_t free;
715
    uint32_t max;
716
    ESP.getHeapStats(&free, &max, nullptr);
717
    doc.setNumber("totalHeap", (double)free);
718
    doc.setNumber("maxBlock", (double)max);
719
#endif
720
    String msg = doc.serialize();
721
    server.wsBroadcast(msg.c_str());
722
  }
723
 
4 raymond 724
  if (captiveRun)
725
    server.updateDNS();
726
 
6 raymond 727
  // Handle MQTT client
728
  if (MQTT_Enabled) {
729
    if (!mqttClient.connected()) {
730
      // Client not connected, try to reconnect every 5 seconds
731
      mqttReconnect();
732
    } else {
733
      // Client connected
734
      mqttClient.loop();
735
      // Publish a new message every 5 seconds
736
      static uint32_t lastMsgTime = millis();
737
      static uint16_t value = 0;
738
      if (millis() - lastMsgTime > 5000) {
739
        lastMsgTime = millis();
740
 
741
        char payload[64];
742
        snprintf(payload, sizeof(payload), "Hello World from %s #%d", clientId, ++value);
743
 
744
        Serial.print("Publish message: ");
745
        Serial.println(payload);
746
        mqttClient.publish(outTopic, payload);
747
        read_p1_hardwareserial();
748
      }
749
    }
4 raymond 750
  }
5 raymond 751
 
6 raymond 752
// read reset button SHORT Press (>1 & <2,5 second restart, >10 seconds (led stays on factory reset))
5 raymond 753
  button.loop(); // MUST call the loop() function first
754
 
755
  if (button.isPressed()) {
756
    pressedTime = millis();
757
    isPressing = true;
758
  }
759
 
760
  long pressingDuration = millis() - pressedTime;
761
 
762
  if ((isPressing == true) && (pressingDuration > LONG_PRESS_TIME))
763
      digitalWrite(LED_BUILTIN, LOW);
764
 
765
  if (button.isReleased()) {
766
    releasedTime = millis();
767
    isPressing = false;
768
 
769
    long pressDuration = releasedTime - pressedTime;
770
 
771
    if ((pressDuration > SHORT_PRESS_TIME_MIN) && (pressDuration < SHORT_PRESS_TIME_MAX)) {
772
      ESP.restart();
773
    }
774
 
775
    if (pressDuration > LONG_PRESS_TIME) {
776
      server.clearConfigFile();
777
      ESP.restart();
778
    }
779
  }
6 raymond 780
 
781
  if (now - LAST_UPDATE_SENT > UPDATE_INTERVAL) {
782
    read_p1_hardwareserial();
783
  }
4 raymond 784
}