├── .gitignore ├── include ├── .gitignore └── secret_template.h ├── README.md ├── platformio.ini └── src ├── main.cpp └── detect-rate.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode -------------------------------------------------------------------------------- /include/.gitignore: -------------------------------------------------------------------------------- 1 | secrets.h -------------------------------------------------------------------------------- /include/secret_template.h: -------------------------------------------------------------------------------- 1 | #define WIFI_ACCESPOINT "YOUR SSID" 2 | #define WIFI_PASSWORD "WIFI PASSWORD" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Small ESP serial forwarder for HomeAssistant 2 | 3 | ## Motivation 4 | I wanted to keep the code as small as possible, so this is a small program that 5 | acts as a serial port forwarder for HomeAssistant. 6 | 7 | It reads from P1, and if a client connects to port 23, it forwards everything to 8 | there. You connect HomeAssistant to it and it will do all the parsing. 9 | 10 | ## Wiring 11 | Wiring is the same as https://github.com/daniel-jong/esp8266_p1meter and also the initial inspiration of using the inverter of the hardware serial. 12 | 13 | ## How to use 14 | 1. In the [include-folder](./include) rename the **'secret_template.h'** to **'secrets.h'**, and enter your WiFi credentials. 15 | 2. Install to an ESP8266 board with [PlatformIO](https://platformio.org) using the [pio run -t upload](https://docs.platformio.org/en/latest/core/userguide/cmd_run.html) command 16 | 3. Connect the ESP8266 to your P1 meter as described in `Wiring` above 17 | 4. In HomeAssistant: Add a new [DSMR Slimme Meter integration](https://www.home-assistant.io/integrations/dsmr/) of type Network and enter the IP/Hostname of your ESP8266 and use port 23 18 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | default_envs = p1mon 13 | 14 | [env] 15 | platform = platformio/espressif8266 @ 4.2.1 16 | platform_packages = 17 | platformio/framework-arduinoespressif8266 @ ~3.30102.0 18 | board = d1_mini 19 | framework = arduino 20 | #monitor_speed = 115200 21 | monitor_speed = 74880 22 | upload_speed = 921600 23 | build_type = debug 24 | monitor_filters = esp8266_exception_decoder 25 | 26 | [env:p1mon] 27 | build_type = release 28 | monitor_filters = 29 | src_filter = + 30 | 31 | [env:p1mon_ota] 32 | extends = env:p1mon 33 | upload_protocol = espota 34 | upload_port = ESP-P1-POWER-D8F15B14734B.fritz.box 35 | upload_flags = 36 | --auth="MONITOR_POWER_42" 37 | build_flags = 38 | '-DOTA' 39 | 40 | [env:p1mon_7n1_ota] 41 | extends = env:p1mon 42 | upload_protocol = espota 43 | upload_port = ESP-P1-POWER-D8F15B14734B.fritz.box 44 | build_flags = 45 | '-DSERIAL_BITS=SERIAL_7N1' 46 | '-DOTA' 47 | upload_flags = 48 | --auth="MONITOR_POWER_42" 49 | 50 | [env:p1detect] 51 | src_filter = + 52 | build_flags = 53 | '-DSERIAL_BITS=SERIAL_7N1' 54 | 55 | [env:p1detect_ota] 56 | extends = env:p1detect 57 | upload_protocol = espota 58 | upload_flags = 59 | --auth="MONITOR_POWER_42" 60 | upload_port = ESP-P1-POWER-D8F15B14734B.fritz.box -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef OTA 3 | #include 4 | #endif 5 | #include 6 | #include "secrets.h" 7 | 8 | #ifndef SERIAL_RATE 9 | #define SERIAL_RATE 115200 10 | #endif 11 | 12 | #ifndef SERIAL_BITS 13 | #define SERIAL_BITS SERIAL_8N1 14 | #endif 15 | 16 | WiFiServer server(23); 17 | WiFiClient current_client; 18 | 19 | #define IDEAL_PACKET_SIZE 1024 20 | void setup() { 21 | // we start a serial, but invert it 22 | Serial.begin(SERIAL_RATE, SERIAL_BITS, SERIAL_RX_ONLY, 1, true); 23 | // we use the internal buffer of the serial class and send from that when we can 24 | // assuming we can send out TCP packets fast enough to not let it overflow 25 | Serial.setRxBufferSize(IDEAL_PACKET_SIZE * 8); 26 | Serial.setDebugOutput(false); 27 | 28 | String hostname = "ESP-P1-POWER-" + WiFi.macAddress(); // allow for multiple esp-p1-power on the network 29 | hostname.replace(":", ""); 30 | WiFi.persistent(false); 31 | WiFi.mode(WIFI_STA); 32 | WiFi.hostname(hostname.c_str()); 33 | WiFi.begin(WIFI_ACCESPOINT, WIFI_PASSWORD); 34 | for (int i = 0 ; i < 20; i++) { 35 | if (WiFi.status() == WL_CONNECTED) { 36 | break; 37 | } 38 | Serial.println("No WIFI connection yet"); 39 | delay(500); 40 | } 41 | server.begin(); 42 | server.setNoDelay(true); 43 | 44 | #ifdef OTA 45 | ArduinoOTA.setPassword("MONITOR_POWER_42"); 46 | ArduinoOTA.setHostname(hostname.c_str()); 47 | ArduinoOTA.begin(); 48 | #endif 49 | } 50 | 51 | unsigned long lastUpdate = 0; 52 | #ifdef OTA 53 | unsigned long lastOTACheck = 0; 54 | #endif 55 | 56 | void loop() { 57 | auto now = millis(); 58 | 59 | auto available = Serial.peekAvailable(); 60 | if (available >= IDEAL_PACKET_SIZE || (available > 0 && (now - lastUpdate) > 50)) { 61 | auto buffer = Serial.peekBuffer(); 62 | if (current_client && current_client.connected()) { 63 | auto written = current_client.write(buffer, available); 64 | Serial.peekConsume(written); 65 | lastUpdate = now; 66 | } 67 | else { 68 | Serial.peekConsume(0); 69 | } 70 | } 71 | if (server.hasClient()) { 72 | current_client.stop(); 73 | current_client = server.available(); 74 | } 75 | #ifdef OTA 76 | if (now - lastOTACheck > 1000) { 77 | ArduinoOTA.handle(); 78 | lastOTACheck = now; 79 | } 80 | else { 81 | delay(10); 82 | } 83 | #else 84 | delay(10); 85 | #endif 86 | } -------------------------------------------------------------------------------- /src/detect-rate.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "secrets.h" 5 | 6 | #define IDEAL_PACKET_SIZE 1024 7 | 8 | WiFiServer server(23); 9 | WiFiClient current_client; 10 | 11 | static size_t baudRateIndex = 0; 12 | 13 | static const long baudRates[] = { 14 | 600, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 57600, 115200, 128000, 256000 15 | }; 16 | 17 | static char header[128] = {0}; 18 | 19 | #ifndef SERIAL_BITS 20 | #define SERIAL_BITS SERIAL_8N1 21 | #endif 22 | 23 | void setupSerial() { 24 | if (current_client) { 25 | sprintf(header, "\n\n*** switching to baud rate: %lu ****\n\n", baudRates[baudRateIndex]); 26 | current_client.write(header); 27 | } 28 | Serial.begin(baudRates[baudRateIndex], SERIAL_BITS, SERIAL_FULL, 1, true); 29 | Serial.setDebugOutput(false); 30 | } 31 | 32 | void setup() { 33 | setupSerial(); 34 | 35 | String hostname = "ESP-P1-POWER-" + WiFi.macAddress(); // allow for multiple esp-p1-power on the network 36 | hostname.replace(":", ""); 37 | WiFi.persistent(false); 38 | WiFi.mode(WIFI_STA); 39 | WiFi.hostname(hostname.c_str()); 40 | WiFi.begin(WIFI_ACCESPOINT, WIFI_PASSWORD); 41 | for (int i = 0 ; i < 20; i++) { 42 | if (WiFi.status() == WL_CONNECTED) { 43 | break; 44 | } 45 | Serial.println("No WIFI connection yet"); 46 | delay(500); 47 | } 48 | server.begin(); 49 | server.setNoDelay(true); 50 | ArduinoOTA.setPassword("MONITOR_POWER_42"); 51 | ArduinoOTA.setHostname(hostname.c_str()); 52 | ArduinoOTA.begin(); 53 | 54 | } 55 | 56 | #define MIN(a, b) (((a) > (b)) ? (b) : (a)) 57 | 58 | static unsigned long lastBaudSwitch = 0; 59 | static unsigned long lastUpdate = 0; 60 | static unsigned long shared = 0; 61 | void loop() { 62 | auto now = millis(); 63 | auto available = Serial.peekAvailable(); 64 | if (available >= IDEAL_PACKET_SIZE || (available > 0 && (now - lastUpdate) > 50)) { 65 | auto buffer = Serial.peekBuffer(); 66 | if (current_client && current_client.connected()) { 67 | auto written = current_client.write(buffer, MIN(available, IDEAL_PACKET_SIZE)); 68 | Serial.peekConsume(written); 69 | shared += written; 70 | lastUpdate = now; 71 | if (shared > (IDEAL_PACKET_SIZE * 2) || (now - lastBaudSwitch) > 20000) { 72 | baudRateIndex++; 73 | if (baudRateIndex >= (sizeof(baudRates) / sizeof(long))) { 74 | baudRateIndex = 0; 75 | } 76 | shared = 0; 77 | lastBaudSwitch = now; 78 | setupSerial(); 79 | } 80 | } 81 | else { 82 | Serial.peekConsume(0); 83 | } 84 | } 85 | if ((now - lastUpdate > 20000) && current_client) { 86 | lastUpdate = now; 87 | current_client.write("\nNo data received\n"); 88 | } 89 | if (server.hasClient()) { 90 | current_client.stop(); 91 | current_client = server.available(); 92 | } 93 | delay(1); 94 | ArduinoOTA.handle(); 95 | } --------------------------------------------------------------------------------