Skip to content

Instantly share code, notes, and snippets.

@robertklep
Created July 13, 2020 14:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robertklep/b94f6a48d46d9f8c633a6a5b5bf10123 to your computer and use it in GitHub Desktop.
Save robertklep/b94f6a48d46d9f8c633a6a5b5bf10123 to your computer and use it in GitHub Desktop.
#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