diff --git a/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.ino b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.ino new file mode 100644 index 00000000..dbb48bb8 --- /dev/null +++ b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.ino @@ -0,0 +1,501 @@ +/* + Name: AmsToMqttBridge.ino + Created: 3/13/2018 7:40:28 PM + Author: roarf +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include "configuration.h" +#include "accesspoint.h" + +#define WIFI_CONNECTION_TIMEOUT 30000; +#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; + +// Object used to boot as Access Point +accesspoint ap; + +// WiFi client and MQTT client +WiFiClient *client; +PubSubClient mqtt; + +// Object used for debugging +HardwareSerial* debugger = NULL; + +// The HAN Port reader, used to read serial data and decode DLMS +HanReader hanReader; + +// the setup function runs once when you press reset or power the board +void setup() +{ + // Uncomment to debug over the same port as used for HAN communication + debugger = &Serial; + + if (debugger) { + // Setup serial port for debugging + debugger->begin(2400, SERIAL_8E1); + while (!&debugger); + 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 + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, LOW); + + // Initialize the AP + ap.setup(0, Serial); + + // Turn off the blue LED + digitalWrite(LED_PIN, HIGH); + + if (!ap.isActivated) + { + setupWiFi(); + hanReader.setup(&Serial, 2400, SERIAL_8E1, debugger); + + // Compensate for the known Kaifa bug + hanReader.compensateFor09HeaderBug = (ap.config.meterType == 1); + } +} + +// the loop function runs over and over again until power down or reset +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); + + // allow the MQTT client some resources + mqtt.loop(); + delay(10); // <- fixes some issues with WiFi stability + + // Reconnect to WiFi and MQTT as needed + if (!mqtt.connected()) { + MQTT_connect(); + } + else + { + // Read data from the HAN port + readHanPort(); + } + } + else + { + // Continously flash the blue LED when AP mode + if (millis() / 1000 % 2 == 0) + digitalWrite(LED_PIN, LOW); + else + digitalWrite(LED_PIN, HIGH); + } +} + +void setupWiFi() +{ + // Turn off AP + WiFi.enableAP(false); + + // Connect to WiFi + WiFi.begin(ap.config.ssid, ap.config.ssidPassword); + + // Initialize WiFi and MQTT clients + if (ap.config.isSecure()) + client = new WiFiClientSecure(); + else + client = new WiFiClient(); + mqtt = PubSubClient(*client); + mqtt.setServer(ap.config.mqtt, ap.config.mqttPort); + + // Direct incoming MQTT messages + if (ap.config.mqttSubscribeTopic != 0 && strlen(ap.config.mqttSubscribeTopic) > 0) + mqtt.setCallback(mqttMessageReceived); + + // Connect to the MQTT server + MQTT_connect(); + + // Notify everyone we're here! + sendMqttData("Connected!"); +} + +void mqttMessageReceived(char* topic, unsigned char* payload, unsigned int length) +{ + // make the incoming message a null-terminated string + char message[1000]; + for (int i = 0; i < length; i++) + message[i] = payload[i]; + message[length] = 0; + + if (debugger) { + debugger->println("Incoming MQTT message:"); + debugger->print("["); + debugger->print(topic); + debugger->print("] "); + debugger->println(message); + } + + // Do whatever needed here... + // Ideas could be to query for values or to initiate OTA firmware update +} + +void readHanPort() +{ + if (hanReader.read()) + { + // Flash LED on, this shows us that data is received + digitalWrite(LED_PIN, LOW); + + // Get the list identifier + int listSize = hanReader.getListSize(); + + switch (ap.config.meterType) + { + case 1: // Kaifa + readHanPort_Kaifa(listSize); + break; + case 2: // Kamstrup + readHanPort_Kamstrup(listSize); + break; + case 3: // Aidon + readHanPort_Aidon(listSize); + break; + default: + debugger->print("Meter type "); + debugger->print(ap.config.meterType, HEX); + debugger->println(" is unknown"); + delay(10000); + break; + } + + // Flash LED off + digitalWrite(LED_PIN, HIGH); + } +} + +void readHanPort_Aidon(int listSize) +{ + if (debugger) + debugger->println("Meter type Aidon is not yet implemented"); + delay(10000); +} + +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() +{ + // Connect to WiFi access point. + if (debugger) + { + debugger->println(); + debugger->println(); + debugger->print("Connecting to WiFi network "); + debugger->println(ap.config.ssid); + } + + if (WiFi.status() != WL_CONNECTED) + { + // Make one first attempt at connect, this seems to considerably speed up the first connection + WiFi.disconnect(); + WiFi.begin(ap.config.ssid, ap.config.ssidPassword); + delay(1000); + } + + // Wait for the WiFi connection to complete + long vTimeout = millis() + WIFI_CONNECTION_TIMEOUT; + while (WiFi.status() != WL_CONNECTED) { + delay(50); + if (debugger) debugger->print("."); + + // If we timed out, disconnect and try again + if (vTimeout < millis()) + { + if (debugger) + { + debugger->print("Timout during connect. WiFi status is: "); + debugger->println(WiFi.status()); + } + WiFi.disconnect(); + WiFi.begin(ap.config.ssid, ap.config.ssidPassword); + vTimeout = millis() + WIFI_CONNECTION_TIMEOUT; + } + yield(); + } + + if (debugger) { + debugger->println(); + debugger->println("WiFi connected"); + debugger->println("IP address: "); + debugger->println(WiFi.localIP()); + debugger->print("\nconnecting to MQTT: "); + debugger->print(ap.config.mqtt); + debugger->print(", port: "); + debugger->print(ap.config.mqttPort); + debugger->println(); + } + + // 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)) || + (ap.config.mqttUser != 0 && mqtt.connect(ap.config.mqttClientID, ap.config.mqttUser, ap.config.mqttPass))) + { + if (debugger) debugger->println("\nSuccessfully connected to MQTT!"); + + // Subscribe to the chosen MQTT topic, if set in configuration + if (ap.config.mqttSubscribeTopic != 0 && strlen(ap.config.mqttSubscribeTopic) > 0) + { + mqtt.subscribe(ap.config.mqttSubscribeTopic); + if (debugger) debugger->printf(" Subscribing to [%s]\r\n", ap.config.mqttSubscribeTopic); + } + } + else + { + if (debugger) + { + debugger->print("."); + debugger->print("failed, mqtt.state() = "); + debugger->print(mqtt.state()); + debugger->println(" trying again in 5 seconds"); + } + + // Wait 2 seconds before retrying + mqtt.disconnect(); + delay(2000); + } + + // Allow some resources for the WiFi connection + yield(); + } +} + +// Send a simple string embedded in json over MQTT +void sendMqttData(String data) +{ + // Make sure we have configured a publish topic + if (ap.config.mqttPublishTopic == 0 || strlen(ap.config.mqttPublishTopic) == 0) + return; + + // Make sure we're connected + if (!client->connected() || !mqtt.connected()) { + MQTT_connect(); + } + + // Build a json with the message in a "data" attribute + DynamicJsonBuffer jsonBuffer; + JsonObject& json = jsonBuffer.createObject(); + json["id"] = WiFi.macAddress(); + json["up"] = millis(); + json["data"] = data; + + // Stringify the json + String msg; + json.printTo(msg); + + // Send the json over MQTT + mqtt.publish(ap.config.mqttPublishTopic, msg.c_str()); +} \ No newline at end of file diff --git a/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.sln b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.sln new file mode 100644 index 00000000..af25343f --- /dev/null +++ b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.16 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AmsToMqttBridge", "AmsToMqttBridge.vcxproj", "{C5F80730-F44F-4478-BDAE-6634EFC2CA88}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HanReader", "..\HanReader\HanReader.vcxitems", "{CD0F5364-923B-49E4-8BE5-EA7D8A60DF80}" +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\HanReader\HanReader.vcxitems*{c5f80730-f44f-4478-bdae-6634efc2ca88}*SharedItemsImports = 4 + ..\HanReader\HanReader.vcxitems*{cd0f5364-923b-49e4-8be5-ea7d8a60df80}*SharedItemsImports = 9 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Debug|x86.ActiveCfg = Debug|Win32 + {C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Debug|x86.Build.0 = Debug|Win32 + {C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Release|x86.ActiveCfg = Release|Win32 + {C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5DFC14B6-4C33-4307-8BDA-C050F68A74F6} + EndGlobalSection +EndGlobal diff --git a/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.vcxproj b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.vcxproj new file mode 100644 index 00000000..117cc3cf --- /dev/null +++ b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.vcxproj @@ -0,0 +1,104 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {C5F80730-F44F-4478-BDAE-6634EFC2CA88} + AmsToMqttBridge + AmsToMqttBridge + + + + Application + true + + + MultiByte + + + Application + false + + + true + MultiByte + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + $(ProjectDir)..\AmsToMqttBridge;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\libraries\EEPROM;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\libraries\ESP8266WiFi\src;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\libraries\ESP8266WebServer\src;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\libraries\DNSServer\src;$(ProjectDir)..\HanReader\src;$(ProjectDir)..\..\..\..\..\..\..\..\..\Program Files (x86)\Arduino\libraries;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\libraries;$(ProjectDir)..\..\..\..\..\..\..\Google Drive\Private\Elektronikk\Arduino\libraries;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266\libb64;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266\spiffs;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266\umm_malloc;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\variants\generic;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\tools\sdk\include;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\tools\sdk\lwip\include;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\tools\xtensa-lx106-elf-gcc\1.20.0-26-gb404fb9-2\xtensa-lx106-elf\include\c++\4.8.2;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\tools\xtensa-lx106-elf-gcc\1.20.0-26-gb404fb9-2\xtensa-lx106-elf\include\c++\4.8.2\xtensa-lx106-elf;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\tools\xtensa-lx106-elf-gcc\1.20.0-26-gb404fb9-2\xtensa-lx106-elf\include;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\tools\xtensa-lx106-elf-gcc\1.20.0-26-gb404fb9-2\lib\gcc\xtensa-lx106-elf\4.8.2\include;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\tools\sdk\include;%(AdditionalIncludeDirectories) + $(ProjectDir)__vm\.AmsToMqttBridge.vsarduino.h;%(ForcedIncludeFiles) + false + __ESP8266_ESp8266__;__ESP8266_ESP8266__;__ets__;ICACHE_FLASH;F_CPU=80000000L;LWIP_OPEN_SRC;ARDUINO=106012;ARDUINO_ESP8266_ESP01;ARDUINO_ARCH_ESP8266;ESP8266;__cplusplus=201103L;_VMICRO_INTELLISENSE;%(PreprocessorDefinitions) + + + true + + + + + Level3 + MaxSpeed + true + true + true + + + true + true + true + + + + + + + VisualMicroDebugger + + + + CppCode + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.vcxproj.filters b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.vcxproj.filters new file mode 100644 index 00000000..509b0459 --- /dev/null +++ b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.vcxproj.filters @@ -0,0 +1,25 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + + + Header Files + + + \ No newline at end of file diff --git a/Code/Arduino/AmsToMqttBridge/accesspoint.cpp b/Code/Arduino/AmsToMqttBridge/accesspoint.cpp new file mode 100644 index 00000000..d45e4475 --- /dev/null +++ b/Code/Arduino/AmsToMqttBridge/accesspoint.cpp @@ -0,0 +1,178 @@ +// +// +// + +#include "accesspoint.h" + +ESP8266WebServer accesspoint::server(80); +Stream* accesspoint::debugger; + +bool accesspoint::hasConfig() { + return config.hasConfig(); +} + +void accesspoint::setup(int accessPointButtonPin, Stream& debugger) +{ + this->debugger = &debugger; + + // Test if we're missing configuration + if (!config.hasConfig()) + { + print("No config. We're booting as AP. Look for SSID "); + println(this->AP_SSID); + isActivated = true; + } + else + { + // Load the configuration + config.load(); + if (this->debugger) config.print(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) + { + print("."); + if (digitalRead(accessPointButtonPin) == LOW) + { + println(""); + print("AP button was pressed. Booting as access point now. Look for SSID "); + println(this->AP_SSID); + isActivated = true; + break; + } + delay(100); + } + } + + if (isActivated) + { + // Setup AP + WiFi.disconnect(true); + WiFi.softAPdisconnect(true); + WiFi.mode(WIFI_OFF); + delay(2000); + + WiFi.softAP(AP_SSID); + WiFi.mode(WIFI_AP); + + /* Setup the DNS server redirecting all the domains to this IP */ + dnsServer.setErrorReplyCode(DNSReplyCode::NoError); + dnsServer.start(DNS_PORT, "*", WiFi.softAPIP()); + + server.on("/", handleRoot); + server.on("/save", handleSave); + server.begin(); // Web server start + + print("Web server is ready for config at http://"); + print(WiFi.softAPIP()); + println("/"); + } +} + +bool accesspoint::loop() { + if (isActivated) + { + //DNS + dnsServer.processNextRequest(); + //HTTP + server.handleClient(); + return true; + } + else + { + return false; + } +} + +/** Handle root or redirect to captive portal */ +void accesspoint::handleRoot() { + println("Serving / over http..."); + + server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + server.sendHeader("Pragma", "no-cache"); + server.sendHeader("Expires", "-1"); + server.setContentLength(CONTENT_LENGTH_UNKNOWN); + server.send(200, "text/html", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. + String html = String("\r\n\r\n\r\n\t\r\n\r\n\r\n\r\n\t
\r\n\r\n\t\t
\r\n\r\n\t\t\t
\r\n\t\t\t\t
\r\n\t\t\t\t\t

WiFi

\r\n\t\t\t\t
\r\n\t\t\t\t
\r\n\t\t\t\t\t\r\n\t\t\t\t
\r\n\t\t\t\t
\r\n\t\t\t\t\t\r\n\t\t\t\t
\r\n\t\t\t
\r\n\t\t\t
\r\n\t\t\t\t
\r\n\t\t\t\t\t

Meter Type

\r\n\t\t\t\t
\r\n\t\t\t\t
\r\n\t\t\t\t\t\r\n\t\t\t\t
\r\n\t\t\t
\r\n\t\t\t
\r\n\t\t\t\t
\r\n\t\t\t\t\t

MQTT

\r\n\t\t\t\t
\r\n\t\t\t\t
\r\n\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t
\r\n\t\t\t\t
\r\n\t\t\t\t\t\r\n\t\t\t\t
\r\n\t\t\t\t
\r\n\t\t\t\t\t\r\n\t\t\t\t
\r\n\t\t\t\t
\r\n\t\t\t\t\t\r\n\t\t\t\t
\r\n\t\t\t\t
\r\n\t\t\t\t\t\r\n\t\t\t\t
\r\n\t\t\t\t
\r\n\t\t\t\t\t\r\n\t\t\t\t
\r\n\t\t\t\t
\r\n\t\t\t\t\t\r\n\t\t\t\t
\r\n\t\t\t
\r\n\t\t
\r\n\t
\r\n\r\n\t\r\n\r\n"); + server.sendContent(html); + server.client().stop(); // Stop is needed because we sent no content length +} + + +void accesspoint::handleSave() { + configuration *config = new configuration(); + + String temp; + + temp = server.arg("ssid"); + config->ssid = new char[temp.length() + 1]; + temp.toCharArray(config->ssid, temp.length() + 1, 0); + + temp = server.arg("ssidPassword"); + config->ssidPassword = new char[temp.length() + 1]; + temp.toCharArray(config->ssidPassword, temp.length() + 1, 0); + + config->meterType = (byte)server.arg("meterType").toInt(); + + temp = server.arg("mqtt"); + config->mqtt = new char[temp.length() + 1]; + temp.toCharArray(config->mqtt, temp.length() + 1, 0); + + config->mqttPort = (int)server.arg("mqttPort").toInt(); + + temp = server.arg("mqttClientID"); + config->mqttClientID = new char[temp.length() + 1]; + temp.toCharArray(config->mqttClientID, temp.length() + 1, 0); + + temp = server.arg("mqttPublishTopic"); + config->mqttPublishTopic = new char[temp.length() + 1]; + temp.toCharArray(config->mqttPublishTopic, temp.length() + 1, 0); + + temp = server.arg("mqttSubscribeTopic"); + config->mqttSubscribeTopic = new char[temp.length() + 1]; + temp.toCharArray(config->mqttSubscribeTopic, temp.length() + 1, 0); + + temp = server.arg("mqttUser"); + config->mqttUser = new char[temp.length() + 1]; + temp.toCharArray(config->mqttUser, temp.length() + 1, 0); + + temp = server.arg("mqttPass"); + config->mqttPass = new char[temp.length() + 1]; + temp.toCharArray(config->mqttPass, temp.length() + 1, 0); + + println("Saving configuration now..."); + + if (accesspoint::debugger) config->print(*accesspoint::debugger); + if (config->save()) + { + println("Successfully saved. Will roboot now."); + String html = "

Successfully Saved!

Device is restarting now...

"; + server.send(200, "text/html", html); + ESP.reset(); + } + else + { + println("Error saving configuration"); + String html = "

Error saving configuration!

"; + server.send(500, "text/html", html); + } +} + +size_t accesspoint::print(const char* text) +{ + if (debugger) debugger->print(text); +} +size_t accesspoint::println(const char* text) +{ + if (debugger) debugger->println(text); +} +size_t accesspoint::print(const Printable& data) +{ + if (debugger) debugger->print(data); +} +size_t accesspoint::println(const Printable& data) +{ + if (debugger) debugger->println(data); +} diff --git a/Code/Arduino/AmsToMqttBridge/accesspoint.h b/Code/Arduino/AmsToMqttBridge/accesspoint.h new file mode 100644 index 00000000..527a90d6 --- /dev/null +++ b/Code/Arduino/AmsToMqttBridge/accesspoint.h @@ -0,0 +1,46 @@ +// ap.h + +#ifndef _ACCESSPOINT_h +#define _ACCESSPOINT_h + +#if defined(ARDUINO) && ARDUINO >= 100 + #include "Arduino.h" +#else + #include "WProgram.h" +#endif + +#include +#include +#include +#include "configuration.h" + +class accesspoint { +public: + void setup(int accessPointButtonPin, Stream& debugger); + bool loop(); + bool hasConfig(); + configuration config; + bool isActivated = false; + +private: + const char* AP_SSID = "AMS2MQTT"; + + // DNS server + const byte DNS_PORT = 53; + DNSServer dnsServer; + + static size_t print(const char* text); + static size_t println(const char* text); + static size_t print(const Printable& data); + static size_t println(const Printable& data); + + // Web server + static void handleRoot(); + static void handleSave(); + static ESP8266WebServer server; + + static Stream* debugger; +}; + +#endif + diff --git a/Code/Arduino/AmsToMqttBridge/configuration.cpp b/Code/Arduino/AmsToMqttBridge/configuration.cpp new file mode 100644 index 00000000..6f0013ef --- /dev/null +++ b/Code/Arduino/AmsToMqttBridge/configuration.cpp @@ -0,0 +1,230 @@ +// +// +// + +#include "configuration.h" + +bool configuration::hasConfig() +{ + bool hasConfig = false; + EEPROM.begin(EEPROM_SIZE); + hasConfig = EEPROM.read(EEPROM_CONFIG_ADDRESS) == EEPROM_CHECK_SUM; + EEPROM.end(); + return hasConfig; +} + +bool configuration::save() +{ + int address = EEPROM_CONFIG_ADDRESS; + + EEPROM.begin(EEPROM_SIZE); + EEPROM.put(address, EEPROM_CHECK_SUM); + address++; + + address += saveString(address, ssid); + address += saveString(address, ssidPassword); + address += saveByte(address, meterType); + address += saveString(address, mqtt); + address += saveInt(address, mqttPort); + address += saveString(address, mqttClientID); + address += saveString(address, mqttPublishTopic); + address += saveString(address, mqttSubscribeTopic); + + if (isSecure()) { + address += saveBool(address, true); + address += saveString(address, mqttUser); + address += saveString(address, mqttPass); + } + else + address += saveBool(address, false); + + bool success = EEPROM.commit(); + EEPROM.end(); + + return success; +} + + +bool configuration::load() +{ + int address = EEPROM_CONFIG_ADDRESS; + bool success = false; + + EEPROM.begin(EEPROM_SIZE); + if (EEPROM.read(address) == EEPROM_CHECK_SUM) + { + address++; + + address += readString(address, &ssid); + address += readString(address, &ssidPassword); + address += readByte(address, &meterType); + address += readString(address, &mqtt); + address += readInt(address, &mqttPort); + address += readString(address, &mqttClientID); + address += readString(address, &mqttPublishTopic); + address += readString(address, &mqttSubscribeTopic); + + bool secure = false; + address += readBool(address, &secure); + + if (secure) + { + address += readString(address, &mqttUser); + address += readString(address, &mqttPass); + } + else + { + mqttUser = 0; + mqttPass = 0; + } + + success = true; + } + else + { + ssid = (char*)String("").c_str(); + ssidPassword = (char*)String("").c_str(); + meterType = (byte)0; + mqtt = (char*)String("").c_str(); + mqttClientID = (char*)String("").c_str(); + mqttPublishTopic = (char*)String("").c_str(); + mqttSubscribeTopic = (char*)String("").c_str(); + mqttUser = 0; + mqttPass = 0; + mqttPort = 1883; + } + EEPROM.end(); + return success; +} + +bool configuration::isSecure() +{ + return (mqttUser != 0) && (String(mqttUser).length() > 0); +} + +int configuration::readInt(int address, int *value) +{ + int lower = EEPROM.read(address); + int higher = EEPROM.read(address + 1); + *value = lower + (higher << 8); + return 2; +} +int configuration::saveInt(int address, int value) +{ + byte lowByte = value & 0xFF; + byte highByte = ((value >> 8) & 0xFF); + + EEPROM.write(address, lowByte); + EEPROM.write(address + 1, highByte); + + return 2; +} + +int configuration::readBool(int address, bool *value) +{ + byte y = EEPROM.read(address); + *value = (bool)y; + return 1; +} + +int configuration::saveBool(int address, bool value) +{ + byte y = (byte)value; + EEPROM.write(address, y); + return 1; +} + +int configuration::readByte(int address, byte *value) +{ + *value = EEPROM.read(address); + return 1; +} + +int configuration::saveByte(int address, byte value) +{ + EEPROM.write(address, value); + return 1; +} +void configuration::print(Stream& serial) +{ + /* + char* ssid; + char* ssidPassword; + byte meterType; + char* mqtt; + int mqttPort; + char* mqttClientID; + char* mqttPublishTopic; + char* mqttSubscribeTopic; + bool secure; + char* mqttUser; + 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); + + 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); + } + serial.println("-----------------------------------------------"); +} + +template int configuration::writeAnything(int ee, const T& value) +{ + const byte* p = (const byte*)(const void*)&value; + unsigned int i; + for (i = 0; i < sizeof(value); i++) + EEPROM.write(ee++, *p++); + return i; +} + +template int configuration::readAnything(int ee, T& value) +{ + byte* p = (byte*)(void*)&value; + unsigned int i; + for (i = 0; i < sizeof(value); i++) + *p++ = EEPROM.read(ee++); + return i; +} + +int configuration::readString(int pAddress, char* pString[]) +{ + int address = 0; + byte length = EEPROM.read(pAddress + address); + address++; + + char* buffer = new char[length]; + for (int i = 0; i + + +#if defined(ARDUINO) && ARDUINO >= 100 + #include "Arduino.h" +#else + #include "WProgram.h" +#endif + +class configuration { +public: + char* ssid; + char* ssidPassword; + char* mqtt; + int mqttPort; + char* mqttClientID; + char* mqttPublishTopic; + char* mqttSubscribeTopic; + char* mqttUser; + char* mqttPass; + byte meterType; + + bool hasConfig(); + bool isSecure(); + bool save(); + bool load(); + + void print(Stream& serial); +protected: + +private: + const int EEPROM_SIZE = 512; + const byte EEPROM_CHECK_SUM = 71; // Used to check if config is stored. Change if structure changes + const int EEPROM_CONFIG_ADDRESS = 0; + + int saveString(int pAddress, char* pString); + int readString(int pAddress, char* pString[]); + int saveInt(int pAddress, int pValue); + int readInt(int pAddress, int *pValue); + int saveBool(int pAddress, bool pValue); + int readBool(int pAddress, bool *pValue); + int saveByte(int pAddress, byte pValue); + int readByte(int pAddress, byte *pValue); + + + template int writeAnything(int ee, const T& value); + template int readAnything(int ee, T& value); +}; + +#endif + diff --git a/Code/Arduino/HanReader/src/HanReader.cpp b/Code/Arduino/HanReader/src/HanReader.cpp index 298f984f..de3be461 100644 --- a/Code/Arduino/HanReader/src/HanReader.cpp +++ b/Code/Arduino/HanReader/src/HanReader.cpp @@ -81,6 +81,8 @@ 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(""); }