From 71860d0719cb0a943cf3fd25b95655de73a27d29 Mon Sep 17 00:00:00 2001 From: Roar Fredriksen Date: Tue, 13 Mar 2018 22:07:09 +0100 Subject: [PATCH 1/4] Created project Started on boot as access point --- .../AmsToMqttBridge/AmsToMqttBridge.ino | 45 ++++ .../AmsToMqttBridge/AmsToMqttBridge.sln | 25 ++ .../AmsToMqttBridge/AmsToMqttBridge.vcxproj | 90 +++++++ .../AmsToMqttBridge.vcxproj.filters | 25 ++ Code/Arduino/AmsToMqttBridge/accesspoint.cpp | 113 +++++++++ Code/Arduino/AmsToMqttBridge/accesspoint.h | 44 ++++ .../Arduino/AmsToMqttBridge/configuration.cpp | 231 ++++++++++++++++++ Code/Arduino/AmsToMqttBridge/configuration.h | 53 ++++ 8 files changed, 626 insertions(+) create mode 100644 Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.ino create mode 100644 Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.sln create mode 100644 Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.vcxproj create mode 100644 Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.vcxproj.filters create mode 100644 Code/Arduino/AmsToMqttBridge/accesspoint.cpp create mode 100644 Code/Arduino/AmsToMqttBridge/accesspoint.h create mode 100644 Code/Arduino/AmsToMqttBridge/configuration.cpp create mode 100644 Code/Arduino/AmsToMqttBridge/configuration.h diff --git a/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.ino b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.ino new file mode 100644 index 00000000..ce276d1d --- /dev/null +++ b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.ino @@ -0,0 +1,45 @@ +/* + Name: AmsToMqttBridge.ino + Created: 3/13/2018 7:40:28 PM + Author: roarf +*/ + + +#include "configuration.h" +#include "accesspoint.h" +#include + +accesspoint ap; + + +// the setup function runs once when you press reset or power the board +void setup() +{ + // Setup serial port for debugging + Serial.begin(115200); + while (!Serial); + Serial.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(2, OUTPUT); + digitalWrite(2, LOW); + + // Initialize the AP + ap.setup(0, Serial); + + // Turn off the blue LED + digitalWrite(2, HIGH); +} + +// the loop function runs over and over again until power down or reset +void loop() +{ + if (!ap.loop()) + { + // Only do normal stupp if we're not booted as AP + } +} diff --git a/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.sln b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.sln new file mode 100644 index 00000000..edff2767 --- /dev/null +++ b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.sln @@ -0,0 +1,25 @@ + +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 +Global + 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..51e7b949 --- /dev/null +++ b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.vcxproj @@ -0,0 +1,90 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {C5F80730-F44F-4478-BDAE-6634EFC2CA88} + AmsToMqttBridge + AmsToMqttBridge + + + + Application + true + + + MultiByte + + + Application + false + + + true + MultiByte + + + + + + + + + + + + + + + Level3 + Disabled + true + $(ProjectDir)..\AmsToMqttBridge;$(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..81f1dd89 --- /dev/null +++ b/Code/Arduino/AmsToMqttBridge/accesspoint.cpp @@ -0,0 +1,113 @@ +// +// +// + +#include "accesspoint.h" + +ESP8266WebServer accesspoint::server(80); + +void accesspoint::setup(int accessPointButtonPin, Stream& debugger) +{ + this->debugger = &debugger; + + // Test if we're missing configuration + if (!config.hasConfig()) + { + this->print("No config. We're booting as AP. Look for SSID "); + this->println(this->AP_SSID); + isActivated = true; + } + else + { + // Test if we're holding down the AP pin, over 5 seconds + int time = millis() + 5000; + this->print("Press the AP button now to boot as access point"); + while (millis() < time) + { + this->print("."); + if (digitalRead(accessPointButtonPin) == LOW) + { + this->println(""); + this->println("AP button was pressed. Booting as access point now"); + isActivated = true; + break; + } + delay(100); + } + } + + if (isActivated) + { + 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.begin(); // Web server start + + this->print("Web server is ready for config at http://"); + this->print(WiFi.softAPIP()); + this->println("/"); + } +} + +/** Handle root or redirect to captive portal */ +void accesspoint::handleRoot() { + Serial.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. + server.sendContent( + "" + "

HELLO WORLD!!

" + ); + server.sendContent( + "

You may want to config the wifi connection.

" + "" + ); + server.client().stop(); // Stop is needed because we sent no content length +} + +bool accesspoint::loop() { + if (isActivated) + { + //DNS + dnsServer.processNextRequest(); + //HTTP + server.handleClient(); + return true; + } + else + { + return false; + } +} + + +size_t accesspoint::print(const char* text) +{ + if (this->debugger) this->debugger->print(text); +} +size_t accesspoint::println(const char* text) +{ + if (this->debugger) this->debugger->println(text); +} +size_t accesspoint::print(const Printable& data) +{ + if (this->debugger) this->debugger->print(data); +} +size_t accesspoint::println(const Printable& data) +{ + if (this->debugger) this->debugger->println(data); +} diff --git a/Code/Arduino/AmsToMqttBridge/accesspoint.h b/Code/Arduino/AmsToMqttBridge/accesspoint.h new file mode 100644 index 00000000..0a4c18f5 --- /dev/null +++ b/Code/Arduino/AmsToMqttBridge/accesspoint.h @@ -0,0 +1,44 @@ +// 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(); + +private: + const char* AP_SSID = "AMS2MQTT"; + + configuration config; + bool isActivated = false; + Stream* debugger = NULL; + + // DNS server + const byte DNS_PORT = 53; + DNSServer dnsServer; + + size_t print(const char* text); + size_t println(const char* text); + size_t print(const Printable& data); + size_t println(const Printable& data); + + // Web server + static void handleRoot(); + static ESP8266WebServer server; +}; + +#endif + diff --git a/Code/Arduino/AmsToMqttBridge/configuration.cpp b/Code/Arduino/AmsToMqttBridge/configuration.cpp new file mode 100644 index 00000000..9ea14acd --- /dev/null +++ b/Code/Arduino/AmsToMqttBridge/configuration.cpp @@ -0,0 +1,231 @@ +// +// +// + +#include "configuration.h" + +bool configuration::hasConfig() +{ + bool has = false; + EEPROM.begin(EEPROM_SIZE); + has = EEPROM.read(EEPROM_CONFIG_ADDRESS) == EEPROM_CHECK_SUM; + EEPROM.end(); + return has; +} + +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 += 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 vRet = EEPROM.commit(); + EEPROM.end(); + + return vRet; +} + + +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 += 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(); + 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 pAddress, int *pValue) +{ + int lower = EEPROM.read(pAddress); + int higher = EEPROM.read(pAddress + 1); + *pValue = lower + (higher << 8); + return 2; +} +int configuration::saveInt(int pAddress, int pValue) +{ + byte lowByte = pValue & 0xFF; + byte highByte = ((pValue >> 8) & 0xFF); + + EEPROM.write(pAddress, lowByte); + EEPROM.write(pAddress + 1, highByte); + + return 2; +} + +int configuration::readBool(int pAddress, bool *pValue) +{ + byte y = EEPROM.read(pAddress); + *pValue = (bool)y; + //Serial.printf("Read bool as %#x [%s]\r\n", y, (*pValue ? "true" : "false")); + return 1; +} + +int configuration::saveBool(int pAddress, bool pValue) +{ + byte y = (byte)pValue; + //Serial.printf("Writing bool as %#x [%s]\r\n", y, (pValue ? "true" : "false")); + EEPROM.write(pAddress, y); + return 1; +} +void configuration::print(Stream& serial) +{ + + /* + char* ssid; + char* ssidPassword; + 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("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 vLength = EEPROM.read(pAddress + address); + address++; + + //Serial.print("Found length of string: "); + //Serial.println(vLength); + + char* buffer = new char[vLength]; + 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; + + bool hasConfig(); + bool isSecure(); + bool save(); + bool load(); + + void print(Stream& serial); +protected: + +private: + const int EEPROM_SIZE = 512; + const byte EEPROM_CHECK_SUM = 124; // 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); + + + template int writeAnything(int ee, const T& value); + template int readAnything(int ee, T& value); +}; + +#endif + From f8fe34a454bee785358631dd0f09fb39726a85da Mon Sep 17 00:00:00 2001 From: Roar Fredriksen Date: Mon, 19 Mar 2018 17:06:57 +0100 Subject: [PATCH 2/4] Including HanReader project --- Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.sln | 6 ++++++ .../AmsToMqttBridge/AmsToMqttBridge.vcxproj | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.sln b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.sln index edff2767..af25343f 100644 --- a/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.sln +++ b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.sln @@ -5,7 +5,13 @@ 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 diff --git a/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.vcxproj b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.vcxproj index 51e7b949..117cc3cf 100644 --- a/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.vcxproj +++ b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.vcxproj @@ -34,6 +34,9 @@ + + + @@ -47,7 +50,7 @@ Level3 Disabled true - $(ProjectDir)..\AmsToMqttBridge;$(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)..\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) @@ -82,9 +85,20 @@ + + + + + + + + + + + \ No newline at end of file From c991403e25d3e58a89a5bf8f6ec3a7ae68982fd8 Mon Sep 17 00:00:00 2001 From: Roar Fredriksen Date: Tue, 20 Mar 2018 01:21:49 +0100 Subject: [PATCH 3/4] Boot as AP, Configuration, TempSensor etc --- .../AmsToMqttBridge/AmsToMqttBridge.ino | 391 +++++++++++++++++- Code/Arduino/AmsToMqttBridge/accesspoint.cpp | 110 ++++- Code/Arduino/AmsToMqttBridge/accesspoint.h | 18 +- .../Arduino/AmsToMqttBridge/configuration.cpp | 87 ++-- Code/Arduino/AmsToMqttBridge/configuration.h | 5 +- Code/Arduino/HanReader/src/HanReader.cpp | 2 + 6 files changed, 534 insertions(+), 79 deletions(-) diff --git a/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.ino b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.ino index ce276d1d..b20d2b75 100644 --- a/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.ino +++ b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.ino @@ -5,20 +5,40 @@ */ +#include +#include +#include +#include #include "configuration.h" #include "accesspoint.h" #include +#include +#include + +#define WIFI_CONNECTION_TIMEOUT 30000; +#define TEMP_SENSOR_PIN 5 // Temperature sensor connected to GPIO5 + +OneWire oneWire(TEMP_SENSOR_PIN); +DallasTemperature tempSensor(&oneWire); +long lastTempDebug = 0; accesspoint ap; +WiFiClient *client; +PubSubClient mqtt; +HardwareSerial* debugger; +// The HAN Port reader +HanReader hanReader; // the setup function runs once when you press reset or power the board void setup() { + debugger = &Serial; + // Setup serial port for debugging - Serial.begin(115200); - while (!Serial); - Serial.println("Started..."); + debugger->begin(2400, SERIAL_8E1); + while (!&debugger); + debugger->println("Started..."); // Assign pin for boot as AP delay(1000); @@ -33,13 +53,376 @@ void setup() // Turn off the blue LED digitalWrite(2, HIGH); + + if (!ap.isActivated) + { + setupWiFi(); + hanReader.setup(&Serial, 2400, SERIAL_8E1, debugger); + hanReader.compensateFor09HeaderBug = (ap.config.meterType == 1); // To compensate for the known Kaifa bug + } +} + +void setupWiFi() +{ + if (ap.config.isSecure()) + client = new WiFiClientSecure(); + else + client = new WiFiClient(); + + mqtt = PubSubClient(*client); + + WiFi.enableAP(false); + WiFi.begin(ap.config.ssid, ap.config.ssidPassword); + mqtt.setServer(ap.config.mqtt, ap.config.mqttPort); + + //std::function vCallback = MakeDelegate(this, xnsClientClass::mqttMessageReceived); + mqtt.setCallback(mqttMessageReceived); + MQTT_connect(); + + sendMqttData("Connected!"); +} + +void mqttMessageReceived(char* topic, unsigned char* payload, unsigned int length) +{ + // make it a null-terminated string + char message[1000]; + for (int i = 0; i < length; i++) + message[i] = payload[i]; + message[length] = 0; + + debugger->println("Incoming MQTT message:"); + debugger->print("["); + debugger->print(topic); + debugger->print("] "); + debugger->println(message); } // the loop function runs over and over again until power down or reset void loop() { + // Only do normal stupp if we're not booted as AP if (!ap.loop()) { - // Only do normal stupp if we're not booted as AP + mqtt.loop(); + delay(10); // <- fixes some issues with WiFi stability + + if (!mqtt.connected()) { + MQTT_connect(); + } + else + { + debugTemperature(); + readHanPort(); + } } } +void debugTemperature() +{ + if (lastTempDebug + 5000 < millis()) + { + lastTempDebug = millis(); + tempSensor.requestTemperatures(); + float temperature = tempSensor.getTempCByIndex(0); + debugger->print("Temperature is "); + debugger->print(temperature); + debugger->println(" degrees"); + } +} +void readHanPort() +{ + switch (ap.config.meterType) + { + case 1: // Kaifa + readHanPort_Kaifa(); + break; + case 2: // Kamstrup + readHanPort_Kamstrup(); + break; + case 3: // Aidon + readHanPort_Aidon(); + break; + default: + debugger->print("Meter type "); + debugger->print(ap.config.meterType, HEX); + debugger->println(" is unknown"); + delay(10000); + break; + } +} + +void readHanPort_Aidon() +{ + debugger->println("Meter type Aidon is not yet implemented"); + delay(10000); +} + +void readHanPort_Kamstrup() +{ + if (hanReader.read()) + { + // Get the list identifier + int listSize = hanReader.getListSize(); + + // 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 + root.printTo(Serial1); + Serial1.println(); + + // Publish the json to the MQTT server + char msg[1024]; + root.printTo(msg, 1024); + mqtt.publish(ap.config.mqttPublishTopic, msg); + } + } +} + + +void readHanPort_Kaifa() { + if (hanReader.read()) + { + // Get the list identifier + int listSize = hanReader.getListSize(); + + // 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; + 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)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 + root.printTo(Serial1); + Serial1.println(); + + // Publish the json to the MQTT server + char msg[1024]; + root.printTo(msg, 1024); + mqtt.publish("sensors/out/espdebugger", 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(); if (debugger) debugger->println(); + if (debugger) debugger->print("Connecting to WiFi network "); + if (debugger) debugger->println(ap.config.ssid); + + long vTimeout = millis() + WIFI_CONNECTION_TIMEOUT; + while (WiFi.status() != WL_CONNECTED) { + delay(50); + if (debugger) debugger->print("."); + 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(); + } + while (!mqtt.connected()) { + 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!"); + 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("."); + if (debugger) debugger->print("failed, mqtt.state() = "); + if (debugger) debugger->print(mqtt.state()); + if (debugger) debugger->println(" trying again in 5 seconds"); + // Wait 2 seconds before retrying + mqtt.disconnect(); + delay(2000); + } + yield(); + } +} + +void sendMqttData(String data) +{ + if (ap.config.mqttPublishTopic == 0 || strlen(ap.config.mqttPublishTopic) == 0) + return; + + if (!client->connected() || !mqtt.connected()) { + MQTT_connect(); + } + + StaticJsonBuffer<500> jsonBuffer; + JsonObject& json = jsonBuffer.createObject(); + json["id"] = WiFi.macAddress(); + json["up"] = millis() / 1000; + json["data"] = data; + + String msg; + json.printTo(msg); + + mqtt.publish(ap.config.mqttPublishTopic, msg.c_str()); +} \ No newline at end of file diff --git a/Code/Arduino/AmsToMqttBridge/accesspoint.cpp b/Code/Arduino/AmsToMqttBridge/accesspoint.cpp index 81f1dd89..a1488402 100644 --- a/Code/Arduino/AmsToMqttBridge/accesspoint.cpp +++ b/Code/Arduino/AmsToMqttBridge/accesspoint.cpp @@ -5,6 +5,11 @@ #include "accesspoint.h" ESP8266WebServer accesspoint::server(80); +Stream* accesspoint::debugger; + +bool accesspoint::hasConfig() { + return config.hasConfig(); +} void accesspoint::setup(int accessPointButtonPin, Stream& debugger) { @@ -13,22 +18,27 @@ void accesspoint::setup(int accessPointButtonPin, Stream& debugger) // Test if we're missing configuration if (!config.hasConfig()) { - this->print("No config. We're booting as AP. Look for SSID "); - this->println(this->AP_SSID); + 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; - this->print("Press the AP button now to boot as access point"); + print("Press the AP button now to boot as access point"); while (millis() < time) { - this->print("."); + print("."); if (digitalRead(accessPointButtonPin) == LOW) { - this->println(""); - this->println("AP button was pressed. Booting as access point now"); + println(""); + print("AP button was pressed. Booting as access point now. Look for SSID "); + println(this->AP_SSID); isActivated = true; break; } @@ -38,6 +48,7 @@ void accesspoint::setup(int accessPointButtonPin, Stream& debugger) if (isActivated) { + // Setup AP WiFi.disconnect(true); WiFi.softAPdisconnect(true); WiFi.mode(WIFI_OFF); @@ -51,34 +62,89 @@ void accesspoint::setup(int accessPointButtonPin, Stream& debugger) dnsServer.start(DNS_PORT, "*", WiFi.softAPIP()); server.on("/", handleRoot); + server.on("/save", handleSave); server.begin(); // Web server start - this->print("Web server is ready for config at http://"); - this->print(WiFi.softAPIP()); - this->println("/"); + print("Web server is ready for config at http://"); + print(WiFi.softAPIP()); + println("/"); } } /** Handle root or redirect to captive portal */ void accesspoint::handleRoot() { - Serial.println("Serving / over http..."); + 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. - server.sendContent( - "" - "

HELLO WORLD!!

" - ); - server.sendContent( - "

You may want to config the wifi connection.

" - "" - ); + String html = String("\r\n\r\n\r\n\t\r\n\r\n\r\n\r\n\t
\r\n\t\t
\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

AMS Meter

\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
\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\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); + } +} + bool accesspoint::loop() { if (isActivated) { @@ -97,17 +163,17 @@ bool accesspoint::loop() { size_t accesspoint::print(const char* text) { - if (this->debugger) this->debugger->print(text); + if (debugger) debugger->print(text); } size_t accesspoint::println(const char* text) { - if (this->debugger) this->debugger->println(text); + if (debugger) debugger->println(text); } size_t accesspoint::print(const Printable& data) { - if (this->debugger) this->debugger->print(data); + if (debugger) debugger->print(data); } size_t accesspoint::println(const Printable& data) { - if (this->debugger) this->debugger->println(data); + if (debugger) debugger->println(data); } diff --git a/Code/Arduino/AmsToMqttBridge/accesspoint.h b/Code/Arduino/AmsToMqttBridge/accesspoint.h index 0a4c18f5..527a90d6 100644 --- a/Code/Arduino/AmsToMqttBridge/accesspoint.h +++ b/Code/Arduino/AmsToMqttBridge/accesspoint.h @@ -18,26 +18,28 @@ class accesspoint { public: void setup(int accessPointButtonPin, Stream& debugger); bool loop(); + bool hasConfig(); + configuration config; + bool isActivated = false; private: const char* AP_SSID = "AMS2MQTT"; - configuration config; - bool isActivated = false; - Stream* debugger = NULL; - // DNS server const byte DNS_PORT = 53; DNSServer dnsServer; - size_t print(const char* text); - size_t println(const char* text); - size_t print(const Printable& data); - size_t println(const Printable& data); + 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 index 9ea14acd..6f0013ef 100644 --- a/Code/Arduino/AmsToMqttBridge/configuration.cpp +++ b/Code/Arduino/AmsToMqttBridge/configuration.cpp @@ -6,11 +6,11 @@ bool configuration::hasConfig() { - bool has = false; + bool hasConfig = false; EEPROM.begin(EEPROM_SIZE); - has = EEPROM.read(EEPROM_CONFIG_ADDRESS) == EEPROM_CHECK_SUM; + hasConfig = EEPROM.read(EEPROM_CONFIG_ADDRESS) == EEPROM_CHECK_SUM; EEPROM.end(); - return has; + return hasConfig; } bool configuration::save() @@ -23,6 +23,7 @@ bool configuration::save() address += saveString(address, ssid); address += saveString(address, ssidPassword); + address += saveByte(address, meterType); address += saveString(address, mqtt); address += saveInt(address, mqttPort); address += saveString(address, mqttClientID); @@ -37,10 +38,10 @@ bool configuration::save() else address += saveBool(address, false); - bool vRet = EEPROM.commit(); + bool success = EEPROM.commit(); EEPROM.end(); - return vRet; + return success; } @@ -56,6 +57,7 @@ bool configuration::load() address += readString(address, &ssid); address += readString(address, &ssidPassword); + address += readByte(address, &meterType); address += readString(address, &mqtt); address += readInt(address, &mqttPort); address += readString(address, &mqttClientID); @@ -82,6 +84,7 @@ bool configuration::load() { 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(); @@ -99,45 +102,55 @@ bool configuration::isSecure() return (mqttUser != 0) && (String(mqttUser).length() > 0); } -int configuration::readInt(int pAddress, int *pValue) +int configuration::readInt(int address, int *value) { - int lower = EEPROM.read(pAddress); - int higher = EEPROM.read(pAddress + 1); - *pValue = lower + (higher << 8); + int lower = EEPROM.read(address); + int higher = EEPROM.read(address + 1); + *value = lower + (higher << 8); return 2; } -int configuration::saveInt(int pAddress, int pValue) +int configuration::saveInt(int address, int value) { - byte lowByte = pValue & 0xFF; - byte highByte = ((pValue >> 8) & 0xFF); + byte lowByte = value & 0xFF; + byte highByte = ((value >> 8) & 0xFF); - EEPROM.write(pAddress, lowByte); - EEPROM.write(pAddress + 1, highByte); + EEPROM.write(address, lowByte); + EEPROM.write(address + 1, highByte); return 2; } -int configuration::readBool(int pAddress, bool *pValue) +int configuration::readBool(int address, bool *value) { - byte y = EEPROM.read(pAddress); - *pValue = (bool)y; - //Serial.printf("Read bool as %#x [%s]\r\n", y, (*pValue ? "true" : "false")); + byte y = EEPROM.read(address); + *value = (bool)y; return 1; } -int configuration::saveBool(int pAddress, bool pValue) +int configuration::saveBool(int address, bool value) { - byte y = (byte)pValue; - //Serial.printf("Writing bool as %#x [%s]\r\n", y, (pValue ? "true" : "false")); - EEPROM.write(pAddress, y); + 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; @@ -152,6 +165,7 @@ void configuration::print(Stream& serial) 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); @@ -167,8 +181,6 @@ void configuration::print(Stream& serial) serial.println("-----------------------------------------------"); } - - template int configuration::writeAnything(int ee, const T& value) { const byte* p = (const byte*)(const void*)&value; @@ -190,38 +202,25 @@ template int configuration::readAnything(int ee, T& value) int configuration::readString(int pAddress, char* pString[]) { int address = 0; - - byte vLength = EEPROM.read(pAddress + address); + byte length = EEPROM.read(pAddress + address); address++; - //Serial.print("Found length of string: "); - //Serial.println(vLength); - - char* buffer = new char[vLength]; - for (int i = 0; i int writeAnything(int ee, const T& value); 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(""); } From d5a519a182d7ecfcb1aa031cd11035c9b0594507 Mon Sep 17 00:00:00 2001 From: Roar Fredriksen Date: Tue, 20 Mar 2018 21:02:12 +0100 Subject: [PATCH 4/4] Working sample app --- .../AmsToMqttBridge/AmsToMqttBridge.ino | 609 ++++++++++-------- Code/Arduino/AmsToMqttBridge/accesspoint.cpp | 33 +- 2 files changed, 357 insertions(+), 285 deletions(-) diff --git a/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.ino b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.ino index b20d2b75..dbb48bb8 100644 --- a/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.ino +++ b/Code/Arduino/AmsToMqttBridge/AmsToMqttBridge.ino @@ -9,353 +9,408 @@ #include #include #include -#include "configuration.h" -#include "accesspoint.h" #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; -HardwareSerial* debugger; -// The HAN Port reader +// 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; - // Setup serial port for debugging - debugger->begin(2400, SERIAL_8E1); - while (!&debugger); - debugger->println("Started..."); - + 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(2, OUTPUT); - digitalWrite(2, LOW); + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, LOW); // Initialize the AP ap.setup(0, Serial); // Turn off the blue LED - digitalWrite(2, HIGH); + digitalWrite(LED_PIN, HIGH); if (!ap.isActivated) { setupWiFi(); hanReader.setup(&Serial, 2400, SERIAL_8E1, debugger); - hanReader.compensateFor09HeaderBug = (ap.config.meterType == 1); // To compensate for the known Kaifa bug + + // Compensate for the known Kaifa bug + hanReader.compensateFor09HeaderBug = (ap.config.meterType == 1); } } -void setupWiFi() -{ - if (ap.config.isSecure()) - client = new WiFiClientSecure(); - else - client = new WiFiClient(); - - mqtt = PubSubClient(*client); - - WiFi.enableAP(false); - WiFi.begin(ap.config.ssid, ap.config.ssidPassword); - mqtt.setServer(ap.config.mqtt, ap.config.mqttPort); - - //std::function vCallback = MakeDelegate(this, xnsClientClass::mqttMessageReceived); - mqtt.setCallback(mqttMessageReceived); - MQTT_connect(); - - sendMqttData("Connected!"); -} - -void mqttMessageReceived(char* topic, unsigned char* payload, unsigned int length) -{ - // make it a null-terminated string - char message[1000]; - for (int i = 0; i < length; i++) - message[i] = payload[i]; - message[length] = 0; - - debugger->println("Incoming MQTT message:"); - debugger->print("["); - debugger->print(topic); - debugger->print("] "); - debugger->println(message); -} - // the loop function runs over and over again until power down or reset -void loop() +void loop() { - // Only do normal stupp if we're not booted as AP + // 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 { - debugTemperature(); + // Read data from the HAN port readHanPort(); } } -} -void debugTemperature() -{ - if (lastTempDebug + 5000 < millis()) + else { - lastTempDebug = millis(); - tempSensor.requestTemperatures(); - float temperature = tempSensor.getTempCByIndex(0); - debugger->print("Temperature is "); - debugger->print(temperature); - debugger->println(" degrees"); - } -} -void readHanPort() -{ - switch (ap.config.meterType) - { - case 1: // Kaifa - readHanPort_Kaifa(); - break; - case 2: // Kamstrup - readHanPort_Kamstrup(); - break; - case 3: // Aidon - readHanPort_Aidon(); - break; - default: - debugger->print("Meter type "); - debugger->print(ap.config.meterType, HEX); - debugger->println(" is unknown"); - delay(10000); - break; + // Continously flash the blue LED when AP mode + if (millis() / 1000 % 2 == 0) + digitalWrite(LED_PIN, LOW); + else + digitalWrite(LED_PIN, HIGH); } } -void readHanPort_Aidon() +void setupWiFi() { - debugger->println("Meter type Aidon is not yet implemented"); + // 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() +void readHanPort_Kamstrup(int listSize) { - if (hanReader.read()) + // Only care for the ACtive Power Imported, which is found in the first list + if (listSize == (int)Kamstrup::List1 || listSize == (int)Kamstrup::List2) { - // Get the list identifier - int listSize = hanReader.getListSize(); - - // 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) { - 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 - root.printTo(Serial1); - Serial1.println(); - - // Publish the json to the MQTT server - char msg[1024]; - root.printTo(msg, 1024); - mqtt.publish(ap.config.mqttPublishTopic, msg); + 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() { - if (hanReader.read()) +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) { - // Get the list identifier - int listSize = hanReader.getListSize(); - - // 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 (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; - 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)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 - root.printTo(Serial1); - Serial1.println(); - - // Publish the json to the MQTT server - char msg[1024]; - root.printTo(msg, 1024); - mqtt.publish("sensors/out/espdebugger", msg); + 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(); if (debugger) debugger->println(); - if (debugger) debugger->print("Connecting to WiFi network "); - if (debugger) debugger->println(ap.config.ssid); + 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) @@ -381,11 +436,17 @@ void MQTT_connect() { 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); @@ -394,35 +455,47 @@ void MQTT_connect() { } else { - if (debugger) debugger->print("."); - if (debugger) debugger->print("failed, mqtt.state() = "); - if (debugger) debugger->print(mqtt.state()); - if (debugger) debugger->println(" trying again in 5 seconds"); + 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(); } - StaticJsonBuffer<500> jsonBuffer; + // Build a json with the message in a "data" attribute + DynamicJsonBuffer jsonBuffer; JsonObject& json = jsonBuffer.createObject(); json["id"] = WiFi.macAddress(); - json["up"] = millis() / 1000; + 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/accesspoint.cpp b/Code/Arduino/AmsToMqttBridge/accesspoint.cpp index a1488402..d45e4475 100644 --- a/Code/Arduino/AmsToMqttBridge/accesspoint.cpp +++ b/Code/Arduino/AmsToMqttBridge/accesspoint.cpp @@ -71,6 +71,21 @@ void accesspoint::setup(int accessPointButtonPin, Stream& debugger) } } +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..."); @@ -80,7 +95,7 @@ void accesspoint::handleRoot() { 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\t\t
\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

AMS Meter

\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
\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\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"); + 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 } @@ -145,22 +160,6 @@ void accesspoint::handleSave() { } } -bool accesspoint::loop() { - if (isActivated) - { - //DNS - dnsServer.processNextRequest(); - //HTTP - server.handleClient(); - return true; - } - else - { - return false; - } -} - - size_t accesspoint::print(const char* text) { if (debugger) debugger->print(text);