// 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