diff --git a/.gitignore b/.gitignore index 69faa27d..69576c24 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ [Rr]elease/ **/__vm/ .DS_Store +*.sw[op] .vscode .pio platformio.ini diff --git a/Arduino Code/AmsToMqttBridge/AmsToMqttBridge.ino b/Arduino Code/AmsToMqttBridge/AmsToMqttBridge.ino index 1ff202c6..d9c27473 100644 --- a/Arduino Code/AmsToMqttBridge/AmsToMqttBridge.ino +++ b/Arduino Code/AmsToMqttBridge/AmsToMqttBridge.ino @@ -5,27 +5,48 @@ */ -#include -#include +#define HAS_DALLAS_TEMP_SENSOR 1 // Set to zero if Dallas one wire temp sensor is not present +#define IS_CUSTOM_AMS_BOARD 1 // Set to zero if using NodeMCU or board not designed by Roar Fredriksen + #include #include -#include -#include -#include -#include -#include "configuration.h" -#include "accesspoint.h" + +#if HAS_DALLAS_TEMP_SENSOR +#include +#include +#endif + +#if defined(ESP8266) +#include +#elif defined(ESP32) +#include +#endif + +#include "HanConfigAp.h" +#include "HanReader.h" +#include "HanToJson.h" #define WIFI_CONNECTION_TIMEOUT 30000; + +#if IS_CUSTOM_AMS_BOARD +#define LED_PIN 2 // The blue on-board LED of the ESP8266 custom AMS board +#define LED_ACTIVE_HIGH 0 +#define AP_BUTTON_PIN 0 +#else +#define LED_PIN LED_BUILTIN +#define LED_ACTIVE_HIGH 1 +#define AP_BUTTON_PIN INVALID_BUTTON_PIN +#endif + +#if HAS_DALLAS_TEMP_SENSOR #define TEMP_SENSOR_PIN 5 // Temperature sensor connected to GPIO5 -#define LED_PIN 2 // The blue on-board LED of the ESP OneWire oneWire(TEMP_SENSOR_PIN); DallasTemperature tempSensor(&oneWire); -long lastTempDebug = 0; +#endif // Object used to boot as Access Point -accesspoint ap; +HanConfigAp ap; // WiFi client and MQTT client WiFiClient *client; @@ -38,37 +59,40 @@ HardwareSerial* debugger = NULL; HanReader hanReader; // the setup function runs once when you press reset or power the board -void setup() +void setup() { // Uncomment to debug over the same port as used for HAN communication - debugger = &Serial; - + //debugger = &Serial; + if (debugger) { // Setup serial port for debugging debugger->begin(2400, SERIAL_8E1); - while (!&debugger); + //debugger->begin(115200); + while (!debugger); + debugger->println(""); debugger->println("Started..."); } - // Assign pin for boot as AP - delay(1000); - pinMode(0, INPUT_PULLUP); - - // Flash the blue LED, to indicate we can boot as AP now + // Flash the LED, to indicate we can boot as AP now pinMode(LED_PIN, OUTPUT); - digitalWrite(LED_PIN, LOW); - + led_on(); + + delay(1000); + // Initialize the AP - ap.setup(0, Serial); - - // Turn off the blue LED - digitalWrite(LED_PIN, HIGH); + ap.setup(AP_BUTTON_PIN, debugger); + + led_off(); if (!ap.isActivated) { setupWiFi(); - hanReader.setup(&Serial, 2400, SERIAL_8E1, 0); - + // Configure uart for AMS data + Serial.begin(2400, SERIAL_8E1); + while (!Serial); + + hanReader.setup(&Serial, debugger); + // Compensate for the known Kaifa bug hanReader.compensateFor09HeaderBug = (ap.config.meterType == 1); } @@ -80,8 +104,8 @@ void loop() // Only do normal stuff if we're not booted as AP if (!ap.loop()) { - // turn off the blue LED - digitalWrite(LED_PIN, HIGH); + // Turn off the LED + led_off(); // allow the MQTT client some resources mqtt.loop(); @@ -99,34 +123,57 @@ void loop() } else { - // Continously flash the blue LED when AP mode - if (millis() / 1000 % 2 == 0) - digitalWrite(LED_PIN, LOW); - else - digitalWrite(LED_PIN, HIGH); + // Continously flash the LED when AP mode + if (millis() / 1000 % 2 == 0) led_on(); + else led_off(); } } + +void led_on() +{ +#if LED_ACTIVE_HIGH + digitalWrite(LED_PIN, HIGH); +#else + digitalWrite(LED_PIN, LOW); +#endif +} + + +void led_off() +{ +#if LED_ACTIVE_HIGH + digitalWrite(LED_PIN, LOW); +#else + digitalWrite(LED_PIN, HIGH); +#endif +} + + void setupWiFi() { // Turn off AP WiFi.enableAP(false); - + // Connect to WiFi - WiFi.mode(WIFI_STA); + WiFi.mode(WIFI_STA); WiFi.begin(ap.config.ssid, ap.config.ssidPassword); - + + // Wait for WiFi connection + if (debugger) debugger->print("\nWaiting for WiFi to connect..."); while (WiFi.status() != WL_CONNECTED) { + if (debugger) debugger->print("."); delay(500); } - - client = new WiFiClient(); + if (debugger) debugger->println(" connected"); + + client = new WiFiClient(); mqtt.begin(ap.config.mqtt, *client); // Direct incoming MQTT messages if (ap.config.mqttSubscribeTopic != 0 && strlen(ap.config.mqttSubscribeTopic) > 0) { - mqtt.subscribe(ap.config.mqttSubscribeTopic); - mqtt.onMessage(mqttMessageReceived); + mqtt.subscribe(ap.config.mqttSubscribeTopic); + mqtt.onMessage(mqttMessageReceived); } // Notify everyone we're here! @@ -153,333 +200,65 @@ void readHanPort() if (hanReader.read()) { // Flash LED on, this shows us that data is received - digitalWrite(LED_PIN, LOW); + led_on(); - // Get the list identifier - int listSize = hanReader.getListSize(); + // Get the timestamp (as unix time) from the package + time_t time = hanReader.getPackageTime(); + if (debugger) debugger->print("Time of the package is: "); + if (debugger) debugger->println(time); - switch (ap.config.meterType) + // Define a json object to keep the data + StaticJsonDocument<500> json; + + // Any generic useful info here + json["id"] = WiFi.macAddress(); + json["up"] = millis(); + json["t"] = time; + + // Add a sub-structure to the json object, + // to keep the data from the meter itself + JsonObject data = json.createNestedObject("data"); + +#if HAS_DALLAS_TEMP_SENSOR + // Get the temperature too + tempSensor.requestTemperatures(); + data["temp"] = tempSensor.getTempCByIndex(0); +#endif + + hanToJson(data, ap.config.meterType, hanReader); + + // Write the json to the debug port + if (debugger) { + debugger->print("Sending data to MQTT: "); + serializeJsonPretty(json, *debugger); + debugger->println(); + } + + // Make sure we have configured a publish topic + if (! ap.config.mqttPublishTopic == 0 || strlen(ap.config.mqttPublishTopic) == 0) { - case 1: // Kaifa - readHanPort_Kaifa(listSize); - break; - case 2: // Aidon - readHanPort_Aidon(listSize); - break; - case 3: // Kamstrup - readHanPort_Kamstrup(listSize); - break; - default: - debugger->print("Meter type "); - debugger->print(ap.config.meterType, HEX); - debugger->println(" is unknown"); - delay(10000); - break; + // Publish the json to the MQTT server + String msg; + serializeJson(json, msg); + + mqtt.publish(ap.config.mqttPublishTopic, msg.c_str()); + mqtt.loop(); } // Flash LED off - digitalWrite(LED_PIN, HIGH); + led_off(); } } -void readHanPort_Aidon(int listSize) -{ - if (listSize == (int)Aidon::List1 || listSize == (int)Aidon::List2 || listSize == (int)Aidon::List3 || listSize == (int)Aidon::List2_1p) - { - if (listSize == (int)Aidon::List2) - { - String id = hanReader.getString((int)Aidon_List2::ListVersionIdentifier); - if (debugger) debugger->println(id); - } - - // Get the timestamp (as unix time) from the package - time_t time = hanReader.getPackageTime(); - if (debugger) debugger->print("Time of the package is: "); - if (debugger) debugger->println(time); - - // Define a json object to keep the data - StaticJsonBuffer<500> jsonBuffer; - JsonObject& root = jsonBuffer.createObject(); - - // Any generic useful info here - root["id"] = WiFi.macAddress(); - root["up"] = millis(); - root["t"] = time; - - // Add a sub-structure to the json object, - // to keep the data from the meter itself - JsonObject& data = root.createNestedObject("data"); - - // Get the temperature too - tempSensor.requestTemperatures(); - float temperature = tempSensor.getTempCByIndex(0); - data["temp"] = temperature; - - // Based on the list number, get all details - // according to OBIS specifications for the meter - if (listSize == (int)Aidon::List1) - { - data["P"] = hanReader.getInt((int)Aidon_List1::ActiveImportPower); - } - else if (listSize == (int)Aidon::List2) - { - data["lv"] = hanReader.getString((int)Aidon_List2::ListVersionIdentifier); - data["id"] = hanReader.getString((int)Aidon_List2::MeterID); - data["type"] = hanReader.getString((int)Aidon_List2::MeterType); - data["P"] = hanReader.getInt((int)Aidon_List2::ActiveImportPower); - data["Q"] = hanReader.getInt((int)Aidon_List2::ReactiveExportPower); - data["I1"] = ((double) hanReader.getInt((int)Aidon_List2::CurrentL1)) / 10; - data["I2"] = ((double) hanReader.getInt((int)Aidon_List2::CurrentL2)) / 10; - data["I3"] = ((double) hanReader.getInt((int)Aidon_List2::CurrentL3)) / 10; - data["U1"] = ((double) hanReader.getInt((int)Aidon_List2::VoltageL1)) / 10; - data["U2"] = ((double) hanReader.getInt((int)Aidon_List2::VoltageL2)) / 10; - data["U3"] = ((double) hanReader.getInt((int)Aidon_List2::VoltageL3)) / 10; - } - else if (listSize == (int)Aidon::List2_1p) - { - data["lv"] = hanReader.getString((int)Aidon_List2_1p::ListVersionIdentifier); - data["id"] = hanReader.getString((int)Aidon_List2_1p::MeterID); - data["type"] = hanReader.getString((int)Aidon_List2_1p::MeterType); - data["P"] = hanReader.getInt((int)Aidon_List2_1p::ActiveImportPower); - data["Q"] = hanReader.getInt((int)Aidon_List2_1p::ReactiveExportPower); - data["I1"] = ((double) hanReader.getInt((int)Aidon_List2_1p::Current)) / 10; - data["U1"] = ((double) hanReader.getInt((int)Aidon_List2_1p::Voltage)) / 10; - } - else if (listSize == (int)Aidon::List3) - { - data["lv"] = hanReader.getString((int)Aidon_List3::ListVersionIdentifier); - data["id"] = hanReader.getString((int)Aidon_List3::MeterID); - data["type"] = hanReader.getString((int)Aidon_List3::MeterType); - data["P"] = hanReader.getInt((int)Aidon_List3::ActiveImportPower); - data["Q"] = hanReader.getInt((int)Aidon_List3::ReactiveExportPower); - data["I1"] = ((double) hanReader.getInt((int)Aidon_List3::CurrentL1)) / 10; - data["I2"] = ((double) hanReader.getInt((int)Aidon_List3::CurrentL2)) / 10; - data["I3"] = ((double) hanReader.getInt((int)Aidon_List3::CurrentL3)) / 10; - data["U1"] = ((double) hanReader.getInt((int)Aidon_List3::VoltageL1)) / 10; - data["U2"] = ((double) hanReader.getInt((int)Aidon_List3::VoltageL2)) / 10; - data["U3"] = ((double) hanReader.getInt((int)Aidon_List3::VoltageL3)) / 10; - data["tPI"] = hanReader.getInt((int)Aidon_List3::CumulativeActiveImportEnergy); - data["tPO"] = hanReader.getInt((int)Aidon_List3::CumulativeActiveExportEnergy); - data["tQI"] = hanReader.getInt((int)Aidon_List3::CumulativeReactiveImportEnergy); - data["tQO"] = hanReader.getInt((int)Aidon_List3::CumulativeReactiveExportEnergy); - } - - // Write the json to the debug port - if (debugger) { - debugger->print("Sending data to MQTT: "); - root.printTo(*debugger); - debugger->println(); - } - - // Make sure we have configured a publish topic - if (ap.config.mqttPublishTopic == 0 || strlen(ap.config.mqttPublishTopic) == 0) - return; - - // Publish the json to the MQTT server - char msg[1024]; - root.printTo(msg, 1024); - mqtt.publish(ap.config.mqttPublishTopic, msg); - mqtt.loop(); - } -} - -void readHanPort_Kamstrup(int listSize) -{ - // Only care for the ACtive Power Imported, which is found in the first list - if (listSize == (int)Kamstrup::List1 || listSize == (int)Kamstrup::List2) - { - if (listSize == (int)Kamstrup::List1) - { - String id = hanReader.getString((int)Kamstrup_List1::ListVersionIdentifier); - if (debugger) debugger->println(id); - } - else if (listSize == (int)Kamstrup::List2) - { - String id = hanReader.getString((int)Kamstrup_List2::ListVersionIdentifier); - if (debugger) debugger->println(id); - } - - // Get the timestamp (as unix time) from the package - time_t time = hanReader.getPackageTime(); - if (debugger) debugger->print("Time of the package is: "); - if (debugger) debugger->println(time); - - // Define a json object to keep the data - StaticJsonBuffer<500> jsonBuffer; - JsonObject& root = jsonBuffer.createObject(); - - // Any generic useful info here - root["id"] = WiFi.macAddress(); - root["up"] = millis(); - root["t"] = time; - - // Add a sub-structure to the json object, - // to keep the data from the meter itself - JsonObject& data = root.createNestedObject("data"); - - // Get the temperature too - tempSensor.requestTemperatures(); - float temperature = tempSensor.getTempCByIndex(0); - data["temp"] = temperature; - - // Based on the list number, get all details - // according to OBIS specifications for the meter - if (listSize == (int)Kamstrup::List1) - { - data["lv"] = hanReader.getString((int)Kamstrup_List1::ListVersionIdentifier); - data["id"] = hanReader.getString((int)Kamstrup_List1::MeterID); - data["type"] = hanReader.getString((int)Kamstrup_List1::MeterType); - data["P"] = hanReader.getInt((int)Kamstrup_List1::ActiveImportPower); - data["Q"] = hanReader.getInt((int)Kamstrup_List1::ReactiveImportPower); - data["I1"] = hanReader.getInt((int)Kamstrup_List1::CurrentL1); - data["I2"] = hanReader.getInt((int)Kamstrup_List1::CurrentL2); - data["I3"] = hanReader.getInt((int)Kamstrup_List1::CurrentL3); - data["U1"] = hanReader.getInt((int)Kamstrup_List1::VoltageL1); - data["U2"] = hanReader.getInt((int)Kamstrup_List1::VoltageL2); - data["U3"] = hanReader.getInt((int)Kamstrup_List1::VoltageL3); - } - else if (listSize == (int)Kamstrup::List2) - { - data["lv"] = hanReader.getString((int)Kamstrup_List2::ListVersionIdentifier);; - data["id"] = hanReader.getString((int)Kamstrup_List2::MeterID); - data["type"] = hanReader.getString((int)Kamstrup_List2::MeterType); - data["P"] = hanReader.getInt((int)Kamstrup_List2::ActiveImportPower); - data["Q"] = hanReader.getInt((int)Kamstrup_List2::ReactiveImportPower); - data["I1"] = hanReader.getInt((int)Kamstrup_List2::CurrentL1); - data["I2"] = hanReader.getInt((int)Kamstrup_List2::CurrentL2); - data["I3"] = hanReader.getInt((int)Kamstrup_List2::CurrentL3); - data["U1"] = hanReader.getInt((int)Kamstrup_List2::VoltageL1); - data["U2"] = hanReader.getInt((int)Kamstrup_List2::VoltageL2); - data["U3"] = hanReader.getInt((int)Kamstrup_List2::VoltageL3); - data["tPI"] = hanReader.getInt((int)Kamstrup_List2::CumulativeActiveImportEnergy); - data["tPO"] = hanReader.getInt((int)Kamstrup_List2::CumulativeActiveExportEnergy); - data["tQI"] = hanReader.getInt((int)Kamstrup_List2::CumulativeReactiveImportEnergy); - data["tQO"] = hanReader.getInt((int)Kamstrup_List2::CumulativeReactiveExportEnergy); - } - - // Write the json to the debug port - if (debugger) { - debugger->print("Sending data to MQTT: "); - root.printTo(*debugger); - debugger->println(); - } - - // Make sure we have configured a publish topic - if (ap.config.mqttPublishTopic == 0 || strlen(ap.config.mqttPublishTopic) == 0) - return; - - // Publish the json to the MQTT server - char msg[1024]; - root.printTo(msg, 1024); - mqtt.publish(ap.config.mqttPublishTopic, msg); - } -} - - -void readHanPort_Kaifa(int listSize) -{ - // Only care for the ACtive Power Imported, which is found in the first list - if (listSize == (int)Kaifa::List1 || listSize == (int)Kaifa::List2 || listSize == (int)Kaifa::List3) - { - if (listSize == (int)Kaifa::List1) - { - if (debugger) debugger->println(" (list #1 has no ID)"); - } - else - { - String id = hanReader.getString((int)Kaifa_List2::ListVersionIdentifier); - if (debugger) debugger->println(id); - } - - // Get the timestamp (as unix time) from the package - time_t time = hanReader.getPackageTime(); - if (debugger) debugger->print("Time of the package is: "); - if (debugger) debugger->println(time); - - // Define a json object to keep the data - //StaticJsonBuffer<500> jsonBuffer; - DynamicJsonBuffer jsonBuffer; - JsonObject& root = jsonBuffer.createObject(); - - // Any generic useful info here - root["id"] = WiFi.macAddress(); - root["up"] = millis(); - root["t"] = time; - - // Add a sub-structure to the json object, - // to keep the data from the meter itself - JsonObject& data = root.createNestedObject("data"); - - // Get the temperature too - tempSensor.requestTemperatures(); - float temperature = tempSensor.getTempCByIndex(0); - data["temp"] = String(temperature); - - // Based on the list number, get all details - // according to OBIS specifications for the meter - if (listSize == (int)Kaifa::List1) - { - data["P"] = hanReader.getInt((int)Kaifa_List1::ActivePowerImported); - } - else if (listSize == (int)Kaifa::List2) - { - data["lv"] = hanReader.getString((int)Kaifa_List2::ListVersionIdentifier); - data["id"] = hanReader.getString((int)Kaifa_List2::MeterID); - data["type"] = hanReader.getString((int)Kaifa_List2::MeterType); - data["P"] = hanReader.getInt((int)Kaifa_List2::ActiveImportPower); - data["Q"] = hanReader.getInt((int)Kaifa_List2::ReactiveImportPower); - data["I1"] = hanReader.getInt((int)Kaifa_List2::CurrentL1); - data["I2"] = hanReader.getInt((int)Kaifa_List2::CurrentL2); - data["I3"] = hanReader.getInt((int)Kaifa_List2::CurrentL3); - data["U1"] = hanReader.getInt((int)Kaifa_List2::VoltageL1); - data["U2"] = hanReader.getInt((int)Kaifa_List2::VoltageL2); - data["U3"] = hanReader.getInt((int)Kaifa_List2::VoltageL3); - } - else if (listSize == (int)Kaifa::List3) - { - data["lv"] = hanReader.getString((int)Kaifa_List3::ListVersionIdentifier);; - data["id"] = hanReader.getString((int)Kaifa_List3::MeterID); - data["type"] = hanReader.getString((int)Kaifa_List3::MeterType); - data["P"] = hanReader.getInt((int)Kaifa_List3::ActiveImportPower); - data["Q"] = hanReader.getInt((int)Kaifa_List3::ReactiveImportPower); - data["I1"] = hanReader.getInt((int)Kaifa_List3::CurrentL1); - data["I2"] = hanReader.getInt((int)Kaifa_List3::CurrentL2); - data["I3"] = hanReader.getInt((int)Kaifa_List3::CurrentL3); - data["U1"] = hanReader.getInt((int)Kaifa_List3::VoltageL1); - data["U2"] = hanReader.getInt((int)Kaifa_List3::VoltageL2); - data["U3"] = hanReader.getInt((int)Kaifa_List3::VoltageL3); - data["tPI"] = hanReader.getInt((int)Kaifa_List3::CumulativeActiveImportEnergy); - data["tPO"] = hanReader.getInt((int)Kaifa_List3::CumulativeActiveExportEnergy); - data["tQI"] = hanReader.getInt((int)Kaifa_List3::CumulativeReactiveImportEnergy); - data["tQO"] = hanReader.getInt((int)Kaifa_List3::CumulativeReactiveExportEnergy); - } - - // Write the json to the debug port - if (debugger) { - debugger->print("Sending data to MQTT: "); - root.printTo(*debugger); - debugger->println(); - } - - // Make sure we have configured a publish topic - if (ap.config.mqttPublishTopic == 0 || strlen(ap.config.mqttPublishTopic) == 0) - return; - - // Publish the json to the MQTT server - char msg[1024]; - root.printTo(msg, 1024); - mqtt.publish(ap.config.mqttPublishTopic, msg); - } -} // Function to connect and reconnect as necessary to the MQTT server. // Should be called in the loop function and it will take care if connecting. -void MQTT_connect() +void MQTT_connect() { // Connect to WiFi access point. if (debugger) { - debugger->println(); + debugger->println(); debugger->println(); debugger->print("Connecting to WiFi network "); debugger->println(ap.config.ssid); @@ -498,7 +277,7 @@ void MQTT_connect() while (WiFi.status() != WL_CONNECTED) { delay(50); if (debugger) debugger->print("."); - + // If we timed out, disconnect and try again if (vTimeout < millis()) { @@ -528,9 +307,8 @@ void MQTT_connect() // Wait for the MQTT connection to complete while (!mqtt.connected()) { - // Connect to a unsecure or secure MQTT server - if ((ap.config.mqttUser == 0 && mqtt.connect(ap.config.mqttClientID)) || + if ((ap.config.mqttUser == 0 && mqtt.connect(ap.config.mqttClientID)) || (ap.config.mqttUser != 0 && mqtt.connect(ap.config.mqttClientID, ap.config.mqttUser, ap.config.mqttPass))) { if (debugger) debugger->println("\nSuccessfully connected to MQTT!"); @@ -559,7 +337,7 @@ void MQTT_connect() // Allow some resources for the WiFi connection yield(); - delay(2000); + delay(2000); } } @@ -576,16 +354,18 @@ void sendMqttData(String data) } // Build a json with the message in a "data" attribute - DynamicJsonBuffer jsonBuffer; - JsonObject& json = jsonBuffer.createObject(); + StaticJsonDocument<500> json; json["id"] = WiFi.macAddress(); json["up"] = millis(); json["data"] = data; // Stringify the json String msg; - json.printTo(msg); + serializeJson(json, msg); // Send the json over MQTT mqtt.publish(ap.config.mqttPublishTopic, msg.c_str()); + + if (debugger) debugger->print("sendMqttData: "); + if (debugger) debugger->println(data); } diff --git a/Arduino Code/Arduino Libraries/HanConfigAp/library.properties b/Arduino Code/Arduino Libraries/HanConfigAp/library.properties new file mode 100644 index 00000000..6fa3db03 --- /dev/null +++ b/Arduino Code/Arduino Libraries/HanConfigAp/library.properties @@ -0,0 +1,9 @@ +name=HanConfigAp +version=1.0.0 +author=roarfred +maintainer=roarfred +sentence=HAN Configuraiton accesspoint +paragraph=HAN Configuraiton accesspoint +category=Sensors +url=https://github.com/roarfred/AmsToMqttBridge +architectures=* diff --git a/Arduino Code/AmsToMqttBridge/accesspoint.cpp b/Arduino Code/Arduino Libraries/HanConfigAp/src/HanConfigAp.cpp similarity index 87% rename from Arduino Code/AmsToMqttBridge/accesspoint.cpp rename to Arduino Code/Arduino Libraries/HanConfigAp/src/HanConfigAp.cpp index f1fe2723..def4e8e0 100644 --- a/Arduino Code/AmsToMqttBridge/accesspoint.cpp +++ b/Arduino Code/Arduino Libraries/HanConfigAp/src/HanConfigAp.cpp @@ -1,19 +1,19 @@ -// -// -// +#include "HanConfigAp.h" -#include "accesspoint.h" +#if defined(ESP8266) +ESP8266WebServer HanConfigAp::server(80); +#elif defined(ESP32) // ARDUINO_ARCH_ESP32 +WebServer HanConfigAp::server(80); +#endif +Stream* HanConfigAp::debugger; -ESP8266WebServer accesspoint::server(80); -Stream* accesspoint::debugger; - -bool accesspoint::hasConfig() { +bool HanConfigAp::hasConfig() { return config.hasConfig(); } -void accesspoint::setup(int accessPointButtonPin, Stream& debugger) +void HanConfigAp::setup(int accessPointButtonPin, Stream* debugger) { - this->debugger = &debugger; + this->debugger = debugger; // Test if we're missing configuration if (!config.hasConfig()) @@ -26,23 +26,29 @@ void accesspoint::setup(int accessPointButtonPin, Stream& debugger) { // Load the configuration config.load(); - if (this->debugger) config.print(debugger); + if (this->debugger) config.print(this->debugger); - // Test if we're holding down the AP pin, over 5 seconds - int time = millis() + 5000; - print("Press the AP button now to boot as access point"); - while (millis() < time) + if (accessPointButtonPin != INVALID_BUTTON_PIN) { - print("."); - if (digitalRead(accessPointButtonPin) == LOW) + // Assign pin for boot as AP + pinMode(accessPointButtonPin, INPUT_PULLUP); + + // Test if we're holding down the AP pin, over 5 seconds + int time = millis() + 5000; + print("Press the AP button now to boot as access point"); + while (millis() < time) { - println(""); - print("AP button was pressed. Booting as access point now. Look for SSID "); - println(this->AP_SSID); - isActivated = true; - break; + print("."); + if (digitalRead(accessPointButtonPin) == LOW) + { + print("AP button was pressed. Booting as access point now. Look for SSID "); + println(this->AP_SSID); + isActivated = true; + break; + } + delay(100); } - delay(100); + println(""); } } @@ -71,7 +77,7 @@ void accesspoint::setup(int accessPointButtonPin, Stream& debugger) } } -bool accesspoint::loop() { +bool HanConfigAp::loop() { if (isActivated) { //DNS @@ -87,7 +93,7 @@ bool accesspoint::loop() { } /** Handle root or redirect to captive portal */ -void accesspoint::handleRoot() { +void HanConfigAp::handleRoot() { println("Serving / over http..."); server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); @@ -102,7 +108,7 @@ void accesspoint::handleRoot() { } -void accesspoint::handleSave() { +void HanConfigAp::handleSave() { configuration *config = new configuration(); String temp; @@ -145,13 +151,17 @@ void accesspoint::handleSave() { println("Saving configuration now..."); - if (accesspoint::debugger) config->print(*accesspoint::debugger); + if (HanConfigAp::debugger) config->print(HanConfigAp::debugger); if (config->save()) { println("Successfully saved. Will roboot now."); String html = "

Successfully Saved!

Device is restarting now...

"; server.send(200, "text/html", html); +#if defined(ESP8266) ESP.reset(); +#elif defined(ESP32) + ESP.restart(); +#endif } else { @@ -161,19 +171,19 @@ void accesspoint::handleSave() { } } -size_t accesspoint::print(const char* text) +size_t HanConfigAp::print(const char* text) { if (debugger) debugger->print(text); } -size_t accesspoint::println(const char* text) +size_t HanConfigAp::println(const char* text) { if (debugger) debugger->println(text); } -size_t accesspoint::print(const Printable& data) +size_t HanConfigAp::print(const Printable& data) { if (debugger) debugger->print(data); } -size_t accesspoint::println(const Printable& data) +size_t HanConfigAp::println(const Printable& data) { if (debugger) debugger->println(data); } diff --git a/Arduino Code/AmsToMqttBridge/accesspoint.h b/Arduino Code/Arduino Libraries/HanConfigAp/src/HanConfigAp.h similarity index 62% rename from Arduino Code/AmsToMqttBridge/accesspoint.h rename to Arduino Code/Arduino Libraries/HanConfigAp/src/HanConfigAp.h index 527a90d6..42eab7c6 100644 --- a/Arduino Code/AmsToMqttBridge/accesspoint.h +++ b/Arduino Code/Arduino Libraries/HanConfigAp/src/HanConfigAp.h @@ -9,14 +9,24 @@ #include "WProgram.h" #endif -#include -#include +#if defined(ESP8266) + #include + #include +#elif defined(ESP32) // ARDUINO_ARCH_ESP32 + #include + #include +#else + #warning "Unsupported board type" +#endif + #include #include "configuration.h" -class accesspoint { +#define INVALID_BUTTON_PIN 0xFFFFFFFF + +class HanConfigAp { public: - void setup(int accessPointButtonPin, Stream& debugger); + void setup(int accessPointButtonPin, Stream* debugger); bool loop(); bool hasConfig(); configuration config; @@ -37,7 +47,11 @@ private: // Web server static void handleRoot(); static void handleSave(); +#if defined(ESP8266) static ESP8266WebServer server; +#elif defined(ESP32) // ARDUINO_ARCH_ESP32 + static WebServer server; +#endif static Stream* debugger; }; diff --git a/Arduino Code/AmsToMqttBridge/configuration.cpp b/Arduino Code/Arduino Libraries/HanConfigAp/src/configuration.cpp similarity index 81% rename from Arduino Code/AmsToMqttBridge/configuration.cpp rename to Arduino Code/Arduino Libraries/HanConfigAp/src/configuration.cpp index 6f0013ef..94031f07 100644 --- a/Arduino Code/AmsToMqttBridge/configuration.cpp +++ b/Arduino Code/Arduino Libraries/HanConfigAp/src/configuration.cpp @@ -145,7 +145,7 @@ int configuration::saveByte(int address, byte value) EEPROM.write(address, value); return 1; } -void configuration::print(Stream& serial) +void configuration::print(Stream* debugger) { /* char* ssid; @@ -161,24 +161,24 @@ void configuration::print(Stream& serial) char* mqttPass; */ - serial.println("Configuration:"); - serial.println("-----------------------------------------------"); - serial.printf("ssid: %s\r\n", this->ssid); - serial.printf("ssidPassword: %s\r\n", this->ssidPassword); - serial.printf("meterType: %i\r\n", this->meterType); - serial.printf("mqtt: %s\r\n", this->mqtt); - serial.printf("mqttPort: %i\r\n", this->mqttPort); - serial.printf("mqttClientID: %s\r\n", this->mqttClientID); - serial.printf("mqttPublishTopic: %s\r\n", this->mqttPublishTopic); - serial.printf("mqttSubscribeTopic: %s\r\n", this->mqttSubscribeTopic); + debugger->println("Configuration:"); + debugger->println("-----------------------------------------------"); + debugger->printf("ssid: %s\r\n", this->ssid); + debugger->printf("ssidPassword: %s\r\n", this->ssidPassword); + debugger->printf("meterType: %i\r\n", this->meterType); + debugger->printf("mqtt: %s\r\n", this->mqtt); + debugger->printf("mqttPort: %i\r\n", this->mqttPort); + debugger->printf("mqttClientID: %s\r\n", this->mqttClientID); + debugger->printf("mqttPublishTopic: %s\r\n", this->mqttPublishTopic); + debugger->printf("mqttSubscribeTopic: %s\r\n", this->mqttSubscribeTopic); if (this->isSecure()) { - serial.printf("SECURE MQTT CONNECTION:\r\n"); - serial.printf("mqttUser: %s\r\n", this->mqttUser); - serial.printf("mqttPass: %s\r\n", this->mqttPass); + debugger->printf("SECURE MQTT CONNECTION:\r\n"); + debugger->printf("mqttUser: %s\r\n", this->mqttUser); + debugger->printf("mqttPass: %s\r\n", this->mqttPass); } - serial.println("-----------------------------------------------"); + debugger->println("-----------------------------------------------"); } template int configuration::writeAnything(int ee, const T& value) diff --git a/Arduino Code/AmsToMqttBridge/configuration.h b/Arduino Code/Arduino Libraries/HanConfigAp/src/configuration.h similarity index 97% rename from Arduino Code/AmsToMqttBridge/configuration.h rename to Arduino Code/Arduino Libraries/HanConfigAp/src/configuration.h index d2855ae5..0dfc86ad 100644 --- a/Arduino Code/AmsToMqttBridge/configuration.h +++ b/Arduino Code/Arduino Libraries/HanConfigAp/src/configuration.h @@ -30,7 +30,7 @@ public: bool save(); bool load(); - void print(Stream& serial); + void print(Stream* debugger); protected: private: diff --git a/Arduino Code/Arduino Libraries/HanReader/src/Aidon.h b/Arduino Code/Arduino Libraries/HanReader/src/Aidon.h index f556a622..9f577dda 100644 --- a/Arduino Code/Arduino Libraries/HanReader/src/Aidon.h +++ b/Arduino Code/Arduino Libraries/HanReader/src/Aidon.h @@ -6,10 +6,11 @@ enum class Aidon { - List1 = 0x01, - List2 = 0x0D, - List2_1p = 0x09, - List3 = 0x12 + List1 = 0x01, + List1PhaseShort = 0x09, + List1PhaseLong = 0xff, // TODO: Need sample + List3PhaseShort = 0x0D, + List3PhaseLong = 0x12 }; enum class Aidon_List1 @@ -24,7 +25,7 @@ enum class Aidon_List1 }; -enum class Aidon_List2 +enum class Aidon_List1Phase { ListSize, IGN_0, @@ -67,88 +68,14 @@ enum class Aidon_List2 CurrentL1Int8, CurrentL1Enum, IGN_13, - CurrentL2_OBIS, - CurrentL2, - IGN_14, - CurrentL2Int8, - CurrentL2Enum, - IGN_15, - CurrentL3_OBIS, - CurrentL3, - IGN_16, - CurrentL3Int8, - CurrentL3Enum, - IGN_17, VoltageL1_OBIS, VoltageL1, - IGN_18, + IGN_14, VoltageL1Int8, VoltageL1Enum, - IGN_19, - VoltageL2_OBIS, - VoltageL2, - IGN_20, - VoltageL2Int8, - VoltageL2Enum, - IGN_21, - VoltageL3_OBIS, - VoltageL3, - IGN_22, - VoltageL3Int8, - VoltageL3Enum }; -enum class Aidon_List2_1p -{ - ListSize, - IGN_0, - ListVersionIdentifier_OBIS, - ListVersionIdentifier, - IGN_1, - MeterID_OBIS, - MeterID, - IGN_2, - MeterType_OBIS, - MeterType, - IGN_3, - ActiveImportPower_OBIS, - ActiveImportPower, - IGN_4, - ActiveImportPowerInt8, - ActiveImportPowerEnum, - IGN_5, - ActiveExportPower_OBIS, - ActiveExportPower, - IGN_6, - ActiveExportPowerInt8, - ActiveExportPowerEnum, - IGN_7, - ReactiveImportPower_OBIS, - ReactiveImportPower, - IGN_8, - ReactiveImportPowerInt8, - ReactiveImportPowerEnum, - IGN_9, - ReactiveExportPower_OBIS, - ReactiveExportPower, - IGN_10, - ReactiveExportPowerInt8, - ReactiveExportPowerEnum, - IGN_11, - Current_OBIS, - Current, - IGN_12, - CurrentInt8, - CurrentEnum, - IGN_13, - Voltage_OBIS, - Voltage, - IGN_14, - VoltageInt8, - VoltageEnum -}; - -enum class Aidon_List3 +enum class Aidon_List3Phase { ListSize, IGN_0, diff --git a/Arduino Code/Arduino Libraries/HanReader/src/HanReader.cpp b/Arduino Code/Arduino Libraries/HanReader/src/HanReader.cpp index 623a9bcf..6012a432 100644 --- a/Arduino Code/Arduino Libraries/HanReader/src/HanReader.cpp +++ b/Arduino Code/Arduino Libraries/HanReader/src/HanReader.cpp @@ -5,15 +5,8 @@ HanReader::HanReader() } -void HanReader::setup(HardwareSerial *hanPort, unsigned long baudrate, SerialConfig config, Stream *debugPort) +void HanReader::setup(HardwareSerial *hanPort, Stream *debugPort) { - // Initialize H/W serial port for MBus communication - if (hanPort != NULL) - { - hanPort->begin(baudrate, config); - while (!hanPort) {} - } - han = hanPort; bytesRead = 0; debug = debugPort; @@ -22,12 +15,7 @@ void HanReader::setup(HardwareSerial *hanPort, unsigned long baudrate, SerialCon void HanReader::setup(HardwareSerial *hanPort) { - setup(hanPort, 2400, SERIAL_8E1, NULL); -} - -void HanReader::setup(HardwareSerial *hanPort, Stream *debugPort) -{ - setup(hanPort, 2400, SERIAL_8E1, debugPort); + setup(hanPort, NULL); } bool HanReader::read(byte data) @@ -63,10 +51,13 @@ bool HanReader::read(byte data) return false; } - if (debug) debug->println("HAN data is valid"); listSize = getInt(0, buffer, 0, bytesRead); + if (debug) debug->print("HAN data is valid, listSize: "); + if (debug) debug->println(listSize); return true; } + + return false; } void HanReader::debugPrint(byte *buffer, int start, int length) @@ -81,7 +72,7 @@ void HanReader::debugPrint(byte *buffer, int start, int length) debug->println(""); else if ((i - start + 1) % 4 == 0) debug->print(" "); - + yield(); // Let other get some resources too } debug->println(""); @@ -294,4 +285,4 @@ time_t HanReader::toUnixTime(int year, int month, int day, int hour, int minute, time += second; return (time_t)time; -} \ No newline at end of file +} diff --git a/Arduino Code/Arduino Libraries/HanReader/src/HanReader.h b/Arduino Code/Arduino Libraries/HanReader/src/HanReader.h index 3ef0cf5b..95753b9b 100644 --- a/Arduino Code/Arduino Libraries/HanReader/src/HanReader.h +++ b/Arduino Code/Arduino Libraries/HanReader/src/HanReader.h @@ -20,7 +20,6 @@ public: HanReader(); void setup(HardwareSerial *hanPort); void setup(HardwareSerial *hanPort, Stream *debugPort); - void setup(HardwareSerial *hanPort, unsigned long baudrate, SerialConfig config, Stream *debugPort); bool read(); bool read(byte data); int getListSize(); diff --git a/Arduino Code/Arduino Libraries/HanReader/src/Kaifa.h b/Arduino Code/Arduino Libraries/HanReader/src/Kaifa.h index 517df221..b6e48d34 100644 --- a/Arduino Code/Arduino Libraries/HanReader/src/Kaifa.h +++ b/Arduino Code/Arduino Libraries/HanReader/src/Kaifa.h @@ -2,9 +2,11 @@ #define _KAIFA_h enum class Kaifa : byte { - List1 = 0x01, - List2 = 0x0D, - List3 = 0x12 + List1 = 0x01, + List1PhaseShort = 0x09, + List3PhaseShort = 0x0D, + List1PhaseLong = 0x0E, + List3PhaseLong = 0x12 }; enum class Kaifa_List1 { @@ -12,24 +14,7 @@ enum class Kaifa_List1 { ActivePowerImported }; -enum class Kaifa_List2 { - ListSize, - ListVersionIdentifier, - MeterID, - MeterType, - ActiveImportPower, - ActiveExportPower, - ReactiveImportPower, - ReactiveExportPower, - CurrentL1, - CurrentL2, - CurrentL3, - VoltageL1, - VoltageL2, - VoltageL3 -}; - -enum class Kaifa_List3 { +enum class Kaifa_List3Phase { ListSize, ListVersionIdentifier, MeterID, @@ -51,4 +36,22 @@ enum class Kaifa_List3 { CumulativeReactiveExportEnergy }; +enum class Kaifa_List1Phase { + ListSize, + ListVersionIdentifier, + MeterID, + MeterType, + ActiveImportPower, + ActiveExportPower, + ReactiveImportPower, + ReactiveExportPower, + CurrentL1, + VoltageL1, + MeterClock, + CumulativeActiveImportEnergy, + CumulativeActiveExportEnergy, + CumulativeReactiveImportEnergy, + CumulativeReactiveExportEnergy +}; + #endif diff --git a/Arduino Code/Arduino Libraries/HanReader/src/Kamstrup.h b/Arduino Code/Arduino Libraries/HanReader/src/Kamstrup.h index 609628d5..71513916 100644 --- a/Arduino Code/Arduino Libraries/HanReader/src/Kamstrup.h +++ b/Arduino Code/Arduino Libraries/HanReader/src/Kamstrup.h @@ -6,42 +6,13 @@ enum class Kamstrup { - List1 = 0x19, - List2 = 0x23 + List3PhaseShort = 0x19, + List3PhaseLong = 0x23, + List1PhaseShort = 0x11, + List1PhaseLong = 0x1B }; -enum class Kamstrup_List1 -{ - ListSize, - ListVersionIdentifier, - MeterID_OBIS, - MeterID, - MeterType_OBIS, - MeterType, - ActiveImportPower_OBIS, - ActiveImportPower, - ActiveExportPower_OBIS, - ActiveExportPower, - ReactiveImportPower_OBIS, - ReactiveImportPower, - ReactiveExportPower_OBIS, - ReactiveExportPower, - CurrentL1_OBIS, - CurrentL1, - CurrentL2_OBIS, - CurrentL2, - CurrentL3_OBIS, - CurrentL3, - VoltageL1_OBIS, - VoltageL1, - VoltageL2_OBIS, - VoltageL2, - VoltageL3_OBIS, - VoltageL3 -}; - - -enum class Kamstrup_List2 +enum class Kamstrup_List3Phase { ListSize, ListVersionIdentifier, @@ -81,6 +52,37 @@ enum class Kamstrup_List2 CumulativeReactiveExportEnergy }; +enum class Kamstrup_List1Phase +{ + ListSize, + ListVersionIdentifier, + MeterID_OBIS, + MeterID, + MeterType_OBIS, + MeterType, + ActiveImportPower_OBIS, + ActiveImportPower, + ActiveExportPower_OBIS, + ActiveExportPower, + ReactiveImportPower_OBIS, + ReactiveImportPower, + ReactiveExportPower_OBIS, + ReactiveExportPower, + CurrentL1_OBIS, + CurrentL1, + VoltageL1_OBIS, + VoltageL1, + MeterClock_OBIS, + MeterClock, + CumulativeActiveImportEnergy_OBIS, + CumulativeActiveImportEnergy, + CumulativeActiveExportEnergy_OBIS, + CumulativeActiveExportEnergy, + CumulativeReactiveImportEnergy_OBIS, + CumulativeReactiveImportEnergy, + CumulativeReactiveExportEnergy_OBIS, + CumulativeReactiveExportEnergy +}; #endif diff --git a/Arduino Code/Arduino Libraries/HanToJson/library.properties b/Arduino Code/Arduino Libraries/HanToJson/library.properties new file mode 100644 index 00000000..204ea958 --- /dev/null +++ b/Arduino Code/Arduino Libraries/HanToJson/library.properties @@ -0,0 +1,9 @@ +name=HanToJson +version=1.0.0 +author=roarfred +maintainer=roarfred +sentence=HAN reader data to Json +paragraph=HAN reader data to Json +category=Sensors +url=https://github.com/roarfred/AmsToMqttBridge +architectures=* diff --git a/Arduino Code/Arduino Libraries/HanToJson/src/HanToJson.cpp b/Arduino Code/Arduino Libraries/HanToJson/src/HanToJson.cpp new file mode 100644 index 00000000..f5369609 --- /dev/null +++ b/Arduino Code/Arduino Libraries/HanToJson/src/HanToJson.cpp @@ -0,0 +1,240 @@ +#include "HanToJson.h" +#include "Aidon.h" +#include "Kaifa.h" +#include "Kamstrup.h" + + +static void hanToJsonKaifa3phase(int listSize, JsonObject& data, HanReader& hanReader, Stream *debugger) +{ + if (listSize >= (int)Kaifa::List3PhaseShort) + { + data["lv"] = hanReader.getString( (int)Kaifa_List3Phase::ListVersionIdentifier); + data["id"] = hanReader.getString( (int)Kaifa_List3Phase::MeterID); + data["type"] = hanReader.getString( (int)Kaifa_List3Phase::MeterType); + data["P"] = hanReader.getInt( (int)Kaifa_List3Phase::ActiveImportPower); + data["Q"] = hanReader.getInt( (int)Kaifa_List3Phase::ReactiveImportPower); + data["I1"] = hanReader.getInt( (int)Kaifa_List3Phase::CurrentL1); + data["I2"] = hanReader.getInt( (int)Kaifa_List3Phase::CurrentL2); + data["I3"] = hanReader.getInt( (int)Kaifa_List3Phase::CurrentL3); + data["U1"] = hanReader.getInt( (int)Kaifa_List3Phase::VoltageL1); + data["U2"] = hanReader.getInt( (int)Kaifa_List3Phase::VoltageL2); + data["U3"] = hanReader.getInt( (int)Kaifa_List3Phase::VoltageL3); + } + + if (listSize >= (int)Kaifa::List3PhaseLong) + { + data["tPI"] = hanReader.getInt( (int)Kaifa_List3Phase::CumulativeActiveImportEnergy); + data["tPO"] = hanReader.getInt( (int)Kaifa_List3Phase::CumulativeActiveExportEnergy); + data["tQI"] = hanReader.getInt( (int)Kaifa_List3Phase::CumulativeReactiveImportEnergy); + data["tQO"] = hanReader.getInt( (int)Kaifa_List3Phase::CumulativeReactiveExportEnergy); + } +} + +static void hanToJsonKaifa1phase(int listSize, JsonObject& data, HanReader& hanReader, Stream *debugger) +{ + if (listSize >= (int)Kaifa::List1PhaseShort) + { + data["lv"] = hanReader.getString( (int)Kaifa_List1Phase::ListVersionIdentifier); + data["id"] = hanReader.getString( (int)Kaifa_List1Phase::MeterID); + data["type"] = hanReader.getString( (int)Kaifa_List1Phase::MeterType); + data["P"] = hanReader.getInt( (int)Kaifa_List1Phase::ActiveImportPower); + data["Q"] = hanReader.getInt( (int)Kaifa_List1Phase::ReactiveImportPower); + data["I1"] = hanReader.getInt( (int)Kaifa_List1Phase::CurrentL1); + data["U1"] = hanReader.getInt( (int)Kaifa_List1Phase::VoltageL1); + } + + if (listSize >= (int)Kaifa::List1PhaseLong) + { + data["tPI"] = hanReader.getInt( (int)Kaifa_List1Phase::CumulativeActiveImportEnergy); + data["tPO"] = hanReader.getInt( (int)Kaifa_List1Phase::CumulativeActiveExportEnergy); + data["tQI"] = hanReader.getInt( (int)Kaifa_List1Phase::CumulativeReactiveImportEnergy); + data["tQO"] = hanReader.getInt( (int)Kaifa_List1Phase::CumulativeReactiveExportEnergy); + } +} + +static void hanToJsonKaifa(JsonObject& data, HanReader& hanReader, Stream *debugger) +{ + int listSize = hanReader.getListSize(); + + if (listSize == (int)Kaifa::List1) + { + // Handle listSize == 1 specially + data["P"] = hanReader.getInt( (int)Kaifa_List1::ActivePowerImported); + return; + } + + switch (listSize) { + case (int)Kaifa::List3PhaseShort: + case (int)Kaifa::List3PhaseLong: + return hanToJsonKaifa3phase(listSize, data, hanReader, debugger); + case (int)Kaifa::List1PhaseShort: + case (int)Kaifa::List1PhaseLong: + return hanToJsonKaifa1phase(listSize, data, hanReader, debugger); + default: + if (debugger) debugger->printf("Warning: Unknown listSize %d\n", listSize); + return; + } +} + + +static void hanToJsonAidon3phase(int listSize, JsonObject& data, HanReader& hanReader, Stream *debugger) +{ + if (listSize >= (int)Aidon::List3PhaseShort) + { + data["lv"] = hanReader.getString( (int)Aidon_List3Phase::ListVersionIdentifier); + data["id"] = hanReader.getString( (int)Aidon_List3Phase::MeterID); + data["type"] = hanReader.getString( (int)Aidon_List3Phase::MeterType); + data["P"] = hanReader.getInt( (int)Aidon_List3Phase::ActiveImportPower); + data["Q"] = hanReader.getInt( (int)Aidon_List3Phase::ReactiveExportPower); + data["I1"] = ((double) hanReader.getInt( (int)Aidon_List3Phase::CurrentL1)) / 10; + data["I2"] = ((double) hanReader.getInt( (int)Aidon_List3Phase::CurrentL2)) / 10; + data["I3"] = ((double) hanReader.getInt( (int)Aidon_List3Phase::CurrentL3)) / 10; + data["U1"] = ((double) hanReader.getInt( (int)Aidon_List3Phase::VoltageL1)) / 10; + data["U2"] = ((double) hanReader.getInt( (int)Aidon_List3Phase::VoltageL2)) / 10; + data["U3"] = ((double) hanReader.getInt( (int)Aidon_List3Phase::VoltageL3)) / 10; + } + + if (listSize >= (int)Aidon::List3PhaseLong) + { + data["tPI"] = hanReader.getInt( (int)Aidon_List3Phase::CumulativeActiveImportEnergy); + data["tPO"] = hanReader.getInt( (int)Aidon_List3Phase::CumulativeActiveExportEnergy); + data["tQI"] = hanReader.getInt( (int)Aidon_List3Phase::CumulativeReactiveImportEnergy); + data["tQO"] = hanReader.getInt( (int)Aidon_List3Phase::CumulativeReactiveExportEnergy); + } + + // TODO: Do not divide Aidon values by 10!? +} + +static void hanToJsonAidon1phase(int listSize, JsonObject& data, HanReader& hanReader, Stream *debugger) +{ + if (listSize >= (int)Aidon::List1PhaseShort) + { + data["lv"] = hanReader.getString( (int)Aidon_List1Phase::ListVersionIdentifier); + data["id"] = hanReader.getString( (int)Aidon_List1Phase::MeterID); + data["type"] = hanReader.getString( (int)Aidon_List1Phase::MeterType); + data["P"] = hanReader.getInt( (int)Aidon_List1Phase::ActiveImportPower); + data["Q"] = hanReader.getInt( (int)Aidon_List1Phase::ReactiveExportPower); + data["I1"] = ((double) hanReader.getInt( (int)Aidon_List1Phase::CurrentL1)) / 10; + data["U1"] = ((double) hanReader.getInt( (int)Aidon_List1Phase::VoltageL1)) / 10; + } + + // TODO Aidon::List1PhaseLong +} + +static void hanToJsonAidon(JsonObject& data, HanReader& hanReader, Stream *debugger) +{ + int listSize = hanReader.getListSize(); + + // Based on the list number, get all details + // according to OBIS specifications for the meter + if (listSize == (int)Aidon::List1) + { + // Handle listSize == 1 specially + data["P"] = hanReader.getInt((int)Aidon_List1::ActiveImportPower); + return; + } + + switch (listSize) { + case (int)Aidon::List3PhaseShort: + case (int)Aidon::List3PhaseLong: + return hanToJsonAidon3phase(listSize, data, hanReader, debugger); + case (int)Aidon::List1PhaseShort: + case (int)Aidon::List1PhaseLong: + return hanToJsonAidon1phase(listSize, data, hanReader, debugger); + default: + if (debugger) debugger->printf("Warning: Unknown listSize %d\n", listSize); + return; + } +} + +static void hanToJsonKamstrup3phase(int listSize, JsonObject& data, HanReader& hanReader, Stream *debugger) +{ + if (listSize >= (int)Kamstrup::List3PhaseShort) + { + data["lv"] = hanReader.getString( (int)Kamstrup_List3Phase::ListVersionIdentifier); + data["id"] = hanReader.getString( (int)Kamstrup_List3Phase::MeterID); + data["type"] = hanReader.getString( (int)Kamstrup_List3Phase::MeterType); + data["P"] = hanReader.getInt( (int)Kamstrup_List3Phase::ActiveImportPower); + data["Q"] = hanReader.getInt( (int)Kamstrup_List3Phase::ReactiveImportPower); + data["I1"] = hanReader.getInt( (int)Kamstrup_List3Phase::CurrentL1); + data["I2"] = hanReader.getInt( (int)Kamstrup_List3Phase::CurrentL2); + data["I3"] = hanReader.getInt( (int)Kamstrup_List3Phase::CurrentL3); + data["U1"] = hanReader.getInt( (int)Kamstrup_List3Phase::VoltageL1); + data["U2"] = hanReader.getInt( (int)Kamstrup_List3Phase::VoltageL2); + data["U3"] = hanReader.getInt( (int)Kamstrup_List3Phase::VoltageL3); + } + + if (listSize >= (int)Kamstrup::List3PhaseLong) + { + data["tPI"] = hanReader.getInt( (int)Kamstrup_List3Phase::CumulativeActiveImportEnergy); + data["tPO"] = hanReader.getInt( (int)Kamstrup_List3Phase::CumulativeActiveExportEnergy); + data["tQI"] = hanReader.getInt( (int)Kamstrup_List3Phase::CumulativeReactiveImportEnergy); + data["tQO"] = hanReader.getInt( (int)Kamstrup_List3Phase::CumulativeReactiveExportEnergy); + } +} + +static void hanToJsonKamstrup1phase(int listSize, JsonObject& data, HanReader& hanReader, Stream *debugger) +{ + if (listSize >= (int)Kamstrup::List1PhaseShort) + { + data["lv"] = hanReader.getString( (int)Kamstrup_List1Phase::ListVersionIdentifier); + data["id"] = hanReader.getString( (int)Kamstrup_List1Phase::MeterID); + data["type"] = hanReader.getString( (int)Kamstrup_List1Phase::MeterType); + data["P"] = hanReader.getInt( (int)Kamstrup_List1Phase::ActiveImportPower); + data["Q"] = hanReader.getInt( (int)Kamstrup_List1Phase::ReactiveImportPower); + data["I1"] = hanReader.getInt( (int)Kamstrup_List1Phase::CurrentL1); + data["U1"] = hanReader.getInt( (int)Kamstrup_List1Phase::VoltageL1); + } + + if (listSize >= (int)Kamstrup::List1PhaseLong) + { + data["tPI"] = hanReader.getInt( (int)Kamstrup_List1Phase::CumulativeActiveImportEnergy); + data["tPO"] = hanReader.getInt( (int)Kamstrup_List1Phase::CumulativeActiveExportEnergy); + data["tQI"] = hanReader.getInt( (int)Kamstrup_List1Phase::CumulativeReactiveImportEnergy); + data["tQO"] = hanReader.getInt( (int)Kamstrup_List1Phase::CumulativeReactiveExportEnergy); + } +} + +static void hanToJsonKamstrup(JsonObject& data, HanReader& hanReader, Stream *debugger) +{ + int listSize = hanReader.getListSize(); + + switch (listSize) { + case (int)Kamstrup::List3PhaseShort: + case (int)Kamstrup::List3PhaseLong: + return hanToJsonKamstrup3phase(listSize, data, hanReader, debugger); + case (int)Kamstrup::List1PhaseShort: + case (int)Kamstrup::List1PhaseLong: + return hanToJsonKamstrup1phase(listSize, data, hanReader, debugger); + default: + if (debugger) debugger->printf("Warning: Unknown listSize %d\n", listSize); + return; + } +} + +void hanToJson(JsonObject& data, byte meterType, HanReader& hanReader, Stream *debugger) +{ + // Based on the list number, get all details + // according to OBIS specifications for the meter + switch (meterType) + { + case 1: // Kaifa + return hanToJsonKaifa(data, hanReader, debugger); + case 2: // Aidon + return hanToJsonAidon(data, hanReader, debugger); + case 3: // Kamstrup + return hanToJsonKamstrup(data, hanReader, debugger); + default: + if (debugger) { + debugger->print("Meter type "); + debugger->print(meterType, HEX); + debugger->println(" is unknown"); + } + break; + } +} + +void hanToJson(JsonObject& data, byte meterType, HanReader& hanReader) +{ + return hanToJson(data, meterType, hanReader, NULL); +} diff --git a/Arduino Code/Arduino Libraries/HanToJson/src/HanToJson.h b/Arduino Code/Arduino Libraries/HanToJson/src/HanToJson.h new file mode 100644 index 00000000..a06f4caf --- /dev/null +++ b/Arduino Code/Arduino Libraries/HanToJson/src/HanToJson.h @@ -0,0 +1,17 @@ +#ifndef _HANTOJSON_h +#define _HANTOJSON_h + +#if defined(ARDUINO) && ARDUINO >= 100 + #include "Arduino.h" +#else + #include "WProgram.h" +#endif + +#include +#include "HanReader.h" + +void hanToJson(JsonObject& data, byte meterType, HanReader& hanReader); +void hanToJson(JsonObject& root, byte meterType, HanReader& hanReader, Stream *debugPort); + + +#endif diff --git a/Arduino Code/README.md b/Arduino Code/README.md new file mode 100644 index 00000000..fb05a76f --- /dev/null +++ b/Arduino Code/README.md @@ -0,0 +1,27 @@ +## Arduino Code + +FW and libraries for running on various HW + +### AmsToMqttBridge + +The original FW by roarfred, runs a Wifi access point until configured. Uploads data as json to a MQTT server. + +### WifiFeatherRestBridge + +_Note: This project is still under development._ + +Firmware for runninw AMS decoding with off-the-shelf components, so no PCB etching or soldering necessary. Specifially, +the sketch runs on the [Adafruit Feather M0 WiFi w/ATWINC1500](https://www.adafruit.com/product/3010) card and uses the +[TSS721 M-BUS module board](https://www.aliexpress.com/item/TSS721/32751482255.html) available from Aliexpress. + +![FeatherMbus](/Debugging/Documentation/feather_3010-00_mbus_slave.jpg) + + +### Arduino Libraries/HanReader + +The shared library to read from a serial port and decode the data into packets. + +### Arduino Libraries/HanToJson + +Shared library to create a json structure from the read HAN packets (for the different meter types). + diff --git a/Debugging/Code/Arduino/HanToJsonTest/HanToJsonTest.ino b/Debugging/Code/Arduino/HanToJsonTest/HanToJsonTest.ino new file mode 100644 index 00000000..9499b530 --- /dev/null +++ b/Debugging/Code/Arduino/HanToJsonTest/HanToJsonTest.ino @@ -0,0 +1,283 @@ +/** + * Simple sketch to simulate reading data from different meters. + * + * Created 24. October 2017 by Roar Fredriksen + */ + +#include +//#include "Kaifa.h" +//#include "Kamstrup.h" +//#include "Aidon.h" +#include "HanReader.h" +#include "HanToJson.h" + +// The HAN Port reader +HanReader hanReader; + +HardwareSerial* debugger = NULL; + +byte meterType = 1; // Start with Kaifa +int sampleIndex = 0; +int expectIndex = 0; +byte kaifa_samples[] = +{ + // List #1 + 0x7E, 0xA0, 0x27, 0x01, 0x02, 0x01, 0x10, 0x5A, 0x87, 0xE6, 0xE7, 0x00, 0x0F, 0x40, 0x00, 0x00, + 0x00, 0x09, 0x0C, 0x07, 0xE1, 0x09, 0x0E, 0x04, 0x13, 0x1F, 0x02, 0xFF, 0x80, 0x00, 0x00, 0x02, + 0x01, 0x06, 0x00, 0x00, 0x03, 0x98, 0xAB, 0xAD, 0x7E, + + // List#2 + 0x7E, 0xA0, 0x79, 0x01, 0x02, 0x01, 0x10, 0x80, 0x93, 0xE6, 0xE7, 0x00, 0x0F, 0x40, 0x00, 0x00, + 0x00, 0x09, 0x0C, 0x07, 0xE1, 0x09, 0x0E, 0x04, 0x13, 0x1F, 0x0A, 0xFF, 0x80, 0x00, 0x00, 0x02, + 0x0D, 0x09, 0x07, 0x4B, 0x46, 0x4D, 0x5F, 0x30, 0x30, 0x31, 0x09, 0x10, 0x36, 0x39, 0x37, 0x30, + 0x36, 0x33, 0x31, 0x34, 0x30, 0x31, 0x37, 0x35, 0x33, 0x39, 0x38, 0x35, 0x09, 0x08, 0x4D, 0x41, + 0x33, 0x30, 0x34, 0x48, 0x33, 0x45, 0x06, 0x00, 0x00, 0x03, 0x96, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x06, 0x00, 0x00, 0x05, 0x64, 0x06, + 0x00, 0x00, 0x0C, 0x92, 0x06, 0x00, 0x00, 0x0C, 0x49, 0x06, 0x00, 0x00, 0x09, 0x46, 0x06, 0x00, + 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x09, 0x4E, 0x1F, 0x85, 0x7E, + + // List#3 + 0x7E, 0xA0, 0x9B, 0x01, 0x02, 0x01, 0x10, 0xEE, 0xAE, 0xE6, 0xE7, 0x00, 0x0F, 0x40, 0x00, 0x00, + 0x00, 0x09, 0x0C, 0x07, 0xE1, 0x09, 0x0E, 0x04, 0x14, 0x00, 0x0A, 0xFF, 0x80, 0x00, 0x00, 0x02, + 0x12, 0x09, 0x07, 0x4B, 0x46, 0x4D, 0x5F, 0x30, 0x30, 0x31, 0x09, 0x10, 0x36, 0x39, 0x37, 0x30, + 0x36, 0x33, 0x31, 0x34, 0x30, 0x31, 0x37, 0x35, 0x33, 0x39, 0x38, 0x35, 0x09, 0x08, 0x4D, 0x41, + 0x33, 0x30, 0x34, 0x48, 0x33, 0x45, 0x06, 0x00, 0x00, 0x03, 0xFE, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x40, 0x06, 0x00, 0x00, 0x07, 0x91, 0x06, + 0x00, 0x00, 0x0C, 0x9D, 0x06, 0x00, 0x00, 0x0D, 0x66, 0x06, 0x00, 0x00, 0x09, 0x41, 0x06, 0x00, + 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x09, 0x4C, 0x09, 0x0C, 0x07, 0xE1, 0x09, 0x0E, 0x04, 0x14, + 0x00, 0x0A, 0xFF, 0x80, 0x00, 0x00, 0x06, 0x00, 0x02, 0xBF, 0x69, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0xF7, 0x06, 0x00, 0x00, 0x3F, 0xFC, 0x71, 0x71, 0x7E, + + // List#2.1 + 0x7e, 0xa0, 0x65, 0x01, 0x02, 0x01, 0x10, 0xf0, 0x50, 0xe6, 0xe7, 0x00, 0x0f, 0x40, 0x00, 0x00, + 0x00, 0x09, 0x0c, 0x07, 0xe3, 0x05, 0x08, 0x03, 0x16, 0x20, 0x28, 0xff, 0x80, 0x00, 0x00, 0x02, + 0x09, 0x09, 0x07, 0x4b, 0x46, 0x4d, 0x5f, 0x30, 0x30, 0x31, 0x09, 0x10, 0x36, 0x39, 0x37, 0x30, + 0x36, 0x33, 0x31, 0x34, 0x30, 0x31, 0x39, 0x39, 0x31, 0x36, 0x38, 0x34, 0x09, 0x08, 0x4d, 0x41, + 0x31, 0x30, 0x35, 0x48, 0x32, 0x45, 0x06, 0x00, 0x00, 0x03, 0xa5, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x21, 0x06, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x10, 0x0e, 0x06, + 0x00, 0x00, 0x09, 0x0a, 0x98, 0x76, 0x7e, +}; +String kaifa_expect[] = { + String("{\"t\":1505417462,\"data\":{\"P\":920}}"), + String("{\"t\":1505417470,\"data\":{\"lv\":\"KFM_001\",\"id\":\"6970631401753985\",\"type\":\"MA304H3E\",\"P\":918,\"Q\":0,\"I1\":1380,\"I2\":3218,\"I3\":3145,\"U1\":2374,\"U2\":0,\"U3\":2382}}"), + String("{\"t\":1505419210,\"data\":{\"lv\":\"KFM_001\",\"id\":\"6970631401753985\",\"type\":\"MA304H3E\",\"P\":1022,\"Q\":0,\"I1\":1937,\"I2\":3229,\"I3\":3430,\"U1\":2369,\"U2\":0,\"U3\":2380,\"tPI\":180073,\"tPO\":0,\"tQI\":247,\"tQO\":16380}}"), + String("{\"t\":1557354760,\"data\":{\"lv\":\"KFM_001\",\"id\":\"6970631401991684\",\"type\":\"MA105H2E\",\"P\":933,\"Q\":33,\"I1\":4110,\"U1\":2314}}") +}; + +byte aidon_samples[] = +{ // From Aidon-HAN-Interface-Description-v10A-ID-34331.pdf + // List 2 sending (1-phase) + 0x7e, 0xa0, 0xd2, 0x41, 0x08, 0x83, 0x13, 0x82, 0xd6, 0xe6, 0xe7, 0x00, 0x0f, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x09, 0x02, 0x02, 0x09, 0x06, 0x01, 0x01, 0x00, 0x02, 0x81, 0xff, 0x0a, 0x0b, + 0x41, 0x49, 0x44, 0x4f, 0x4e, 0x5f, 0x56, 0x30, 0x30, 0x30, 0x31, 0x02, 0x02, 0x09, 0x06, 0x00, + 0x00, 0x60, 0x01, 0x00, 0xff, 0x0a, 0x10, 0x37, 0x33, 0x35, 0x39, 0x39, 0x39, 0x32, 0x38, 0x39, + 0x30, 0x39, 0x34, 0x31, 0x37, 0x34, 0x32, 0x02, 0x02, 0x09, 0x06, 0x00, 0x00, 0x60, 0x01, 0x07, + 0xff, 0x0a, 0x04, 0x36, 0x35, 0x31, 0x35, 0x02, 0x03, 0x09, 0x06, 0x01, 0x00, 0x01, 0x07, 0x00, + 0xff, 0x06, 0x00, 0x00, 0x05, 0x52, 0x02, 0x02, 0x0f, 0x00, 0x16, 0x1b, 0x02, 0x03, 0x09, 0x06, + 0x01, 0x00, 0x02, 0x07, 0x00, 0xff, 0x06, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x0f, 0x00, 0x16, + 0x1b, 0x02, 0x03, 0x09, 0x06, 0x01, 0x00, 0x03, 0x07, 0x00, 0xff, 0x06, 0x00, 0x00, 0x03, 0xe4, + 0x02, 0x02, 0x0f, 0x00, 0x16, 0x1d, 0x02, 0x03, 0x09, 0x06, 0x01, 0x00, 0x04, 0x07, 0x00, 0xff, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x0f, 0x00, 0x16, 0x1d, 0x02, 0x03, 0x09, 0x06, 0x01, + 0x00, 0x1f, 0x07, 0x00, 0xff, 0x10, 0x00, 0x5d, 0x02, 0x02, 0x0f, 0xff, 0x16, 0x21, 0x02, 0x03, + 0x09, 0x06, 0x01, 0x00, 0x20, 0x07, 0x00, 0xff, 0x12, 0x09, 0xc4, 0x02, 0x02, 0x0f, 0xff, 0x16, + 0x23, 0xe0, 0xc4, 0x7e +}; +String aidon_expect[] = { + String("{\"t\":0,\"data\":{\"lv\":\"AIDON_V0001\",\"id\":\"7359992890941742\",\"type\":\"6515\",\"P\":1362,\"Q\":0,\"I1\":9.3,\"U1\":250}}") +}; + +byte kamstrup_samples[] = +{ // [2017-10-20 04.43.32.368 - Received 229 (0xE5) bytes] + // List #1 + 0x7E, 0xA0, 0xE2, 0x2B, 0x21, 0x13, 0x23, 0x9A, 0xE6, 0xE7, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, + 0x0C, 0x07, 0xE2, 0x03, 0x04, 0x07, 0x14, 0x3B, 0x32, 0xFF, 0x80, 0x00, 0x00, 0x02, 0x19, 0x0A, + 0x0E, 0x4B, 0x61, 0x6D, 0x73, 0x74, 0x72, 0x75, 0x70, 0x5F, 0x56, 0x30, 0x30, 0x30, 0x31, 0x09, + 0x06, 0x01, 0x01, 0x00, 0x00, 0x05, 0xFF, 0x0A, 0x10, 0x35, 0x37, 0x30, 0x36, 0x35, 0x36, 0x37, + 0x32, 0x37, 0x34, 0x33, 0x38, 0x39, 0x37, 0x30, 0x32, 0x09, 0x06, 0x01, 0x01, 0x60, 0x01, 0x01, + 0xFF, 0x0A, 0x12, 0x36, 0x38, 0x34, 0x31, 0x31, 0x32, 0x31, 0x42, 0x4E, 0x32, 0x34, 0x33, 0x31, + 0x30, 0x31, 0x30, 0x34, 0x30, 0x09, 0x06, 0x01, 0x01, 0x01, 0x07, 0x00, 0xFF, 0x06, 0x00, 0x00, + 0x11, 0x28, 0x09, 0x06, 0x01, 0x01, 0x02, 0x07, 0x00, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x09, + 0x06, 0x01, 0x01, 0x03, 0x07, 0x00, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x84, 0x09, 0x06, 0x01, 0x01, + 0x04, 0x07, 0x00, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x09, 0x06, 0x01, 0x01, 0x1F, 0x07, 0x00, + 0xFF, 0x06, 0x00, 0x00, 0x05, 0x66, 0x09, 0x06, 0x01, 0x01, 0x33, 0x07, 0x00, 0xFF, 0x06, 0x00, + 0x00, 0x03, 0x0C, 0x09, 0x06, 0x01, 0x01, 0x47, 0x07, 0x00, 0xFF, 0x06, 0x00, 0x00, 0x05, 0x5A, + 0x09, 0x06, 0x01, 0x01, 0x20, 0x07, 0x00, 0xFF, 0x12, 0x00, 0xE0, 0x09, 0x06, 0x01, 0x01, 0x34, + 0x07, 0x00, 0xFF, 0x12, 0x00, 0xDF, 0x09, 0x06, 0x01, 0x01, 0x48, 0x07, 0x00, 0xFF, 0x12, 0x00, + 0xDF, 0xA1, 0xD8, 0x7E, + + // List #2 + 0x7E, 0xA1, 0x2C, 0x2B, 0x21, 0x13, 0xFC, 0x04, 0xE6, 0xE7, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, + 0x0C, 0x07, 0xE2, 0x03, 0x04, 0x07, 0x15, 0x00, 0x05, 0xFF, 0x80, 0x00, 0x00, 0x02, 0x23, 0x0A, + 0x0E, 0x4B, 0x61, 0x6D, 0x73, 0x74, 0x72, 0x75, 0x70, 0x5F, 0x56, 0x30, 0x30, 0x30, 0x31, 0x09, + 0x06, 0x01, 0x01, 0x00, 0x00, 0x05, 0xFF, 0x0A, 0x10, 0x35, 0x37, 0x30, 0x36, 0x35, 0x36, 0x37, + 0x32, 0x37, 0x34, 0x33, 0x38, 0x39, 0x37, 0x30, 0x32, 0x09, 0x06, 0x01, 0x01, 0x60, 0x01, 0x01, + 0xFF, 0x0A, 0x12, 0x36, 0x38, 0x34, 0x31, 0x31, 0x32, 0x31, 0x42, 0x4E, 0x32, 0x34, 0x33, 0x31, + 0x30, 0x31, 0x30, 0x34, 0x30, 0x09, 0x06, 0x01, 0x01, 0x01, 0x07, 0x00, 0xFF, 0x06, 0x00, 0x00, + 0x0E, 0x3B, 0x09, 0x06, 0x01, 0x01, 0x02, 0x07, 0x00, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x09, + 0x06, 0x01, 0x01, 0x03, 0x07, 0x00, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x86, 0x09, 0x06, 0x01, 0x01, + 0x04, 0x07, 0x00, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x09, 0x06, 0x01, 0x01, 0x1F, 0x07, 0x00, + 0xFF, 0x06, 0x00, 0x00, 0x04, 0x21, 0x09, 0x06, 0x01, 0x01, 0x33, 0x07, 0x00, 0xFF, 0x06, 0x00, + 0x00, 0x03, 0x0C, 0x09, 0x06, 0x01, 0x01, 0x47, 0x07, 0x00, 0xFF, 0x06, 0x00, 0x00, 0x04, 0x1C, + 0x09, 0x06, 0x01, 0x01, 0x20, 0x07, 0x00, 0xFF, 0x12, 0x00, 0xE2, 0x09, 0x06, 0x01, 0x01, 0x34, + 0x07, 0x00, 0xFF, 0x12, 0x00, 0xE0, 0x09, 0x06, 0x01, 0x01, 0x48, 0x07, 0x00, 0xFF, 0x12, 0x00, + 0xDF, 0x09, 0x06, 0x00, 0x01, 0x01, 0x00, 0x00, 0xFF, 0x09, 0x0C, 0x07, 0xE2, 0x03, 0x04, 0x07, + 0x15, 0x00, 0x05, 0xFF, 0x80, 0x00, 0x00, 0x09, 0x06, 0x01, 0x01, 0x01, 0x08, 0x00, 0xFF, 0x06, + 0x00, 0x1A, 0x40, 0x49, 0x09, 0x06, 0x01, 0x01, 0x02, 0x08, 0x00, 0xFF, 0x06, 0x00, 0x00, 0x00, + 0x00, 0x09, 0x06, 0x01, 0x01, 0x03, 0x08, 0x00, 0xFF, 0x06, 0x00, 0x00, 0x05, 0x64, 0x09, 0x06, + 0x01, 0x01, 0x04, 0x08, 0x00, 0xFF, 0x06, 0x00, 0x02, 0x7B, 0x21, 0x20, 0x92, 0x7E +}; +String kamstrup_expect[] = { + String("{\"t\":1520197190,\"data\":{\"lv\":\"Kamstrup_V0001\",\"id\":\"5706567274389702\",\"type\":\"6841121BN243101040\",\"P\":4392,\"Q\":132,\"I1\":1382,\"I2\":780,\"I3\":1370,\"U1\":224,\"U2\":223,\"U3\":223}}"), + String("{\"t\":1520197205,\"data\":{\"lv\":\"Kamstrup_V0001\",\"id\":\"5706567274389702\",\"type\":\"6841121BN243101040\",\"P\":3643,\"Q\":134,\"I1\":1057,\"I2\":780,\"I3\":1052,\"U1\":226,\"U2\":224,\"U3\":223,\"tPI\":1720393,\"tPO\":0,\"tQI\":1380,\"tQO\":162593}}") +}; + + +void assert_equal(DynamicJsonDocument& doc, const String& expected) +{ + String jsonActual; + serializeJson(doc, jsonActual); + if (jsonActual != expected) + { + debugger->printf("Test assert failed:\n %s\n !=\n %s\n", jsonActual.c_str(), expected.c_str()); + while (true) {} + } +} + + +void setup() { + Serial1.begin(115200); + //while (!Serial) {} + Serial1.println("Serial1 debugging port initialized"); + + // initialize the HanReader + // (passing no han port, as we are feeding data manually, but provide Serial for debugging) + hanReader.setup(NULL, &Serial1); + hanReader.compensateFor09HeaderBug = true; // Starting with Kaifa + + debugger = &Serial1; +} + +void loopKaifa() +{ + if (sampleIndex >= sizeof(kaifa_samples)) + { + meterType++; + sampleIndex = 0; + expectIndex = 0; + hanReader.setup(NULL, debugger); + hanReader.compensateFor09HeaderBug = false; + Serial1.println("Done with Kaifa"); + } + + // Read one byte from the "port", and see if we got a full package + if (hanReader.read(kaifa_samples[sampleIndex++])) + { + DynamicJsonDocument doc(500); + + doc["t"] = hanReader.getPackageTime(); + + JsonObject data = doc.createNestedObject("data"); + + hanToJson(data, meterType, hanReader); + + debugger->println("Kaifa JsonData: "); + serializeJsonPretty(doc, Serial1); + debugger->println(); + + if (expectIndex < sizeof(kaifa_expect) / sizeof(kaifa_expect[0])) + { + assert_equal(doc, kaifa_expect[expectIndex++]); + } else { + debugger->println("Not enough expected results spesified"); + while (true) {} + } + } +} + +void loopAidon() +{ + if (sampleIndex >= sizeof(aidon_samples)) + { + meterType++; + sampleIndex = 0; + expectIndex = 0; + hanReader.setup(NULL, &Serial1); + debugger->println("Done with Aidon"); + } + + // Read one byte from the "port", and see if we got a full package + if (hanReader.read(aidon_samples[sampleIndex++])) + { + DynamicJsonDocument doc(500); + + doc["t"] = hanReader.getPackageTime(); + + JsonObject data = doc.createNestedObject("data"); + + hanToJson(data, meterType, hanReader); + + debugger->println("Aidon JsonData: "); + serializeJsonPretty(doc, Serial1); + debugger->println(); + + if (expectIndex < sizeof(aidon_expect) / sizeof(aidon_expect[0])) + { + assert_equal(doc, aidon_expect[expectIndex++]); + } else { + debugger->println("Not enough expected results spesified"); + while (true) {} + } + } +} + +void loopKamstrup() +{ + if (sampleIndex >= sizeof(kamstrup_samples)) + { + meterType++; + sampleIndex = 0; + hanReader.setup(NULL, &Serial1); + debugger->println("Done with Kamstrup"); + } + + // Read one byte from the "port", and see if we got a full package + if (hanReader.read(kamstrup_samples[sampleIndex++])) + { + DynamicJsonDocument doc(500); + + doc["t"] = hanReader.getPackageTime(); + + JsonObject data = doc.createNestedObject("data"); + + hanToJson(data, meterType, hanReader); + + debugger->println("Kamstrup JsonData: "); + serializeJsonPretty(doc, Serial1); + debugger->println(); + + if (expectIndex < sizeof(kamstrup_expect) / sizeof(kamstrup_expect[0])) + { + assert_equal(doc, kamstrup_expect[expectIndex++]); + } else { + debugger->println("Not enough expected results spesified"); + while (true) {} + } + } +} + +void loop() { + switch (meterType) + { + case 1: // Kaifa + return loopKaifa(); + case 2: // Aidon + return loopAidon(); + case 3: // Kamstrup + return loopKamstrup(); + default: + debugger->println("Done"); + while (true) ; + break; + } +} + diff --git a/Debugging/Code/README.md b/Debugging/Code/README.md index bd074b78..4a997d38 100644 --- a/Debugging/Code/README.md +++ b/Debugging/Code/README.md @@ -18,3 +18,9 @@ Very similar to the HanDebugger, simply reading the HAN data and outputting the This code outputs a changing serial test pattern. ![](SerialTestPattern/SerialTestPattern.gif) + +### SerialSimulator (Python code to send sample HAN data over serial port) + +Use this Python3 script (e.g. running on a desktop computer using a USB to serial dongle) to simulate the data sent over the HAN port (after being translate from Mbus to uart signals). + +![](SerialSimulator/ams_serial_simulator.jpg) diff --git a/Debugging/Code/SerialSimulator/ams_serial_simulator.jpg b/Debugging/Code/SerialSimulator/ams_serial_simulator.jpg new file mode 100644 index 00000000..45a86d68 Binary files /dev/null and b/Debugging/Code/SerialSimulator/ams_serial_simulator.jpg differ diff --git a/Debugging/Code/SerialSimulator/ams_serial_simulator.py b/Debugging/Code/SerialSimulator/ams_serial_simulator.py new file mode 100644 index 00000000..214a1c5e --- /dev/null +++ b/Debugging/Code/SerialSimulator/ams_serial_simulator.py @@ -0,0 +1,19 @@ +import serial +import time +import sys + +import sample_data + +serial_port = sys.argv[1] + +with serial.Serial(port=serial_port, baudrate=2400, bytesize=8, parity='E', stopbits=1) as ser: + print(ser) + print('sleeping') + time.sleep(1) + while True: + for packet in sample_data.kaifa_20190508_packets[:]: + sys.stdout.flush() + ser.write(packet) + print('sleeping') + time.sleep(2) + break diff --git a/Debugging/Code/SerialSimulator/sample_data.py b/Debugging/Code/SerialSimulator/sample_data.py new file mode 100644 index 00000000..02f167f7 --- /dev/null +++ b/Debugging/Code/SerialSimulator/sample_data.py @@ -0,0 +1,27 @@ +# From Samples/Kaifa/HAN 20170912-3.txt: +kaifa_han_20170912_3_packets = [ + # [2017-09-13 00.00.07.776 - Received 41 (0x29) bytes] + b'\x7E\xA0\x27\x01\x02\x01\x10\x5A\x87\xE6\xE7\x00\x0F\x40\x00\x00\x00\x09\x0C\x07\xE1\x09\x0D\x02\x00\x00\x06\xFF\x80\x00\x00\x02\x01\x06\x00\x00\x11\xAC\x85\x61\x7E', + # [2017-09-13 00.00.09.784 - Received 41 (0x29) bytes] + b'\x7E\xA0\x27\x01\x02\x01\x10\x5A\x87\xE6\xE7\x00\x0F\x40\x00\x00\x00\x09\x0C\x07\xE1\x09\x0D\x02\x00\x00\x08\xFF\x80\x00\x00\x02\x01\x06\x00\x00\x11\xB4\x6D\xF0\x7E', + # [2017-09-13 00.00.11.761 - Received 157 (0x9D) bytes] + b'\x7E\xA0\x9B\x01\x02\x01\x10\xEE\xAE\xE6\xE7\x00\x0F\x40\x00\x00\x00\x09\x0C\x07\xE1\x09\x0D\x02\x00\x00\x0A\xFF\x80\x00\x00\x02\x12\x09\x07\x4B\x46\x4D\x5F\x30\x30\x31\x09\x10\x36\x39\x37\x30\x36\x33\x31\x34\x30\x31\x37\x35\x33\x39\x38\x35\x09\x08\x4D\x41\x33\x30\x34\x48\x33\x45\x06\x00\x00\x11\xB5\x06\x00\x00\x00\x00\x06\x00\x00\x00\x00\x06\x00\x00\x00\x80\x06\x00\x00\x38\x48\x06\x00\x00\x3C\x76\x06\x00\x00\x14\x5B\x06\x00\x00\x09\x4D\x06\x00\x00\x00\x00\x06\x00\x00\x09\x5A\x09\x0C\x07\xE1\x09\x0D\x02\x00\x00\x0A\xFF\x80\x00\x00\x06\x00\x01\xB8\x57\x06\x00\x00\x00\x00\x06\x00\x00\x00\x65\x06\x00\x00\x33\xA4\x54\x83\x7E', + # [2017-09-13 00.00.13.769 - Received 41 (0x29) bytes] + b'\x7E\xA0\x27\x01\x02\x01\x10\x5A\x87\xE6\xE7\x00\x0F\x40\x00\x00\x00\x09\x0C\x07\xE1\x09\x0D\x02\x00\x00\x0C\xFF\x80\x00\x00\x02\x01\x06\x00\x00\x11\xB9\xFE\x2E\x7E', + # [2017-09-13 00.00.15.778 - Received 41 (0x29) bytes] + b'\x7E\xA0\x27\x01\x02\x01\x10\x5A\x87\xE6\xE7\x00\x0F\x40\x00\x00\x00\x09\x0C\x07\xE1\x09\x0D\x02\x00\x00\x0E\xFF\x80\x00\x00\x02\x01\x06\x00\x00\x11\xBA\xDE\x1E\x7E', + # [2017-09-13 00.00.17.786 - Received 41 (0x29) bytes] + b'\x7E\xA0\x27\x01\x02\x01\x10\x5A\x87\xE6\xE7\x00\x0F\x40\x00\x00\x00\x09\x0C\x07\xE1\x09\x0D\x02\x00\x00\x10\xFF\x80\x00\x00\x02\x01\x06\x00\x00\x11\xBB\xAE\x17\x7E', + # [2017-09-13 00.00.19.762 - Received 41 (0x29) bytes] + b'\x7E\xA0\x27\x01\x02\x01\x10\x5A\x87\xE6\xE7\x00\x0F\x40\x00\x00\x00\x09\x0C\x07\xE1\x09\x0D\x02\x00\x00\x12\xFF\x80\x00\x00\x02\x01\x06\x00\x00\x11\xB7\x79\xDF\x7E', + # [2017-09-13 00.00.21.771 - Received 123 (0x7B) bytes] + b'\x7E\xA0\x79\x01\x02\x01\x10\x80\x93\xE6\xE7\x00\x0F\x40\x00\x00\x00\x09\x0C\x07\xE1\x09\x0D\x02\x00\x00\x14\xFF\x80\x00\x00\x02\x0D\x09\x07\x4B\x46\x4D\x5F\x30\x30\x31\x09\x10\x36\x39\x37\x30\x36\x33\x31\x34\x30\x31\x37\x35\x33\x39\x38\x35\x09\x08\x4D\x41\x33\x30\x34\x48\x33\x45\x06\x00\x00\x11\xBB\x06\x00\x00\x00\x00\x06\x00\x00\x00\x00\x06\x00\x00\x00\x7F\x06\x00\x00\x38\x4D\x06\x00\x00\x3C\x86\x06\x00\x00\x14\x5B\x06\x00\x00\x09\x4F\x06\x00\x00\x00\x00\x06\x00\x00\x09\x5C\xC1\x54\x7E', +] + +# From Kaifa MA105H2E 2019-05-08 +kaifa_20190508_packets = [ + b'~\xa0\'\x01\x02\x01\x10Z\x87\xe6\xe7\x00\x0f@\x00\x00\x00\t\x0c\x07\xe3\x05\x08\x03\x16 "\xff\x80\x00\x00\x02\x01\x06\x00\x00\x03\xa3L\xab~', + b'~\xa0e\x01\x02\x01\x10\xf0P\xe6\xe7\x00\x0f@\x00\x00\x00\t\x0c\x07\xe3\x05\x08\x03\x16 (\xff\x80\x00\x00\x02\t\t\x07KFM_001\t\x106970631401991684\t\x08MA105H2E\x06\x00\x00\x03\xa5\x06\x00\x00\x00\x00\x06\x00\x00\x00!\x06\x00\x00\x00\x00\x06\x00\x00\x10\x0e\x06\x00\x00\t\n\x98v~', + b'~\xa0\'\x01\x02\x01\x10Z\x87\xe6\xe7\x00\x0f@\x00\x00\x00\t\x0c\x07\xe3\x05\x08\x03\x16 "\xff\x80\x00\x00\x02\x01\x06\x00\x00\x03\xa3L\xab~', +] + diff --git a/Debugging/Documentation/Aidon-HAN-Interface-Description-v10A-ID-34331.pdf b/Debugging/Documentation/Aidon-HAN-Interface-Description-v10A-ID-34331.pdf new file mode 100644 index 00000000..c579c142 Binary files /dev/null and b/Debugging/Documentation/Aidon-HAN-Interface-Description-v10A-ID-34331.pdf differ diff --git a/Debugging/Documentation/feather_3010-00_mbus_slave.jpg b/Debugging/Documentation/feather_3010-00_mbus_slave.jpg new file mode 100644 index 00000000..8ab64d92 Binary files /dev/null and b/Debugging/Documentation/feather_3010-00_mbus_slave.jpg differ