-
-
Save robertklep/b94f6a48d46d9f8c633a6a5b5bf10123 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <Arduino.h> | |
#include <string> | |
#include <ESP8266WiFi.h> | |
#include <PubSubClient.h> | |
#include <SoftwareSerial.h> | |
#include <ArduinoOTA.h> | |
#include "config.h" | |
WiFiClient wifiClient; | |
PubSubClient mqtt(wifiClient); | |
// fake-ser2net setup | |
#define TCP_LISTEN_PORT 2000 | |
WiFiServer tcpserver(TCP_LISTEN_PORT); | |
WiFiClient tcpclient; | |
// SoftwareSerial setup | |
#define MAXLINELENGTH 64 // longest normal line is 47 char (+3 for \r\n\0) | |
#define SERIAL_RX D5 // pin for SoftwareSerial RX | |
char buffer[MAXLINELENGTH]; | |
SoftwareSerial mySerial(SERIAL_RX, -1, true); // (RX, TX, inverted) | |
struct Mapping { | |
std::string prefix; | |
std::string topic; | |
bool numerical; | |
}; | |
// https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/dsmr_reader/definitions.py | |
Mapping const MAPPING[] = { | |
{ "0-0:96.1.1", "dsmr/reading/id", false }, | |
{ "1-0:1.8.1", "dsmr/reading/electricity_delivered_1", true }, | |
{ "1-0:1.8.2", "dsmr/reading/electricity_delivered_2", true }, | |
{ "1-0:2.8.1", "dsmr/reading/electricity_returned_1", true }, | |
{ "1-0:2.8.2", "dsmr/reading/electricity_returned_2", true }, | |
{ "1-0:1.7.0", "dsmr/reading/electricity_currently_delivered", true }, | |
{ "1-0:2.7.0", "dsmr/reading/electricity_currently_returned", true }, | |
{ "0-0:96.14.0", "dsmr/meter-stats/electricity_tariff", true }, | |
{ "(", "dsmr/consumption/gas/delivered", true }, | |
}; | |
void publish(const char *topic, const char *value) { | |
mqtt.publish(topic, value); | |
} | |
void setup() { | |
Serial.begin(115200); | |
Serial.println(); | |
// Connect WiFi | |
WiFi.hostname(WIFI_HOSTNAME); | |
WiFi.begin(WIFI_SSID, WIFI_PASSWORD); | |
while (WiFi.status() != WL_CONNECTED) { | |
delay(500); | |
Serial.print("."); | |
} | |
Serial.print("\nWiFi connected, IP address: "); | |
Serial.println(WiFi.localIP()); | |
// Connect MQTT | |
mqtt.setServer(MQTT_HOST, MQTT_PORT); | |
while (! mqtt.connected()) { | |
Serial.print("Attempting MQTT connection: "); | |
if (mqtt.connect(MQTT_CLIENT_ID)) { | |
Serial.printf("connected to %s:%d\n", MQTT_HOST, MQTT_PORT); | |
} else { | |
Serial.printf("FAILED, rc = %i, trying again in 5 seconds.\n", mqtt.state()); | |
delay(5000); | |
} | |
} | |
publish("dsmr/info/ip_address", WiFi.localIP().toString().c_str()); | |
// Set up serial connection to P1 meter. | |
mySerial.begin(9600, SWSERIAL_7E1, SERIAL_RX, -1, true); | |
// Enable OTA updates | |
ArduinoOTA.onStart([]() { | |
Serial.println("Starting OTA update..."); | |
}); | |
ArduinoOTA.onEnd([]() { | |
Serial.println("\nOTA update ended."); | |
}); | |
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { | |
Serial.printf("OTA update progress: %u%%\r", (progress / (total / 100))); | |
}); | |
ArduinoOTA.onError([](ota_error_t error) { | |
Serial.printf("OTA update error[%u]: ", error); | |
if (error == OTA_AUTH_ERROR) Serial.println("- Auth Failed"); | |
else if (error == OTA_BEGIN_ERROR) Serial.println("- Begin Failed"); | |
else if (error == OTA_CONNECT_ERROR) Serial.println("- Connect Failed"); | |
else if (error == OTA_RECEIVE_ERROR) Serial.println("- Receive Failed"); | |
else if (error == OTA_END_ERROR) Serial.println("- End Failed"); | |
}); | |
ArduinoOTA.begin(); | |
// Start TCP server | |
tcpserver.begin(); | |
} | |
const std::string extractValue(std::string telegram) { | |
unsigned int start = telegram.find("("); | |
unsigned int end = telegram.find(")"); | |
unsigned int unit_separator = telegram.find("*"); | |
bool numerical = unit_separator != std::string::npos; | |
if (numerical) { | |
end = unit_separator; | |
} | |
if (start == std::string::npos || end == std::string::npos) return ""; | |
return telegram.substr(start + 1, end - start - 1); | |
} | |
void parseTelegram() { | |
std::string telegram(buffer); | |
#ifdef PUBLISH_RAW | |
publish("dsmr/raw", buffer); | |
#endif | |
for (auto mapping: MAPPING) { | |
if (telegram.rfind(mapping.prefix, 0) == 0) { | |
auto value = extractValue(telegram); | |
if (value != "") { | |
String valueString; | |
if (mapping.numerical) { | |
valueString = String(std::atof(value.c_str())); | |
} else { | |
valueString = String(value.c_str()); | |
} | |
publish(mapping.topic.c_str(), valueString.c_str()); | |
} | |
} | |
} | |
} | |
void readTelegram() { | |
if (mySerial.available()) { | |
memset(buffer, 0, sizeof(buffer)); | |
while (mySerial.available()) { | |
int len = mySerial.readBytesUntil('\n', buffer, MAXLINELENGTH + 1); | |
buffer[len] = 0; | |
yield(); | |
parseTelegram(); | |
// dump data to TCP client | |
if (tcpclient) { | |
if (tcpclient.connected()) { | |
buffer[len] = '\n'; | |
buffer[len + 1] = 0; | |
tcpclient.write((const uint8_t *) buffer, len + 1); | |
tcpclient.flush(); | |
} else { | |
tcpclient.stop(); | |
} | |
} | |
} | |
} | |
} | |
#ifdef PUBLISH_HEAP_STATS | |
unsigned long last = 0; | |
#endif | |
void loop () { | |
// Reboot when WiFi isn't connected anymore. | |
if (WiFi.status() != WL_CONNECTED) { | |
ESP.restart(); | |
return; | |
} | |
// Check if a TCP client has connected | |
if (! tcpclient) { | |
tcpclient = tcpserver.available(); | |
} | |
ArduinoOTA.handle(); | |
readTelegram(); | |
#ifdef PUBLISH_HEAP_STATS | |
unsigned long now = millis(); | |
if (now - last > 10000) { | |
publish("dsmr/info/stats/heap", String(ESP.getFreeHeap()).c_str()); | |
publish("dsmr/info/stats/fragmentation", String(ESP.getHeapFragmentation()).c_str()); | |
publish("dsmr/info/stats/max_free_block_size", String(ESP.getMaxFreeBlockSize()).c_str()); | |
last = now; | |
} | |
#endif | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment