From 9a2995667b507ff3a5ceb8d7829aab1d82d30eb5 Mon Sep 17 00:00:00 2001 From: Vegar Westerlund Date: Sat, 11 May 2019 21:34:11 +0200 Subject: [PATCH] Start working on a Adafruit Feather M0 WiFi w/ATWINC1500 firmware (with slight update to HanReader interface) --- .../AmsToMqttBridge/AmsToMqttBridge.ino | 7 +- .../HanReader/src/HanReader.cpp | 21 +- .../HanReader/src/HanReader.h | 1 - .../Arduino Libraries/HanReader/src/Kaifa.h | 16 +- .../WifiFeatherRestBridge.ino | 338 ++++++++++++++++++ .../WifiFeatherRestBridge/arduino_secrets.h | 3 + .../WifiFeatherRestBridge/wifi_client.h | 10 + .../WifiFeatherRestBridge/wifi_client.ino | 87 +++++ 8 files changed, 463 insertions(+), 20 deletions(-) create mode 100644 Arduino Code/WifiFeatherRestBridge/WifiFeatherRestBridge.ino create mode 100644 Arduino Code/WifiFeatherRestBridge/arduino_secrets.h create mode 100644 Arduino Code/WifiFeatherRestBridge/wifi_client.h create mode 100644 Arduino Code/WifiFeatherRestBridge/wifi_client.ino diff --git a/Arduino Code/AmsToMqttBridge/AmsToMqttBridge.ino b/Arduino Code/AmsToMqttBridge/AmsToMqttBridge.ino index b5a184cd..ce7e2e5c 100644 --- a/Arduino Code/AmsToMqttBridge/AmsToMqttBridge.ino +++ b/Arduino Code/AmsToMqttBridge/AmsToMqttBridge.ino @@ -67,8 +67,11 @@ void setup() if (!ap.isActivated) { setupWiFi(); - hanReader.setup(&Serial, 2400, SERIAL_8E1, 0); - + Serial.begin(2400, SERIAL_8E1); + while (!Serial); + + hanReader.setup(&Serial, debugger); + // Compensate for the known Kaifa bug hanReader.compensateFor09HeaderBug = (ap.config.meterType == 1); } diff --git a/Arduino Code/Arduino Libraries/HanReader/src/HanReader.cpp b/Arduino Code/Arduino Libraries/HanReader/src/HanReader.cpp index 623a9bcf..69ca424b 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,8 +51,9 @@ 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; } } @@ -294,4 +283,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..87e5370c 100644 --- a/Arduino Code/Arduino Libraries/HanReader/src/Kaifa.h +++ b/Arduino Code/Arduino Libraries/HanReader/src/Kaifa.h @@ -4,7 +4,8 @@ enum class Kaifa : byte { List1 = 0x01, List2 = 0x0D, - List3 = 0x12 + List3 = 0x12, + List4 = 0x09 }; enum class Kaifa_List1 { @@ -51,4 +52,17 @@ enum class Kaifa_List3 { CumulativeReactiveExportEnergy }; +enum class Kaifa_List4 { // TODO: Stop using list size like this? + ListSize, + ListVersionIdentifier, + MeterID, + MeterType, + ActiveImportPower, + ActiveExportPower, + ReactiveImportPower, + ReactiveExportPower, + CurrentL1, + CurrentL2, +}; + #endif diff --git a/Arduino Code/WifiFeatherRestBridge/WifiFeatherRestBridge.ino b/Arduino Code/WifiFeatherRestBridge/WifiFeatherRestBridge.ino new file mode 100644 index 00000000..19ae071f --- /dev/null +++ b/Arduino Code/WifiFeatherRestBridge/WifiFeatherRestBridge.ino @@ -0,0 +1,338 @@ +#include +#include +#include +#include "Kaifa.h" +#include "Kamstrup.h" +#include "HanReader.h" +#include "wifi_client.h" + + +#define LED_PIN 13 // The red led on the WiFi Feather + + +int state = 0; +WiFiClient client; + +// Object used for debugging +Serial_* debugger = NULL; + +// The HAN Port reader, used to read serial data and decode DLMS +HanReader hanReader; +byte meterType = 1; // Kaifa TODO: Read config, don't hard code + +void setup() { + // Enable red LED + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, HIGH); + delay(200); + digitalWrite(LED_PIN, LOW); + delay(100); + + // Initialize serial and wait for port to open: + Serial.begin(9600); + while (!Serial) { + ; // wait for serial port to connect. Needed for native USB port only + } + + // Uncomment to debug over uart + debugger = &Serial; + + if (debugger) debugger->print("Unwantingly wait 5"); // Allow programming during restart + delay(1000); + if (debugger) debugger->print(" 4"); + delay(1000); + if (debugger) debugger->print(" 3"); + delay(1000); + if (debugger) debugger->print(" 2"); + delay(1000); + if (debugger) debugger->print(" 1"); + delay(1000); + if (debugger) debugger->println(" 0"); + + // Configure uart for AMS data + Serial1.begin(2400, SERIAL_8E1); + while (!Serial1); + + // Configure pins for Adafruit ATWINC1500 Feather + WiFi.setPins(8,7,4,2); + + // Check for the presence of the shield: + if (WiFi.status() == WL_NO_SHIELD) { + if (debugger) debugger->println("WiFi shield not present"); + // don't continue: + while (true); + } + + //connect_wifi(); + + hanReader.setup(&Serial1, debugger); + + // Compensate for the known Kaifa bug + hanReader.compensateFor09HeaderBug = (meterType == 1); +} + +void loop() { + // If there are incoming bytes available + // from the server, read them and print them: + while (client.available()) { + state = 2; + char c = client.read(); + Serial.write(c); + } + + // Change state if last byte read from client + if (state == 2 && !client.available()) { + state = 0; + + if (debugger) debugger->println(); + if (debugger) debugger->println("Response data received"); + } + + readHanPort(); + + //if (debugger) debugger->println("Wait for event "); + __WFI(); +} + + +void readHanPort() +{ + if (hanReader.read()) + { + // Flash LED on, this shows us that data is received + digitalWrite(LED_PIN, HIGH); + + // Get the list identifier + int listSize = hanReader.getListSize(); + + switch (meterType) + { + case 1: // Kaifa + readHanPort_Kaifa(listSize); + break; + case 2: // Aidon + readHanPort_Aidon(listSize); + break; + case 3: // Kamstrup + readHanPort_Kamstrup(listSize); + break; + default: + if (debugger) { + debugger->print("Meter type "); + debugger->print(meterType, HEX); + debugger->println(" is unknown"); + } + delay(10000); // TODO: Why sleep? + break; + } + + // Flash LED off + digitalWrite(LED_PIN, LOW); + } +} + +void readHanPort_Aidon(int listSize) +{ + if (debugger) debugger->println("Meter type Aidon is not yet implemented"); + delay(1000); +} + +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 + StaticJsonDocument<500> doc; + + // Any generic useful info here + //doc["id"] = WiFi.macAddress(); // TODO: Fix? + doc["up"] = millis(); + doc["t"] = time; + + // Add a sub-structure to the json object, + // to keep the data from the meter itself + JsonObject data = doc.createNestedObject("data"); + + // 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: "); + serializeJsonPretty(doc, *debugger); + debugger->println(); + } + + // TODO: Post data + //// 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]; + //serializeJsonPretty(doc, msg, 1024); + //mqtt.publish(ap.config.mqttPublishTopic, msg); + + //if (send_data(&client)) { + // state = 1; + //} + } +} + + +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 || listSize == (int)Kaifa::List4) + { + 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 + //StaticJsonDocument<500> doc; + DynamicJsonDocument doc(500); // TODO: Too small? + + // Any generic useful info here + //doc["id"] = WiFi.macAddress(); // TODO: Fix? + doc["up"] = millis(); + doc["t"] = time; + + // Add a sub-structure to the json object, + // to keep the data from the meter itself + JsonObject data = doc.createNestedObject("data"); + + // 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); + } + else if (listSize == (int)Kaifa::List4) + { + 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); + } + + // Write the json to the debug port + if (debugger) { + debugger->print("Sending data to MQTT: "); + serializeJsonPretty(doc, *debugger); + debugger->println(); + debugger->print("data size: "); + debugger->println(measureJson(doc)); + } + + // TODO: Post data + //// 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]; + serializeJson(doc, msg, sizeof(msg)); + //mqtt.publish(ap.config.mqttPublishTopic, msg); + + if (send_data(&client, msg)) { + state = 1; + } + } +} + diff --git a/Arduino Code/WifiFeatherRestBridge/arduino_secrets.h b/Arduino Code/WifiFeatherRestBridge/arduino_secrets.h new file mode 100644 index 00000000..2b6787e9 --- /dev/null +++ b/Arduino Code/WifiFeatherRestBridge/arduino_secrets.h @@ -0,0 +1,3 @@ +#define SECRET_SSID "" +#define SECRET_PASS "" +#define AUTH_HEADER "Basic [base64-user:pass-string]" diff --git a/Arduino Code/WifiFeatherRestBridge/wifi_client.h b/Arduino Code/WifiFeatherRestBridge/wifi_client.h new file mode 100644 index 00000000..b1d5d829 --- /dev/null +++ b/Arduino Code/WifiFeatherRestBridge/wifi_client.h @@ -0,0 +1,10 @@ +#ifndef _WIFI_CLIENT_H +#define _WIFI_CLIENT_H + +#include + + +void connect_wifi(); +bool send_data(WiFiClient *client, char* json_data); + +#endif//_WIFI_CLIENT_H diff --git a/Arduino Code/WifiFeatherRestBridge/wifi_client.ino b/Arduino Code/WifiFeatherRestBridge/wifi_client.ino new file mode 100644 index 00000000..b49cceb9 --- /dev/null +++ b/Arduino Code/WifiFeatherRestBridge/wifi_client.ino @@ -0,0 +1,87 @@ +#include "wifi_client.h" +#include +#include "arduino_secrets.h" + + +char ssid[] = SECRET_SSID; +char pass[] = SECRET_PASS; +char auth[] = AUTH_HEADER; + + +// TODO: s/Serial.print/if (debugger) debugger->print/ +// TODO: Rename from .ino to .cpp + + +void printWiFiStatus() { + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); + + // print the received signal strength: + long rssi = WiFi.RSSI(); + Serial.print("signal strength (RSSI):"); + Serial.print(rssi); + Serial.println(" dBm"); +} + + +void connect_wifi() { + // attempt to connect to WiFi network: + while (true) { + Serial.print("Attempting to connect to SSID: "); + Serial.println(ssid); + // Connect to WPA/WPA2 network. Change this line if using open or WEP network: + int status = WiFi.begin(ssid, pass); + + if (status == WL_CONNECTED) { + break; + } + + // wait 1 seconds for connection: + Serial.print("Waiting 1 before trying again. status: "); + Serial.println(status); + delay(1000); + } + + WiFi.lowPowerMode(); + //WiFi.setSleepMode(M2M_PS_H_AUTOMATIC, 1); + + Serial.println("Connected to wifi"); + printWiFiStatus(); +} + + +bool send_data(WiFiClient *client, char* json_data) { + if (WiFi.status() != WL_CONNECTED) { + Serial.println("Ehhh, not connected?"); + return false; + } + + Serial.println("\nConnection to server..."); + if (client->connect("kanskje.de", 8181)) { + Serial.println("connected to server"); + char content_length[] = "Content-Length: 1234567890"; + sprintf(content_length, "Content-Length: %d", strlen(json_data)); + + // Make a HTTP request: + client->println("POST /what-ever HTTP/1.1"); + client->println("Host: kanskje.de"); + client->print("Authorization: "); + client->println(auth); + client->println("Content-Type: application/json"); + client->println(content_length); + client->println("Connection: close"); + client->println(); + client->println(json_data); + return true; + } + + return false; +} + +