From bf873b67e50fbbda22b4b3c70a675955f5377de4 Mon Sep 17 00:00:00 2001
From: Thomas Popp <66408890+tspopp@users.noreply.github.com>
Date: Sat, 15 Jun 2024 21:44:44 +0200
Subject: [PATCH] feat(aquadbg): more troubleshooting tooling (#27)
---
TROUBLESHOOTING.md | 10 +-
tools/AquaDebug/.clang-format | 137 ++++++++++++++++++
tools/AquaDebug/.gitignore | 2 +
tools/AquaDebug/.idea/.gitignore | 8 +
tools/AquaDebug/.idea/codeStyles/Project.xml | 7 +
.../.idea/codeStyles/codeStyleConfig.xml | 5 +
tools/AquaDebug/.idea/misc.xml | 17 +++
tools/AquaDebug/.idea/vcs.xml | 7 +
tools/AquaDebug/README.md | 39 +++++
tools/AquaDebug/collect.py | 44 ++++++
.../AquaDebug/include/config/Configuration.h | 44 ++++++
.../include/config/ExampleConfiguration.h | 22 +++
tools/AquaDebug/include/handler/OTA.h | 19 +++
.../AquaDebug/include/mqtt/MQTTDefinitions.h | 15 ++
tools/AquaDebug/platformio.ini | 31 ++++
tools/AquaDebug/src/handler/OTA.cpp | 62 ++++++++
tools/AquaDebug/src/main.cpp | 119 +++++++++++++++
tools/AquaDebug/test/README | 11 ++
tools/AquaDebug/test/main.cpp | 40 +++++
19 files changed, 636 insertions(+), 3 deletions(-)
create mode 100644 tools/AquaDebug/.clang-format
create mode 100644 tools/AquaDebug/.gitignore
create mode 100644 tools/AquaDebug/.idea/.gitignore
create mode 100644 tools/AquaDebug/.idea/codeStyles/Project.xml
create mode 100644 tools/AquaDebug/.idea/codeStyles/codeStyleConfig.xml
create mode 100644 tools/AquaDebug/.idea/misc.xml
create mode 100644 tools/AquaDebug/.idea/vcs.xml
create mode 100644 tools/AquaDebug/README.md
create mode 100755 tools/AquaDebug/collect.py
create mode 100644 tools/AquaDebug/include/config/Configuration.h
create mode 100644 tools/AquaDebug/include/config/ExampleConfiguration.h
create mode 100644 tools/AquaDebug/include/handler/OTA.h
create mode 100644 tools/AquaDebug/include/mqtt/MQTTDefinitions.h
create mode 100644 tools/AquaDebug/platformio.ini
create mode 100644 tools/AquaDebug/src/handler/OTA.cpp
create mode 100644 tools/AquaDebug/src/main.cpp
create mode 100644 tools/AquaDebug/test/README
create mode 100644 tools/AquaDebug/test/main.cpp
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
index fddfcf0..806b934 100644
--- a/TROUBLESHOOTING.md
+++ b/TROUBLESHOOTING.md
@@ -84,7 +84,7 @@ flowchart TD
time. If this counter is rising, there might be connectivity issues or a different heatpump protocol using a
different CRC mechanism.
-- Both counter values `msgHandled` and `msgSent` shall be rising over time if the connection is good. If these values stay at `0` there are connectivity issues.
+- The value `msgHandled` shall be rising over time if the connection is good. If this value stays at `0` there are most likely connectivity issues or the heat pump protocol is not (yet) supported.
### Q: AquaMQTT is connected to MQTT, but I don't see any values from the heatpump?
@@ -108,7 +108,7 @@ It seems that depending on your heat-pump brand and model there are sometimes fo
### Q: Why do I need the HMI controller? Do I have to connect the HMI controller?
In both of AquaMQTT current implemented OperationModes `LISTENER` and `MITM` it is expected to have an HMI controller
-connected, since AquaMQTT is either just forwarding or altering the original messages emitted by the heat-pump
+connected, since AquaMQTT is either just listening or forwarding original or modified messages emitted by the heat-pump
controllers.
Technically it would be feasible to build another OperationMode without the need for the original HMI controller, but
@@ -146,4 +146,8 @@ There is a [python script](./tools) which subscribes all the debug topics and wr
*The python script has to be modified to connect to your mqtt broker, with your mqtt broker credentials etc.*
-Afterwards you can inspect changing values over time and identify attributes by making changes to your HMI controller. If you identifiy something new which is not documented within [PROTOCOL.md](./PROTOCOL.md) yet, please create an issue or even an PR.
\ No newline at end of file
+Afterwards you can inspect changing values over time and identify attributes by making changes to your HMI controller. If you identifiy something new which is not documented within [PROTOCOL.md](./PROTOCOL.md) yet, please create an issue or even an PR.
+
+### Q: I think my heat-pump uses an incompatible serial protocol? What can I do?
+
+If you already have the AquaMQTT Board installed and available, you can set it to LISTENER Mode and install [AquaDebug](./tools/AquaDebug/) to collect unmodified serial data traces from the heat-pump controller. If there are no other hardware and or connectivity related issues you should be able to identify the [heat-pump messages](./PROTOCOL.md) within the raw data. If the messages differ from the expected protocol, open up an github issue to discuss further.
diff --git a/tools/AquaDebug/.clang-format b/tools/AquaDebug/.clang-format
new file mode 100644
index 0000000..cb02385
--- /dev/null
+++ b/tools/AquaDebug/.clang-format
@@ -0,0 +1,137 @@
+# Configured for clang-format-9
+# If You want to test some setting, try https://zed0.co.uk/clang-format-configurator/
+# Integration helpers, see here: https://clang.llvm.org/docs/ClangFormat.html
+
+Language: Cpp
+BasedOnStyle: Google
+AccessModifierOffset: -4
+AllowAllConstructorInitializersOnNextLine: false
+AllowAllArgumentsOnNextLine : false
+AlignAfterOpenBracket: AlwaysBreak
+AlignConsecutiveAssignments: true
+AlignConsecutiveDeclarations: true
+AlignEscapedNewlines: Left
+AlignOperands: true
+AlignTrailingComments: true
+AlignConsecutiveMacros: true
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: true
+BinPackArguments: false
+BinPackParameters: false
+BreakBeforeBraces: Custom
+BraceWrapping:
+ AfterClass: true
+ AfterControlStatement: true
+ AfterEnum: true
+ AfterFunction: true
+ AfterNamespace: true
+ AfterObjCDeclaration: false
+ AfterStruct: true
+ AfterUnion: true
+ AfterExternBlock: false
+ BeforeCatch: true
+ BeforeElse: true
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: true
+ SplitEmptyNamespace: true
+ AfterCaseLabel: true
+BreakBeforeBinaryOperators: NonAssignment
+BreakBeforeInheritanceComma: false
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: BeforeComma
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+BreakInheritanceList: BeforeComma
+ColumnLimit: 119
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 8
+Cpp11BracedListStyle: false
+DerivePointerAlignment: false
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+ - std::for_each
+IncludeBlocks: Regroup
+IncludeCategories:
+
+ - Regex: '^<(gmock|gtest|Poco|rapidjson|eb)\/([A-Z0-9.\/-_])+'
+ Priority: 4
+
+ - Regex: '^<([A-Z0-9\/-_])+>'
+ Priority: 3
+
+ - Regex: '^<.*/.*\.hpp>'
+ Priority: 5
+
+ - Regex: '^<.*.h>'
+ Priority: 1
+
+ - Regex: '^<.*/.*\.h>'
+ Priority: 2
+
+ - Regex: '^".*/.*\.hpp"'
+ Priority: 6
+
+ - Regex: '^"([A-Z0-9.-_])+"'
+ Priority: 7
+
+IncludeIsMainRegex: '(_test)?$'
+IndentCaseLabels: true
+IndentPPDirectives: AfterHash
+IndentWidth: 4
+IndentWrappedFunctionNames: true
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: true
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 4
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: false
+PointerAlignment: Left
+RawStringFormats:
+ - Language: TextProto
+ BasedOnStyle: google
+ Delimiters: - pb
+ReflowComments: true
+SortIncludes: true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: true
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 2
+SpacesInAngles: false
+SpacesInContainerLiterals: false
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Cpp11
+TabWidth: 4
+UseTab: Never
+
+PenaltyBreakAssignment: 200
+PenaltyBreakBeforeFirstCallParameter: 1
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 200
diff --git a/tools/AquaDebug/.gitignore b/tools/AquaDebug/.gitignore
new file mode 100644
index 0000000..53ef685
--- /dev/null
+++ b/tools/AquaDebug/.gitignore
@@ -0,0 +1,2 @@
+.pio
+include/config/CustomConfiguration.h
\ No newline at end of file
diff --git a/tools/AquaDebug/.idea/.gitignore b/tools/AquaDebug/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/tools/AquaDebug/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/tools/AquaDebug/.idea/codeStyles/Project.xml b/tools/AquaDebug/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..f603881
--- /dev/null
+++ b/tools/AquaDebug/.idea/codeStyles/Project.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tools/AquaDebug/.idea/codeStyles/codeStyleConfig.xml b/tools/AquaDebug/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/tools/AquaDebug/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/tools/AquaDebug/.idea/misc.xml b/tools/AquaDebug/.idea/misc.xml
new file mode 100644
index 0000000..d858eb1
--- /dev/null
+++ b/tools/AquaDebug/.idea/misc.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tools/AquaDebug/.idea/vcs.xml b/tools/AquaDebug/.idea/vcs.xml
new file mode 100644
index 0000000..64713b8
--- /dev/null
+++ b/tools/AquaDebug/.idea/vcs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tools/AquaDebug/README.md b/tools/AquaDebug/README.md
new file mode 100644
index 0000000..951ce1c
--- /dev/null
+++ b/tools/AquaDebug/README.md
@@ -0,0 +1,39 @@
+# AquaDebug
+
+AquaDebug is a simple serial bus debugging application meant to be flashed on the AquaMQTT Board for troubleshooting the
+heat-pumps serial protocol.
+
+- AquaDebug is sniffing the serial protocol without altering or interpreting any data
+- AquaDebug expects the AquaMQTT Board to be configured as LISTENER
+- AquaDebug reads the heat-pumps serial protocol and provides raw data to MQTT
+- A python script running on any machine collects the data from MQTT and writes the data into files
+
+In case you suspect that your heat-pump is incompatible with the existing AquaMQTT implementation, due to a different
+protocol, CRC or any other reason, you may try AquaDebug and provide a trace from your heat-pump communication as an
+github issue.
+
+## Getting Started
+
+Refer to the [AquaMQTT README](./../../AquaMQTT/README.md) for installation and configuration instructions.
+
+## Debugging / Troubleshooting
+
+As soon as AquaDebug has been flashed, it will publish mqtt messages to `$prefix/aquadbg/debug` in case values have been
+read from the Serial port.
+
+These data is collected by a little python script and stored on your machine.
+
+### Create the python environment
+
+```
+python3 -m venv venv
+source venv/bin/activate
+pip install paho-mqtt
+```
+
+### Run the python script
+
+```
+source venv/bin/activate
+python3 collect.py
+```
\ No newline at end of file
diff --git a/tools/AquaDebug/collect.py b/tools/AquaDebug/collect.py
new file mode 100755
index 0000000..90874d3
--- /dev/null
+++ b/tools/AquaDebug/collect.py
@@ -0,0 +1,44 @@
+import os
+import time
+import datetime
+import paho.mqtt.client as mqtt
+
+last_payloads = {}
+
+def on_message(client, userdata, message):
+ topic = message.topic
+ payload = message.payload
+
+ hex_file_name = topic.replace('/', '_') + '_hex.csv'
+ dec_file_name = topic.replace('/', '_') + '_dec.csv'
+
+ hex_payload = payload.decode('utf-8')
+
+ if topic in last_payloads and last_payloads[topic] == hex_payload:
+ return
+
+ timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
+
+ with open(hex_file_name, 'a') as hex_file:
+ hex_file.write(timestamp + ',' + hex_payload + '\n')
+
+ dec_payload = ' '.join([str(int(payload[i:i+2], 16)) for i in range(0, len(payload), 2)])
+
+ with open(dec_file_name, 'a') as dec_file:
+ dec_file.write(timestamp + ',' + dec_payload + '\n')
+
+ last_payloads[topic] = hex_payload
+
+client = mqtt.Client()
+client.username_pw_set(username='your-broker-user', password='your-broker-pass')
+client.connect('your-broker-addr', 1883)
+
+client.subscribe('your-prefix/aquadbg/debug')
+
+client.on_message = on_message
+
+client.loop_start()
+
+while True:
+ time.sleep(1)
+
diff --git a/tools/AquaDebug/include/config/Configuration.h b/tools/AquaDebug/include/config/Configuration.h
new file mode 100644
index 0000000..79f7846
--- /dev/null
+++ b/tools/AquaDebug/include/config/Configuration.h
@@ -0,0 +1,44 @@
+#ifndef AQUAMQTT_CONFIGURATION_H
+#define AQUAMQTT_CONFIGURATION_H
+
+/**
+ * Possibility to include your own configuration file (added to .gitignore)
+ */
+//#define CUSTOM_CONFIGURATION
+
+#ifdef CUSTOM_CONFIGURATION
+# include "CustomConfiguration.h"
+#else
+# include "ExampleConfiguration.h"
+#endif
+
+namespace aquamqtt
+{
+namespace config
+{
+/**
+ * Defines the network name of your esp32 device in your network
+ */
+constexpr char networkName[] = "aquamqtt";
+
+/**
+ * Self explanatory internal settings: most probably you don't want to change them.
+ */
+constexpr uint8_t WATCHDOG_TIMEOUT_S = 60;
+constexpr uint8_t MQTT_MAX_TOPIC_SIZE = 80;
+constexpr uint8_t MQTT_MAX_PAYLOAD_SIZE = 255;
+
+/**
+ * Pin assignments for AquaMQTT Board Rev 1.0
+ */
+constexpr uint8_t GPIO_MAIN_RX = 2;
+constexpr uint8_t GPIO_MAIN_TX = 3;
+constexpr uint8_t GPIO_HMI_RX = 4;
+constexpr uint8_t GPIO_HMI_TX = 5;
+constexpr uint8_t GPIO_SDA_RTC = A4;
+constexpr uint8_t GPIO_SCL_RTC = A5;
+
+} // namespace config
+} // namespace aquamqtt
+
+#endif // AQUAMQTT_CONFIGURATION_H
diff --git a/tools/AquaDebug/include/config/ExampleConfiguration.h b/tools/AquaDebug/include/config/ExampleConfiguration.h
new file mode 100644
index 0000000..3f4302f
--- /dev/null
+++ b/tools/AquaDebug/include/config/ExampleConfiguration.h
@@ -0,0 +1,22 @@
+#ifndef AQUAMQTT_EXAMPLECONFIGURATION_H
+#define AQUAMQTT_EXAMPLECONFIGURATION_H
+
+namespace aquamqtt
+{
+namespace config
+{
+
+constexpr char ssid[] = "ExampleSSID";
+constexpr char psk[] = "ExamplePSK";
+constexpr char brokerAddr[] = "192.168.188.1";
+constexpr uint16_t brokerPort = 1883;
+constexpr char brokerClientId[] = "aquamqtt";
+// leave blank if your broker does not require a username/password
+constexpr char brokerUser[] = "";
+constexpr char brokerPassword[] = "";
+constexpr char mqttPrefix[] = "";
+
+} // namespace config
+} // namespace aquamqtt
+
+#endif // AQUAMQTT_EXAMPLECONFIGURATION_H
diff --git a/tools/AquaDebug/include/handler/OTA.h b/tools/AquaDebug/include/handler/OTA.h
new file mode 100644
index 0000000..6307a98
--- /dev/null
+++ b/tools/AquaDebug/include/handler/OTA.h
@@ -0,0 +1,19 @@
+#ifndef AQUAMQTT_OTA_H
+#define AQUAMQTT_OTA_H
+
+namespace aquamqtt
+{
+class OTAHandler
+{
+public:
+ OTAHandler() = default;
+
+ virtual ~OTAHandler() = default;
+
+ void setup();
+
+ void loop();
+};
+} // namespace aquamqtt
+
+#endif // AQUAMQTT_OTA_H
diff --git a/tools/AquaDebug/include/mqtt/MQTTDefinitions.h b/tools/AquaDebug/include/mqtt/MQTTDefinitions.h
new file mode 100644
index 0000000..d09d79c
--- /dev/null
+++ b/tools/AquaDebug/include/mqtt/MQTTDefinitions.h
@@ -0,0 +1,15 @@
+#ifndef AQUAMQTT_MQTTDEFINITIONS_H
+#define AQUAMQTT_MQTTDEFINITIONS_H
+
+namespace aquamqtt
+{
+namespace mqtt
+{
+
+constexpr char BASE_TOPIC[] = { "aquadbg/" };
+constexpr char DEBUG[] = { "debug" };
+
+} // namespace mqtt
+} // namespace aquamqtt
+
+#endif // AQUAMQTT_MQTTDEFINITIONS_H
diff --git a/tools/AquaDebug/platformio.ini b/tools/AquaDebug/platformio.ini
new file mode 100644
index 0000000..4e5914c
--- /dev/null
+++ b/tools/AquaDebug/platformio.ini
@@ -0,0 +1,31 @@
+; PlatformIO Project Configuration File
+;
+; Build options: build flags, source filter
+; Upload options: custom upload port, speed and extra flags
+; Library options: dependencies, extra library storages
+; Advanced options: extra scripting
+;
+; Please visit documentation for the other options and examples
+; https://docs.platformio.org/page/projectconf.html
+
+[env]
+test_framework = googletest
+
+[env:native]
+platform = native
+build_src_filter = src/**
+lib_deps =
+ googletest
+
+[env:arduino_nano_esp32]
+platform = espressif32
+board = arduino_nano_esp32
+framework = arduino
+test_framework = googletest
+build_flags = -std=c++11
+# uncomment the below lines to use over the air update
+# upload_protocol = espota
+# upload_port = 192.168.188.62
+lib_deps =
+ locoduino/RingBuffer
+ 256dpi/MQTT
diff --git a/tools/AquaDebug/src/handler/OTA.cpp b/tools/AquaDebug/src/handler/OTA.cpp
new file mode 100644
index 0000000..7d71e88
--- /dev/null
+++ b/tools/AquaDebug/src/handler/OTA.cpp
@@ -0,0 +1,62 @@
+#include "handler/OTA.h"
+
+#include
+
+#include "config/Configuration.h"
+
+namespace aquamqtt
+{
+
+void OTAHandler::setup() // NOLINT(*-convert-member-functions-to-static)
+{
+ // Port defaults to 3232
+ // ArduinoOTA.setPort(3232);
+
+ // Hostname defaults to esp3232-[MAC]
+ ArduinoOTA.setHostname(config::networkName);
+
+ // No authentication by default
+ // ArduinoOTA.setPassword("admin");
+
+ // Password can be set with it's md5 value as well
+ // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
+ // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");
+
+ ArduinoOTA
+ .onStart([]() {
+ String type;
+ if (ArduinoOTA.getCommand() == U_FLASH)
+ type = "sketch";
+ else // U_SPIFFS
+ type = "filesystem";
+
+ // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
+ Serial.println("Start updating " + type);
+ })
+ .onEnd([]() { Serial.println("\nEnd"); })
+ .onProgress([](unsigned int progress, unsigned int total) {
+ Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
+ })
+ .onError([](ota_error_t error) {
+ Serial.printf("Error[%u]: ", error);
+ if (error == OTA_AUTH_ERROR)
+ Serial.println("Auth Failed");
+ else if (error == OTA_BEGIN_ERROR)
+ Serial.println("Begin Failed");
+ else if (error == OTA_CONNECT_ERROR)
+ Serial.println("Connect Failed");
+ else if (error == OTA_RECEIVE_ERROR)
+ Serial.println("Receive Failed");
+ else if (error == OTA_END_ERROR)
+ Serial.println("End Failed");
+ });
+
+ ArduinoOTA.begin();
+}
+
+void OTAHandler::loop() // NOLINT(*-convert-member-functions-to-static)
+{
+ ArduinoOTA.handle();
+}
+
+} // namespace aquamqtt
diff --git a/tools/AquaDebug/src/main.cpp b/tools/AquaDebug/src/main.cpp
new file mode 100644
index 0000000..0044a0f
--- /dev/null
+++ b/tools/AquaDebug/src/main.cpp
@@ -0,0 +1,119 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "config/Configuration.h"
+#include "handler/OTA.h"
+#include "mqtt/MQTTDefinitions.h"
+
+using namespace aquamqtt;
+using namespace aquamqtt::config;
+using namespace aquamqtt::mqtt;
+
+constexpr uint16_t BUFFER_SIZE = 40;
+
+WiFiClient mWiFiClient;
+MQTTClient mMQTTClient(256);
+RingBuf mBuffer;
+OTAHandler otaHandler;
+uint8_t mTopicBuffer[config::MQTT_MAX_TOPIC_SIZE];
+uint8_t mPayloadBuffer[MQTT_MAX_PAYLOAD_SIZE];
+uint8_t mTempBuffer[BUFFER_SIZE];
+
+void toHexStr(uint8_t* data, uint8_t data_len, char* buffer)
+{
+ const size_t num_bytes = data_len / sizeof(uint8_t);
+ // char hex_str[num_bytes * 2 + 1];
+ for (size_t i = 0; i < num_bytes; i++)
+ {
+ sprintf(&buffer[i * 2], "%02X", data[i]);
+ }
+ buffer[num_bytes * 2] = '\0';
+}
+
+void wifiCallback(WiFiEvent_t event)
+{
+ switch (event)
+ {
+ case WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_CONNECTED:
+ Serial.println(F("ARDUINO_EVENT_WIFI_STA_CONNECTED"));
+ break;
+ case WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
+ Serial.println(F("ARDUINO_EVENT_WIFI_STA_DISCONNECTED"));
+ WiFi.setAutoReconnect(true);
+ break;
+ default:
+ break;
+ }
+}
+
+void loop()
+{
+ // watchdog
+ esp_task_wdt_reset();
+
+ // handle over-the-air module in main thread
+ otaHandler.loop();
+
+ // connect mqtt if not connected
+ if (!mMQTTClient.connected())
+ {
+ mMQTTClient.connect(
+ aquamqtt::config::brokerClientId,
+ strlen(aquamqtt::config::brokerUser) == 0 ? nullptr : aquamqtt::config::brokerUser,
+ strlen(aquamqtt::config::brokerPassword) == 0 ? nullptr : aquamqtt::config::brokerPassword);
+ }
+ else
+ {
+ mMQTTClient.loop();
+ }
+
+ // read serial into buffer
+ while (Serial2.available())
+ {
+ // push new element to the back
+ mBuffer.push(Serial2.read());
+
+ // if buffer is full, we emit everything to mqtt and clear the buffer
+ if (mBuffer.isFull())
+ {
+ for (int i = 0; i < BUFFER_SIZE; i++)
+ {
+ int retVal;
+ mBuffer.pop(retVal);
+ mTempBuffer[i] = retVal;
+ }
+ sprintf(reinterpret_cast(mTopicBuffer), "%s%s%s", config::mqttPrefix, BASE_TOPIC, DEBUG);
+ toHexStr(mTempBuffer, BUFFER_SIZE, reinterpret_cast(mPayloadBuffer));
+ mMQTTClient.publish(reinterpret_cast(mTopicBuffer), reinterpret_cast(mPayloadBuffer));
+ }
+ }
+}
+
+void setup()
+{
+ // limited serial output for debuggability
+ Serial.begin(9600);
+ Serial.println("REBOOT");
+
+ // initialize watchdog
+ esp_task_wdt_init(WATCHDOG_TIMEOUT_S, true);
+ esp_task_wdt_add(nullptr);
+
+ // connect to Wi-Fi
+ WiFiClass::mode(WIFI_STA);
+ WiFi.onEvent(wifiCallback);
+ WiFi.begin(aquamqtt::config::ssid, aquamqtt::config::psk);
+
+ // setup ota module
+ otaHandler.setup();
+
+ // setup mqtt client
+ mMQTTClient.begin(aquamqtt::config::brokerAddr, aquamqtt::config::brokerPort, mWiFiClient);
+
+ // setup serial port
+ Serial2.begin(9550, SERIAL_8N2, aquamqtt::config::GPIO_MAIN_RX, aquamqtt::config::GPIO_MAIN_TX);
+}
\ No newline at end of file
diff --git a/tools/AquaDebug/test/README b/tools/AquaDebug/test/README
new file mode 100644
index 0000000..9b1e87b
--- /dev/null
+++ b/tools/AquaDebug/test/README
@@ -0,0 +1,11 @@
+
+This directory is intended for PlatformIO Test Runner and project tests.
+
+Unit Testing is a software testing method by which individual units of
+source code, sets of one or more MCU program modules together with associated
+control data, usage procedures, and operating procedures, are tested to
+determine whether they are fit for use. Unit testing finds problems early
+in the development cycle.
+
+More information about PlatformIO Unit Testing:
+- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
diff --git a/tools/AquaDebug/test/main.cpp b/tools/AquaDebug/test/main.cpp
new file mode 100644
index 0000000..942f3dd
--- /dev/null
+++ b/tools/AquaDebug/test/main.cpp
@@ -0,0 +1,40 @@
+#include
+
+#if defined(ARDUINO)
+#include
+
+void setup()
+{
+ // should be the same value as for the `test_speed` option in "platformio.ini"
+ // default value is test_speed=115200
+ Serial.begin(115200);
+
+ ::testing::InitGoogleTest();
+ // if you plan to use GMock, replace the line above with
+ // ::testing::InitGoogleMock();
+}
+
+void loop()
+{
+ // Run tests
+ if (RUN_ALL_TESTS())
+ ;
+
+ // sleep for 1 sec
+ delay(1000);
+}
+
+#else
+int main(int argc, char **argv)
+{
+ ::testing::InitGoogleTest(&argc, argv);
+ // if you plan to use GMock, replace the line above with
+ // ::testing::InitGoogleMock(&argc, argv);
+
+ if (RUN_ALL_TESTS())
+ ;
+
+ // Always return zero-code and allow PlatformIO to parse results
+ return 0;
+}
+#endif
\ No newline at end of file