Blame | Last modification | View Log | RSS feed
#include <FS.h>#include <EEPROM.h>#include <DNSServer.h>#include <ESP8266WiFi.h>#include <Ticker.h>#include <WiFiManager.h>#include <ESP8266mDNS.h>#include <WiFiUdp.h>#include <ArduinoOTA.h>#include <PubSubClient.h>// * Include settings#include "settings.h"// * Initiate led blinker libraryTicker ticker;// * Initiate WIFI clientWiFiClient espClient;// * Initiate MQTT clientPubSubClient mqtt_client(espClient);// **********************************// * WIFI *// **********************************// * Gets called when WiFiManager enters configuration modevoid configModeCallback(WiFiManager *myWiFiManager){Serial.println(F("Entered config mode"));Serial.println(WiFi.softAPIP());// * If you used auto generated SSID, print itSerial.println(myWiFiManager->getConfigPortalSSID());// * Entered config mode, make led toggle fasterticker.attach(0.2, tick);}// **********************************// * Ticker (System LED Blinker) *// **********************************// * Blink on-board Ledvoid tick(){// * Toggle stateint state = digitalRead(LED_BUILTIN); // * Get the current state of GPIO1 pindigitalWrite(LED_BUILTIN, !state); // * Set pin to the opposite state}// **********************************// * MQTT *// **********************************// * Send a message to a broker topicvoid send_mqtt_message(const char *topic, char *payload){Serial.printf("MQTT Outgoing on %s: ", topic);Serial.println(payload);bool result = mqtt_client.publish(topic, payload, false);if (!result){Serial.printf("MQTT publish to topic %s failed\n", topic);}}// * Reconnect to MQTT server and subscribe to in and out topicsbool mqtt_reconnect(){// * Loop until we're reconnectedint MQTT_RECONNECT_RETRIES = 0;while (!mqtt_client.connected() && MQTT_RECONNECT_RETRIES < MQTT_MAX_RECONNECT_TRIES){MQTT_RECONNECT_RETRIES++;Serial.printf("MQTT connection attempt %d / %d ...\n", MQTT_RECONNECT_RETRIES, MQTT_MAX_RECONNECT_TRIES);// * Attempt to connectif (mqtt_client.connect(HOSTNAME, MQTT_USER, MQTT_PASS)){Serial.println(F("MQTT connected!"));// * Once connected, publish an announcement...char *message = new char[16 + strlen(HOSTNAME) + 1];strcpy(message, "p1 meter alive: ");strcat(message, HOSTNAME);mqtt_client.publish("hass/status", message);Serial.printf("MQTT root topic: %s\n", MQTT_ROOT_TOPIC);}else{Serial.print(F("MQTT Connection failed: rc="));Serial.println(mqtt_client.state());Serial.println(F(" Retrying in 5 seconds"));Serial.println("");// * Wait 5 seconds before retryingdelay(5000);}}if (MQTT_RECONNECT_RETRIES >= MQTT_MAX_RECONNECT_TRIES){Serial.printf("*** MQTT connection failed, giving up after %d tries ...\n", MQTT_RECONNECT_RETRIES);return false;}return true;}void send_metric(String name, long metric){Serial.print(F("Sending metric to broker: "));Serial.print(name);Serial.print(F("="));Serial.println(metric);char output[10];ltoa(metric, output, sizeof(output));String topic = String(MQTT_ROOT_TOPIC) + "/" + name;send_mqtt_message(topic.c_str(), output);}void send_data_to_broker(){send_metric("consumption_low_tarif", CONSUMPTION_LOW_TARIF);send_metric("consumption_high_tarif", CONSUMPTION_HIGH_TARIF);send_metric("returndelivery_low_tarif", RETURNDELIVERY_LOW_TARIF);send_metric("returndelivery_high_tarif", RETURNDELIVERY_HIGH_TARIF);send_metric("actual_consumption", ACTUAL_CONSUMPTION);send_metric("actual_returndelivery", ACTUAL_RETURNDELIVERY);send_metric("l1_instant_power_usage", L1_INSTANT_POWER_USAGE);send_metric("l2_instant_power_usage", L2_INSTANT_POWER_USAGE);send_metric("l3_instant_power_usage", L3_INSTANT_POWER_USAGE);send_metric("l1_instant_power_current", L1_INSTANT_POWER_CURRENT);send_metric("l2_instant_power_current", L2_INSTANT_POWER_CURRENT);send_metric("l3_instant_power_current", L3_INSTANT_POWER_CURRENT);send_metric("l1_voltage", L1_VOLTAGE);send_metric("l2_voltage", L2_VOLTAGE);send_metric("l3_voltage", L3_VOLTAGE);send_metric("gas_meter_m3", GAS_METER_M3);send_metric("actual_tarif_group", ACTUAL_TARIF);send_metric("short_power_outages", SHORT_POWER_OUTAGES);send_metric("long_power_outages", LONG_POWER_OUTAGES);send_metric("short_power_drops", SHORT_POWER_DROPS);send_metric("short_power_peaks", SHORT_POWER_PEAKS);}// **********************************// * P1 *// **********************************unsigned int CRC16(unsigned int crc, unsigned char *buf, int len){for (int pos = 0; pos < len; pos++){crc ^= (unsigned int)buf[pos]; // * XOR byte into least sig. byte of crc// * Loop over each bitfor (int i = 8; i != 0; i--){// * If the LSB is setif ((crc & 0x0001) != 0){// * Shift right and XOR 0xA001crc >>= 1;crc ^= 0xA001;}// * Else LSB is not setelse// * Just shift rightcrc >>= 1;}}return crc;}bool isNumber(char *res, int len){for (int i = 0; i < len; i++){if (((res[i] < '0') || (res[i] > '9')) && (res[i] != '.' && res[i] != 0))return false;}return true;}int FindCharInArrayRev(char array[], char c, int len){for (int i = len - 1; i >= 0; i--){if (array[i] == c)return i;}return -1;}long getValue(char *buffer, int maxlen, char startchar, char endchar){int s = FindCharInArrayRev(buffer, startchar, maxlen - 2);int l = FindCharInArrayRev(buffer, endchar, maxlen - 2) - s - 1;char res[16];memset(res, 0, sizeof(res));if (strncpy(res, buffer + s + 1, l)){if (endchar == '*'){if (isNumber(res, l))// * Lazy convert float to longreturn (1000 * atof(res));}else if (endchar == ')'){if (isNumber(res, l))return atof(res);}}return 0;}bool decode_telegram(int len){int startChar = FindCharInArrayRev(telegram, '/', len);int endChar = FindCharInArrayRev(telegram, '!', len);bool validCRCFound = false;for (int cnt = 0; cnt < len; cnt++) {Serial.print(telegram[cnt]);}Serial.print("\n");if (startChar >= 0){// * Start found. Reset CRC calculationcurrentCRC = CRC16(0x0000,(unsigned char *) telegram+startChar, len-startChar);}else if (endChar >= 0){// * Add to crc calccurrentCRC = CRC16(currentCRC,(unsigned char*)telegram+endChar, 1);char messageCRC[5];strncpy(messageCRC, telegram + endChar + 1, 4);messageCRC[4] = 0; // * Thanks to HarmOtten (issue 5)validCRCFound = (strtol(messageCRC, NULL, 16) == currentCRC);if (validCRCFound)Serial.println(F("CRC Valid!"));elseSerial.println(F("CRC Invalid!"));currentCRC = 0;}else{currentCRC = CRC16(currentCRC, (unsigned char*) telegram, len);}// 1-0:1.8.1(000992.992*kWh)// 1-0:1.8.1 = Elektra verbruik laag tarief (DSMR v4.0)if (strncmp(telegram, "1-0:1.8.1", strlen("1-0:1.8.1")) == 0){CONSUMPTION_LOW_TARIF = getValue(telegram, len, '(', '*');}// 1-0:1.8.2(000560.157*kWh)// 1-0:1.8.2 = Elektra verbruik hoog tarief (DSMR v4.0)if (strncmp(telegram, "1-0:1.8.2", strlen("1-0:1.8.2")) == 0){CONSUMPTION_HIGH_TARIF = getValue(telegram, len, '(', '*');}// 1-0:2.8.1(000560.157*kWh)// 1-0:2.8.1 = Elektra teruglevering laag tarief (DSMR v4.0)if (strncmp(telegram, "1-0:2.8.1", strlen("1-0:2.8.1")) == 0){RETURNDELIVERY_LOW_TARIF = getValue(telegram, len, '(', '*');}// 1-0:2.8.2(000560.157*kWh)// 1-0:2.8.2 = Elektra teruglevering hoog tarief (DSMR v4.0)if (strncmp(telegram, "1-0:2.8.2", strlen("1-0:2.8.2")) == 0){RETURNDELIVERY_HIGH_TARIF = getValue(telegram, len, '(', '*');}// 1-0:1.7.0(00.424*kW) Actueel verbruik// 1-0:1.7.x = Electricity consumption actual usage (DSMR v4.0)if (strncmp(telegram, "1-0:1.7.0", strlen("1-0:1.7.0")) == 0){ACTUAL_CONSUMPTION = getValue(telegram, len, '(', '*');}// 1-0:2.7.0(00.000*kW) Actuele teruglevering (-P) in 1 Watt resolutionif (strncmp(telegram, "1-0:2.7.0", strlen("1-0:2.7.0")) == 0){ACTUAL_RETURNDELIVERY = getValue(telegram, len, '(', '*');}// 1-0:21.7.0(00.378*kW)// 1-0:21.7.0 = Instantaan vermogen Elektriciteit levering L1if (strncmp(telegram, "1-0:21.7.0", strlen("1-0:21.7.0")) == 0){L1_INSTANT_POWER_USAGE = getValue(telegram, len, '(', '*');}// 1-0:41.7.0(00.378*kW)// 1-0:41.7.0 = Instantaan vermogen Elektriciteit levering L2if (strncmp(telegram, "1-0:41.7.0", strlen("1-0:41.7.0")) == 0){L2_INSTANT_POWER_USAGE = getValue(telegram, len, '(', '*');}// 1-0:61.7.0(00.378*kW)// 1-0:61.7.0 = Instantaan vermogen Elektriciteit levering L3if (strncmp(telegram, "1-0:61.7.0", strlen("1-0:61.7.0")) == 0){L3_INSTANT_POWER_USAGE = getValue(telegram, len, '(', '*');}// 1-0:31.7.0(002*A)// 1-0:31.7.0 = Instantane stroom Elektriciteit L1if (strncmp(telegram, "1-0:31.7.0", strlen("1-0:31.7.0")) == 0){L1_INSTANT_POWER_CURRENT = getValue(telegram, len, '(', '*');}// 1-0:51.7.0(002*A)// 1-0:51.7.0 = Instantane stroom Elektriciteit L2if (strncmp(telegram, "1-0:51.7.0", strlen("1-0:51.7.0")) == 0){L2_INSTANT_POWER_CURRENT = getValue(telegram, len, '(', '*');}// 1-0:71.7.0(002*A)// 1-0:71.7.0 = Instantane stroom Elektriciteit L3if (strncmp(telegram, "1-0:71.7.0", strlen("1-0:71.7.0")) == 0){L3_INSTANT_POWER_CURRENT = getValue(telegram, len, '(', '*');}// 1-0:32.7.0(232.0*V)// 1-0:32.7.0 = Voltage L1if (strncmp(telegram, "1-0:32.7.0", strlen("1-0:32.7.0")) == 0){L1_VOLTAGE = getValue(telegram, len, '(', '*');}// 1-0:52.7.0(232.0*V)// 1-0:52.7.0 = Voltage L2if (strncmp(telegram, "1-0:52.7.0", strlen("1-0:52.7.0")) == 0){L2_VOLTAGE = getValue(telegram, len, '(', '*');}// 1-0:72.7.0(232.0*V)// 1-0:72.7.0 = Voltage L3if (strncmp(telegram, "1-0:72.7.0", strlen("1-0:72.7.0")) == 0){L3_VOLTAGE = getValue(telegram, len, '(', '*');}// 0-1:24.2.1(150531200000S)(00811.923*m3)// 0-1:24.2.1 = Gas (DSMR v4.0) on Kaifa MA105 meterif (strncmp(telegram, "0-1:24.2.1", strlen("0-1:24.2.1")) == 0){GAS_METER_M3 = getValue(telegram, len, '(', '*');}// 0-0:96.14.0(0001)// 0-0:96.14.0 = Actual Tarifif (strncmp(telegram, "0-0:96.14.0", strlen("0-0:96.14.0")) == 0){ACTUAL_TARIF = getValue(telegram, len, '(', ')');}// 0-0:96.7.21(00003)// 0-0:96.7.21 = Aantal onderbrekingen Elektriciteitif (strncmp(telegram, "0-0:96.7.21", strlen("0-0:96.7.21")) == 0){SHORT_POWER_OUTAGES = getValue(telegram, len, '(', ')');}// 0-0:96.7.9(00001)// 0-0:96.7.9 = Aantal lange onderbrekingen Elektriciteitif (strncmp(telegram, "0-0:96.7.9", strlen("0-0:96.7.9")) == 0){LONG_POWER_OUTAGES = getValue(telegram, len, '(', ')');}// 1-0:32.32.0(00000)// 1-0:32.32.0 = Aantal korte spanningsdalingen Elektriciteit in fase 1if (strncmp(telegram, "1-0:32.32.0", strlen("1-0:32.32.0")) == 0){SHORT_POWER_DROPS = getValue(telegram, len, '(', ')');}// 1-0:32.36.0(00000)// 1-0:32.36.0 = Aantal korte spanningsstijgingen Elektriciteit in fase 1if (strncmp(telegram, "1-0:32.36.0", strlen("1-0:32.36.0")) == 0){SHORT_POWER_PEAKS = getValue(telegram, len, '(', ')');}return validCRCFound;}void read_p1_hardwareserial(){if (Serial.available()){memset(telegram, 0, sizeof(telegram));while (Serial.available()){ESP.wdtDisable();int len = Serial.readBytesUntil('\n', telegram, P1_MAXLINELENGTH);ESP.wdtEnable(1);processLine(len);}}}void processLine(int len) {telegram[len] = '\n';telegram[len + 1] = 0;yield();bool result = decode_telegram(len + 1);if (result) {send_data_to_broker();LAST_UPDATE_SENT = millis();}}// **********************************// * EEPROM helpers *// **********************************String read_eeprom(int offset, int len){Serial.print(F("read_eeprom()"));String res = "";for (int i = 0; i < len; ++i){res += char(EEPROM.read(i + offset));}return res;}void write_eeprom(int offset, int len, String value){Serial.println(F("write_eeprom()"));for (int i = 0; i < len; ++i){if ((unsigned)i < value.length()){EEPROM.write(i + offset, value[i]);}else{EEPROM.write(i + offset, 0);}}}// ******************************************// * Callback for saving WIFI config *// ******************************************bool shouldSaveConfig = false;// * Callback notifying us of the need to save configvoid save_wifi_config_callback (){Serial.println(F("Should save config"));shouldSaveConfig = true;}// **********************************// * Setup OTA *// **********************************void setup_ota(){Serial.println(F("Arduino OTA activated."));// * Port defaults to 8266ArduinoOTA.setPort(8266);// * Set hostname for OTAArduinoOTA.setHostname(HOSTNAME);ArduinoOTA.setPassword(OTA_PASSWORD);ArduinoOTA.onStart([](){Serial.println(F("Arduino OTA: Start"));});ArduinoOTA.onEnd([](){Serial.println(F("Arduino OTA: End (Running reboot)"));});ArduinoOTA.onProgress([](unsigned int progress, unsigned int total){Serial.printf("Arduino OTA Progress: %u%%\r", (progress / (total / 100)));});ArduinoOTA.onError([](ota_error_t error){Serial.printf("Arduino OTA Error[%u]: ", error);if (error == OTA_AUTH_ERROR)Serial.println(F("Arduino OTA: Auth Failed"));else if (error == OTA_BEGIN_ERROR)Serial.println(F("Arduino OTA: Begin Failed"));else if (error == OTA_CONNECT_ERROR)Serial.println(F("Arduino OTA: Connect Failed"));else if (error == OTA_RECEIVE_ERROR)Serial.println(F("Arduino OTA: Receive Failed"));else if (error == OTA_END_ERROR)Serial.println(F("Arduino OTA: End Failed"));});ArduinoOTA.begin();Serial.println(F("Arduino OTA finished"));}// **********************************// * Setup MDNS discovery service *// **********************************void setup_mdns(){Serial.println(F("Starting MDNS responder service"));bool mdns_result = MDNS.begin(HOSTNAME);if (mdns_result){MDNS.addService("http", "tcp", 80);}}// **********************************// * Setup Main *// **********************************void setup(){// * Configure EEPROMEEPROM.begin(512);// Setup a hw serial connection for communication with the P1 meter and logging (not using inversion)Serial.begin(BAUD_RATE, SERIAL_8N1, SERIAL_FULL);Serial.setRxBufferSize(1024);Serial.println("");Serial.println("Swapping UART0 RX to inverted");Serial.flush();// Invert the RX serialport by setting a register value, this way the TX might continue normally allowing the serial monitor to read println'sUSC0(UART0) = USC0(UART0) | BIT(UCRXI);Serial.println("Serial port is ready to recieve.");// * Set led pin as outputpinMode(LED_BUILTIN, OUTPUT);// * Start ticker with 0.5 because we start in AP mode and try to connectticker.attach(0.6, tick);// * Get MQTT Server settingsString settings_available = read_eeprom(134, 1);if (settings_available == "1"){read_eeprom(0, 64).toCharArray(MQTT_HOST, 64); // * 0-63read_eeprom(64, 6).toCharArray(MQTT_PORT, 6); // * 64-69read_eeprom(70, 32).toCharArray(MQTT_USER, 32); // * 70-101read_eeprom(102, 32).toCharArray(MQTT_PASS, 32); // * 102-133}WiFiManagerParameter CUSTOM_MQTT_HOST("host", "MQTT hostname", MQTT_HOST, 64);WiFiManagerParameter CUSTOM_MQTT_PORT("port", "MQTT port", MQTT_PORT, 6);WiFiManagerParameter CUSTOM_MQTT_USER("user", "MQTT user", MQTT_USER, 32);WiFiManagerParameter CUSTOM_MQTT_PASS("pass", "MQTT pass", MQTT_PASS, 32);// * WiFiManager local initialization. Once its business is done, there is no need to keep it aroundWiFiManager wifiManager;// * Reset settings - uncomment for testing// wifiManager.resetSettings();// * Set callback that gets called when connecting to previous WiFi fails, and enters Access Point modewifiManager.setAPCallback(configModeCallback);// * Set timeoutwifiManager.setConfigPortalTimeout(WIFI_TIMEOUT);// * Set save config callbackwifiManager.setSaveConfigCallback(save_wifi_config_callback);// * Add all your parameters herewifiManager.addParameter(&CUSTOM_MQTT_HOST);wifiManager.addParameter(&CUSTOM_MQTT_PORT);wifiManager.addParameter(&CUSTOM_MQTT_USER);wifiManager.addParameter(&CUSTOM_MQTT_PASS);// * Fetches SSID and pass and tries to connect// * Reset when no connection after 10 secondsif (!wifiManager.autoConnect()){Serial.println(F("Failed to connect to WIFI and hit timeout"));// * Reset and try again, or maybe put it to deep sleepESP.reset();delay(WIFI_TIMEOUT);}// * Read updated parametersstrcpy(MQTT_HOST, CUSTOM_MQTT_HOST.getValue());strcpy(MQTT_PORT, CUSTOM_MQTT_PORT.getValue());strcpy(MQTT_USER, CUSTOM_MQTT_USER.getValue());strcpy(MQTT_PASS, CUSTOM_MQTT_PASS.getValue());// * Save the custom parameters to FSif (shouldSaveConfig){Serial.println(F("Saving WiFiManager config"));write_eeprom(0, 64, MQTT_HOST); // * 0-63write_eeprom(64, 6, MQTT_PORT); // * 64-69write_eeprom(70, 32, MQTT_USER); // * 70-101write_eeprom(102, 32, MQTT_PASS); // * 102-133write_eeprom(134, 1, "1"); // * 134 --> always "1"EEPROM.commit();}// * If you get here you have connected to the WiFiSerial.println(F("Connected to WIFI..."));// * Keep LED onticker.detach();digitalWrite(LED_BUILTIN, LOW);// * Configure OTAsetup_ota();// * Startup MDNS Servicesetup_mdns();// * Setup MQTTSerial.printf("MQTT connecting to: %s:%s\n", MQTT_HOST, MQTT_PORT);mqtt_client.setServer(MQTT_HOST, atoi(MQTT_PORT));}// **********************************// * Loop *// **********************************void loop(){ArduinoOTA.handle();long now = millis();if (!mqtt_client.connected()){if (now - LAST_RECONNECT_ATTEMPT > 5000){LAST_RECONNECT_ATTEMPT = now;if (mqtt_reconnect()){LAST_RECONNECT_ATTEMPT = 0;}}}else{mqtt_client.loop();}if (now - LAST_UPDATE_SENT > UPDATE_INTERVAL) {read_p1_hardwareserial();}}