commit 401777e1ee402f15fd1b8e363a9ab611c43e5a16 Author: Maurice Makaay Date: Thu Dec 24 13:19:37 2020 +0100 first commit diff --git a/P1Meter/P1Meter.ino b/P1Meter/P1Meter.ino new file mode 100644 index 0000000..a1468a4 --- /dev/null +++ b/P1Meter/P1Meter.ino @@ -0,0 +1,282 @@ +// Some useful websites that I used for writing this sketch: +// +// DSMR parser library: https://github.com/matthijskooijman/arduino-dsmr +// MQTT example code: https://www.instructables.com/MQTT-Bare-Minimum-Sketch/ +// Circuit that inspired mine: https://klushok.etv.tudelft.nl/projects/view?id=8 + +#include +#include +#include +#include "dsmr.h" + +// ----------------------------------------------------------------------- +// Update these for your configuration + +#define wifi_ssid "****" +#define wifi_password "*****" +#define mqtt_server "*****" +#define mqtt_port 1883 +#define mqtt_user "*****" +#define mqtt_password "*****" + +// When defined, no serial data are read, but instead the stub +// data from below is used. This is useful to test the WiFi and +// MQTT connection, while not being connected to an actual +// smart meter. +//#define TEST_DATA + +// ----------------------------------------------------------------------- + +#ifdef TEST_DATA +const char test_msg[] = + "/KFM5KAIFA-METER\r\n" + "\r\n" + "1-3:0.2.8(40)\r\n" + "0-0:1.0.0(150117185916W)\r\n" + "0-0:96.1.1(0000000000000000000000000000000000)\r\n" + "1-0:1.8.1(000671.578*kWh)\r\n" + "1-0:1.8.2(000842.472*kWh)\r\n" + "1-0:2.8.1(000000.000*kWh)\r\n" + "1-0:2.8.2(000000.000*kWh)\r\n" + "0-0:96.14.0(0001)\r\n" + "1-0:1.7.0(00.333*kW)\r\n" + "1-0:2.7.0(00.000*kW)\r\n" + "0-0:17.0.0(999.9*kW)\r\n" + "0-0:96.3.10(1)\r\n" + "0-0:96.7.21(00008)\r\n" + "0-0:96.7.9(00007)\r\n" + "1-0:99.97.0(1)(0-0:96.7.19)(000101000001W)(2147483647*s)\r\n" + "1-0:32.32.0(00000)\r\n" + "1-0:32.36.0(00000)\r\n" + "0-0:96.13.1()\r\n" + "0-0:96.13.0()\r\n" + "1-0:31.7.0(001*A)\r\n" + "1-0:21.7.0(00.332*kW)\r\n" + "1-0:22.7.0(00.000*kW)\r\n" + "0-1:24.1.0(003)\r\n" + "0-1:96.1.0(0000000000000000000000000000000000)\r\n" + "0-1:24.2.1(150117180000W)(00473.789*m3)\r\n" + "0-1:24.4.0(1)\r\n" + "!6F4A\r\n"; +#endif + +// All possible items that can be received from the P1 port, +// depending on the DSMR version used. This sketch tries to +// fetch all of them, and publishes the items that actually +// were received. When desired, you can safely remove unneeded +// items from this definition by simply deleting their lines. +using MeterData = ParsedData< + /* String */ identification, + /* String */ p1_version, + /* String */ timestamp, + /* String */ equipment_id, + /* FixedValue */ energy_delivered_tariff1, + /* FixedValue */ energy_delivered_tariff2, + /* FixedValue */ energy_returned_tariff1, + /* FixedValue */ energy_returned_tariff2, + /* String */ electricity_tariff, + /* FixedValue */ power_delivered, + /* FixedValue */ power_returned, + /* FixedValue */ electricity_threshold, + /* uint8_t */ electricity_switch_position, + /* uint32_t */ electricity_failures, + /* uint32_t */ electricity_long_failures, + /* String */ electricity_failure_log, + /* uint32_t */ electricity_sags_l1, + /* uint32_t */ electricity_sags_l2, + /* uint32_t */ electricity_sags_l3, + /* uint32_t */ electricity_swells_l1, + /* uint32_t */ electricity_swells_l2, + /* uint32_t */ electricity_swells_l3, + /* String */ message_short, + /* String */ message_long, + /* FixedValue */ voltage_l1, + /* FixedValue */ voltage_l2, + /* FixedValue */ voltage_l3, + /* FixedValue */ current_l1, + /* FixedValue */ current_l2, + /* FixedValue */ current_l3, + /* FixedValue */ power_delivered_l1, + /* FixedValue */ power_delivered_l2, + /* FixedValue */ power_delivered_l3, + /* FixedValue */ power_returned_l1, + /* FixedValue */ power_returned_l2, + /* FixedValue */ power_returned_l3, + /* uint16_t */ gas_device_type, + /* String */ gas_equipment_id, + /* uint8_t */ gas_valve_position, + /* TimestampedFixedValue */ gas_delivered, + /* uint16_t */ thermal_device_type, + /* String */ thermal_equipment_id, + /* uint8_t */ thermal_valve_position, + /* TimestampedFixedValue */ thermal_delivered, + /* uint16_t */ water_device_type, + /* String */ water_equipment_id, + /* uint8_t */ water_valve_position, + /* TimestampedFixedValue */ water_delivered, + /* uint16_t */ slave_device_type, + /* String */ slave_equipment_id, + /* uint8_t */ slave_valve_position, + /* TimestampedFixedValue */ slave_delivered +>; + +// My circuit has the request PIN always HIGH (because it is +// directly connected to the 5V power supply of the smart meter. +// PIN 3 (GPIO2) is not connected, so I'll feed that one as a +// stub here. Maybe it's an idea to hook up a LED + resistor +// to this pin for some visual feedback, making it an 'I am +// reading data' indicator. +P1Reader reader(&Serial, 3); + +WiFiClient wifiClient; +PubSubClient pubsubClient; + +void setup() { + Serial.begin(115200, SERIAL_8N1); + delay(1000); // Wait a bit for the serial port to wake up. + Serial.println("Setting up device ..."); + setup_wifi(); + setup_pubsub(); + Serial.println("Starting the main loop ..."); +} + +void setup_wifi() { + // We start by connecting to a WiFi network + Serial.print("Setup connection to WiFi network '"); + Serial.print(wifi_ssid); + Serial.print("' "); + WiFi.begin(wifi_ssid, wifi_password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); +} + +void setup_pubsub() { + Serial.println("Setup MQTT broker connection"); + pubsubClient.setClient(wifiClient); + pubsubClient.setServer(mqtt_server, mqtt_port); +} + +void reconnect() { + // Loop until we're reconnected + while (!pubsubClient.connected()) { + Serial.print("Attempting MQTT connection ... "); + if (pubsubClient.connect("ESP8266Client", mqtt_user, mqtt_password)) { + Serial.println("connected"); + } else { + Serial.print("failed, rc="); + Serial.print(pubsubClient.state()); + Serial.println(", try again in 5 seconds"); + // Wait 5 seconds before retrying + delay(5000); + } + } +} + +struct Printer { + template + void apply(Item &i) { + if (i.present()) { + if (!pubsubClient.connected()) { + reconnect(); + } + String key = String("smartmeter/"); + key.concat(Item::name); + + String value = String(i.val()); + value.concat(" "); + value.concat(Item::unit()); + + Serial.print("> "); + Serial.print(key); + Serial.print(" = "); + Serial.println(value); + pubsubClient.publish(key.c_str(), value.c_str(), true); + } + } +}; + +// Implementation of the main loop using test data. +#ifdef TEST_DATA + +void loop() { + MeterData data; + String err; + + Serial.println("Parsing test data"); + ParseResult res = P1Parser::parse(&data, test_msg, lengthof(test_msg)); + if (res.err) { + Serial.println(res.fullError(test_msg, test_msg + lengthof(test_msg))); + if (!pubsubClient.connected()) { + reconnect(); + } + if (pubsubClient.connected()) { + pubsubClient.publish("smartmeter/error", res.fullError(test_msg, test_msg + lengthof(test_msg)).c_str(), true); + } +// } else if (!data.all_present()) { +// Serial.println("Some fields are missing"); + } else { + data.applyEach(Printer()); + // Succesfully parsed, print results: + Serial.println(data.identification); + Serial.print(data.power_delivered.int_val()); + Serial.println("W"); + } + + delay(10000); +} + +// Implementation of the main loop using serial P1 data. +# else + +long waitLoop = 0; + +void loop() { + // Builds a complete telegram. + reader.loop(); + + // Every 4 seconds, start a new telegram reading operation. + // The smart meter will provide a telegram every 10 seconds. + // By restarting every 4 seconds, we know that we will be + // in sync with the P1 output at some point. A bit crude, but + // I don't have a real RTS request line in my current + // hardware design. + if (millis() > waitLoop) { + Serial.println("Start new telegram read operation"); + waitLoop = millis() + 4000; + reader.enable(true); + } + + // True when a new telegram is read. + if (reader.available()) { + MeterData data; + String err; + + Serial.print("Parsing data from P1 ... "); + if (reader.parse(&data, &err)) { + Serial.println("OK"); + data.applyEach(Printer()); + } else { + // When the parser fails, we log the error to console + // and to an MQTT topic. + Serial.println("ERROR"); + Serial.println(err); + + if (!pubsubClient.connected()) { + reconnect(); + } + if (pubsubClient.connected()) { + pubsubClient.publish("smartmeter/error", err.c_str(), true); + } + } + } +} + +#endif // TEST OR PRODUCTION diff --git a/README.md b/README.md new file mode 100644 index 0000000..3bb61b5 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Smart Meter to MQTT, using an ESP-01 diff --git a/doc/1 end result - open case.jpg b/doc/1 end result - open case.jpg new file mode 100644 index 0000000..4280056 Binary files /dev/null and b/doc/1 end result - open case.jpg differ diff --git a/doc/2 end result - closed case.jpg b/doc/2 end result - closed case.jpg new file mode 100644 index 0000000..cf609e5 Binary files /dev/null and b/doc/2 end result - closed case.jpg differ diff --git a/doc/2N2222_transisto_pinout.gif b/doc/2N2222_transisto_pinout.gif new file mode 100644 index 0000000..3033caa Binary files /dev/null and b/doc/2N2222_transisto_pinout.gif differ diff --git a/doc/3 installed and sending data.mp4 b/doc/3 installed and sending data.mp4 new file mode 100644 index 0000000..432d2a6 Binary files /dev/null and b/doc/3 installed and sending data.mp4 differ diff --git a/doc/ESP-01_pinout.jpg b/doc/ESP-01_pinout.jpg new file mode 100644 index 0000000..a42e3fa Binary files /dev/null and b/doc/ESP-01_pinout.jpg differ diff --git a/doc/Slimme Meter specs.pdf b/doc/Slimme Meter specs.pdf new file mode 100644 index 0000000..c35fe86 Binary files /dev/null and b/doc/Slimme Meter specs.pdf differ diff --git a/doc/circuit.jpg b/doc/circuit.jpg new file mode 100644 index 0000000..02df1fa Binary files /dev/null and b/doc/circuit.jpg differ