From b38ec2f25e649e8f644bc1f534d3da1786213763 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 1 Aug 2024 22:13:52 +0200 Subject: [PATCH] support Unity testing --- lib_standalone/AsyncJson.h | 91 ++++--- lib_standalone/ESPAsyncWebServer.h | 35 ++- {test => mock-api}/api_test.http | 0 {test => mock-api}/api_test.sh | 0 platformio.ini | 70 ++++- src/emsesp.cpp | 7 + src/test/test.cpp | 14 +- src/web/WebAPIService.cpp | 25 +- src/web/WebAPIService.h | 8 +- test/README.txt | 6 - .../emsesp_customizations.json | 28 -- .../emsesp_entities.json | 44 --- .../emsesp_schedule.json | 16 -- .../emsesp_settings.json | 122 --------- test/test_api/test_api.cpp | 166 ++++++++++++ test/unity_config.h | 251 ++++++++++++++++++ 16 files changed, 598 insertions(+), 285 deletions(-) rename {test => mock-api}/api_test.http (100%) rename {test => mock-api}/api_test.sh (100%) delete mode 100644 test/README.txt delete mode 100644 test/standalone_file_export/emsesp_customizations.json delete mode 100644 test/standalone_file_export/emsesp_entities.json delete mode 100644 test/standalone_file_export/emsesp_schedule.json delete mode 100644 test/standalone_file_export/emsesp_settings.json create mode 100644 test/test_api/test_api.cpp create mode 100644 test/unity_config.h diff --git a/lib_standalone/AsyncJson.h b/lib_standalone/AsyncJson.h index e3af4f7a8..21d1db552 100644 --- a/lib_standalone/AsyncJson.h +++ b/lib_standalone/AsyncJson.h @@ -42,64 +42,66 @@ class ChunkPrint : public Print { } }; -class PrettyAsyncJsonResponse { - protected: - JsonDocument _jsonBuffer; +// class PrettyAsyncJsonResponse { +// protected: +// JsonDocument _jsonBuffer; - JsonVariant _root; - bool _isValid; +// JsonVariant _root; +// bool _isValid; - public: - PrettyAsyncJsonResponse(bool isArray = false) - : _isValid{false} { - if (isArray) - _root = _jsonBuffer.to(); - else - _root = _jsonBuffer.add(); - } +// public: +// PrettyAsyncJsonResponse(bool isArray = false) +// : _isValid{false} { +// if (isArray) +// _root = _jsonBuffer.to(); +// else +// _root = _jsonBuffer.add(); +// } - ~PrettyAsyncJsonResponse() { - } +// ~PrettyAsyncJsonResponse() { +// } - JsonVariant getRoot() { - return _root; - } +// JsonVariant getRoot() { +// return _root; +// } - bool _sourceValid() const { - return _isValid; - } +// bool _sourceValid() const { +// return _isValid; +// } - size_t setLength() { - return 0; - } +// size_t setLength() { +// return 0; +// } - void setContentType(const char * s) { - } +// void setContentType(const char * s) { +// } - size_t getSize() { - return _jsonBuffer.size(); - } +// size_t getSize() { +// return _jsonBuffer.size(); +// } - size_t _fillBuffer(uint8_t * data, size_t len) { - return len; - } +// size_t _fillBuffer(uint8_t * data, size_t len) { +// return len; +// } - void setCode(uint16_t) { - } -}; +// void setCode(uint16_t) { +// } +// }; class AsyncJsonResponse { protected: JsonDocument _jsonBuffer; - - JsonVariant _root; - bool _isValid; - bool _isMsgPack; + JsonVariant _root; + bool _isValid; + bool _isMsgPack; + int _code; + size_t _contentLength; public: AsyncJsonResponse(bool isArray = false, bool isMsgPack = false) : _isValid{false} , _isMsgPack{isMsgPack} { + _code = 200; if (isArray) _root = _jsonBuffer.to(); else @@ -118,7 +120,12 @@ class AsyncJsonResponse { } size_t setLength() { - return 0; + _contentLength = _isMsgPack ? measureMsgPack(_root) : measureJson(_root); + + if (_contentLength) { + _isValid = true; + } + return _contentLength; } size_t getSize() { @@ -126,10 +133,12 @@ class AsyncJsonResponse { } size_t _fillBuffer(uint8_t * data, size_t len) { + // _isMsgPack ? serializeMsgPack(_root, data) : serializeJson(_root, data); return len; } - void setCode(uint16_t) { + void setCode(uint16_t c) { + _code = c; } void setContentType(const char * s) { diff --git a/lib_standalone/ESPAsyncWebServer.h b/lib_standalone/ESPAsyncWebServer.h index 57e0d7c08..69f25b43e 100644 --- a/lib_standalone/ESPAsyncWebServer.h +++ b/lib_standalone/ESPAsyncWebServer.h @@ -11,8 +11,8 @@ class AsyncWebServer; class AsyncWebServerRequest; class AsyncWebServerResponse; class AsyncJsonResponse; -class PrettyAsyncJsonResponse; -class MsgpackAsyncJsonResponse; +// class PrettyAsyncJsonResponse; +// class MsgpackAsyncJsonResponse; class AsyncEventSource; class AsyncWebParameter { @@ -76,9 +76,9 @@ class AsyncWebServerRequest { public: void * _tempObject; - AsyncWebServerRequest(AsyncWebServer *, AsyncClient *){}; - AsyncWebServerRequest(){}; - ~AsyncWebServerRequest(){}; + AsyncWebServerRequest(AsyncWebServer *, AsyncClient *) {}; + AsyncWebServerRequest() {}; + ~AsyncWebServerRequest() {}; AsyncClient * client() { return _client; @@ -99,9 +99,11 @@ class AsyncWebServerRequest { } void send(AsyncWebServerResponse * response) {}; + void send(AsyncJsonResponse * response) {}; - void send(PrettyAsyncJsonResponse * response) {}; - void send(MsgpackAsyncJsonResponse * response) {}; + + // void send(PrettyAsyncJsonResponse * response) {}; + // void send(MsgpackAsyncJsonResponse * response) {}; void send(int code, const String & contentType = String(), const String & content = String()) {}; void send(int code, const String & contentType, const __FlashStringHelper *) {}; @@ -205,6 +207,17 @@ class AsyncWebHandler { }; class AsyncWebServerResponse { + protected: + int _code; + String _contentType; + size_t _contentLength; + bool _sendContentLength; + bool _chunked; + size_t _headLength; + size_t _sentLength; + size_t _ackedLength; + size_t _writtenLength; + public: AsyncWebServerResponse(); virtual ~AsyncWebServerResponse(); @@ -221,9 +234,9 @@ class AsyncWebServer { public: AsyncWebServer(uint16_t port) - : _server(port){}; + : _server(port) {}; - ~AsyncWebServer(){}; + ~AsyncWebServer() {}; void begin() {}; void end(); @@ -239,8 +252,8 @@ class AsyncWebServer { class AsyncEventSource : public AsyncWebHandler { public: - AsyncEventSource(const String & url){}; - ~AsyncEventSource(){}; + AsyncEventSource(const String & url) {}; + ~AsyncEventSource() {}; size_t count() const { return 1; diff --git a/test/api_test.http b/mock-api/api_test.http similarity index 100% rename from test/api_test.http rename to mock-api/api_test.http diff --git a/test/api_test.sh b/mock-api/api_test.sh similarity index 100% rename from test/api_test.sh rename to mock-api/api_test.sh diff --git a/platformio.ini b/platformio.ini index 97f4d732a..349e53ef3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,7 +30,7 @@ build_flags = -D ONEWIRE_CRC16=0 -D CONFIG_ETH_ENABLED -D CONFIG_UART_ISR_IN_IRAM - -D CONFIG_ASYNC_TCP_STACK_SIZE=5120 + -D CONFIG_ASYNC_TCP_STACK_SIZE=6144 -D CONFIG_ASYNC_TCP_QUEUE=32 -D CONFIG_ASYNC_TCP_TASK_PRIORITY=10 @@ -38,8 +38,7 @@ unbuild_flags = ${common.core_unbuild_flags} [espressi32_base] -; 6.7.0 = Arduino v2.0.16 (based on IDF v4.4.7). See https://github.com/platformio/platform-espressif32/releases/tag/v6.7.0 -platform = espressif32@6.7.0 +platform = espressif32@6.8.0 framework = arduino board_build.filesystem = littlefs build_flags = @@ -210,15 +209,30 @@ build_flags = ${common.build_flags} build_unflags = ${common.unbuild_flags} -; to build and run: pio run -e native -t exec +; +; Building and testing natively, standalone without an ESP32. +; See https://docs.platformio.org/en/latest/platforms/native.html +; +; It will generate an executbale which when run will show the EMS-ESP Console where you can run tests using the `test` command. +; +; to build and run directly on linux: pio run -e native -t exec +; +; to build and run on Windows: +; - For the first time, install Msys2 (https://www.msys2.org/) and the GCC compiler with `run pacman -S mingw-w64-ucrt-x86_64-gcc` +; - Then, build with `pio run -e native` to create the program.exe file +; - run by calling the executable from the Mysys shell e.g. `C:/msys64/msys2_shell.cmd -defterm -here -no-start -ucrt64 -c /d/dev/proddy/EMS-ESP32/.pio/build/native/program.exe` +; +; The shell is needed to simulate the Winsock serial port. +; You can also integrate into Windows Terminal. See https://www.msys2.org/docs/terminals/ +; [env:native] platform = native -extra_scripts = pre:scripts/rename_prog.py build_flags = -DARDUINOJSON_ENABLE_ARDUINO_STRING=1 -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_TEST -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.7.0-dev.0\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\" - -std=gnu++14 -Og -ggdb -D__linux__ + -std=gnu++14 -Og -ggdb +build_type = debug build_src_flags = -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare -Wno-missing-braces @@ -245,3 +259,47 @@ build_src_filter = lib_compat_mode = off lib_ldf_mode = off lib_ignore = Module EMS-ESP-Modules + +; unit tests +; pio run -e native-test -t exec +[env:native-test] +platform = native +test_build_src = true +build_flags = + -DARDUINOJSON_ENABLE_ARDUINO_STRING=1 + -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_TEST -DUNITY_INCLUDE_CONFIG_H + -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.7.0-dev.0\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\" + -std=gnu++14 -Og -ggdb + -lgcov --coverage +build_type = debug +build_src_flags = + -Wall -Wextra -Werror + -Wno-unused-parameter -Wno-sign-compare -Wno-missing-braces + -I./lib_standalone + -I./lib/uuid-common/src + -I./lib/uuid-console/src + -I./lib/uuid-log/src + -I./lib/semver + -I./lib/PButton + -I./lib/ArduinoJson + -I./lib/espMqttClient/src + -I./lib/espMqttClient/src/Transport + -I./test/api + -I./test +build_src_filter = + +<*> + - + +<../test> + +<../lib_standalone> + +<../lib/uuid-common> + +<../lib/uuid-console> + +<../lib/uuid-log> + +<../lib/semver> + +<../lib/PButton> + +<../lib/espMqttClient/src> + +<../lib/espMqttClient/src/Transport> +lib_ldf_mode = off +lib_deps = Unity +lib_ignore = Module EMS-ESP-Modules +test_testing_command = + ${platformio.build_dir}/${this.__env__}/program diff --git a/src/emsesp.cpp b/src/emsesp.cpp index ea7e43ba7..45b8dfa3d 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -1583,11 +1583,16 @@ EMSESP::EMSESP() // start all the core services // the services must be loaded in the correct order void EMSESP::start() { +// don't need shell if running unit tests +#if !defined(UNITY_INCLUDE_CONFIG_H) + serial_console_.begin(SERIAL_CONSOLE_BAUD_RATE); shell_ = std::make_shared(*this, serial_console_, true); shell_->maximum_log_messages(100); + shell_->start(); + #if defined(EMSESP_DEBUG) shell_->log_level(uuid::log::Level::DEBUG); #else @@ -1598,6 +1603,8 @@ void EMSESP::start() { shell_->add_flags(CommandFlags::ADMIN); // always start in su/admin mode when running tests #endif +#endif + // start the file system #ifndef EMSESP_STANDALONE if (!LittleFS.begin(true)) { diff --git a/src/test/test.cpp b/src/test/test.cpp index 5cf7bf92c..6e21450c1 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -265,6 +265,8 @@ bool Test::test(const std::string & cmd, int8_t id1, int8_t id2) { // These next tests are run from the Consol via the test command, so inherit the Shell void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const std::string & id1_s, const std::string & id2_s) { + bool ok = false; // default tests fail + shell.add_flags(CommandFlags::ADMIN); // switch to su // init stuff @@ -297,8 +299,6 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const id2 = Helpers::atoint(id2_s.c_str()); } - bool ok = false; - // e.g. "test add 0x10 172" if (command == "add") { if (id1 == -1 || id2 == -1) { @@ -638,11 +638,11 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const Serial.print(COLOR_BRIGHT_MAGENTA); serializeJson(doc, Serial); Serial.print(COLOR_RESET); - Serial.println(); - Serial.print(" measureMsgPack="); - Serial.print(measureMsgPack(doc)); - Serial.print(" measureJson="); - Serial.print(measureJson(doc)); + // Serial.println(); + // Serial.print(" measureMsgPack="); + // Serial.print(measureMsgPack(doc)); + // Serial.print(" measureJson="); + // Serial.print(measureJson(doc)); Serial.println(" **"); } } diff --git a/src/web/WebAPIService.cpp b/src/web/WebAPIService.cpp index 5880f4c98..cb55866a3 100644 --- a/src/web/WebAPIService.cpp +++ b/src/web/WebAPIService.cpp @@ -64,7 +64,7 @@ void WebAPIService::webAPIService(AsyncWebServerRequest * request, JsonVariant j } #ifdef EMSESP_TEST -// for test.cpp so we can invoke GETs to test the API +// for test.cpp and unit tests so we can invoke GETs to test the API void WebAPIService::webAPIService(AsyncWebServerRequest * request) { JsonDocument input_doc; parse(request, input_doc.to()); @@ -134,6 +134,10 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) { const char * api_data = output["api_data"]; if (api_data) { request->send(200, "text/plain; charset=utf-8", api_data); +#if defined(EMSESP_TEST) + // store the result so we can test with Unity later + storeResponse(output); +#endif #if defined(EMSESP_STANDALONE) Serial.printf("%sweb output: %s[%s] %s(200)%s ", COLOR_WHITE, COLOR_BRIGHT_CYAN, request->url().c_str(), COLOR_BRIGHT_GREEN, COLOR_MAGENTA); serializeJson(output, Serial); @@ -157,6 +161,10 @@ void WebAPIService::parse(AsyncWebServerRequest * request, JsonObject input) { request->send(response); api_count_++; +#if defined(EMSESP_TEST) + // store the result so we can test with Unity later + storeResponse(output); +#endif #if defined(EMSESP_STANDALONE) Serial.printf("%sweb output: %s[%s]", COLOR_WHITE, COLOR_BRIGHT_CYAN, request->url().c_str()); Serial.printf(" %s(%d)%s ", ret_codes[return_code] == 200 ? COLOR_BRIGHT_GREEN : COLOR_BRIGHT_RED, ret_codes[return_code], COLOR_YELLOW); @@ -221,4 +229,19 @@ void WebAPIService::getEntities(AsyncWebServerRequest * request) { request->send(response); } +#if defined(EMSESP_TEST) +// store the result so we can test with Unity later +static JsonDocument storeResponseDoc_; + +void WebAPIService::storeResponse(JsonObject response) { + storeResponseDoc_.clear(); // clear it, so can only recall once + storeResponseDoc_.add(response); // add the object to our doc +} +const char * WebAPIService::getResponse() { + static std::string buffer; + serializeJson(storeResponseDoc_, buffer); + return buffer.c_str(); +} +#endif + } // namespace emsesp diff --git a/src/web/WebAPIService.h b/src/web/WebAPIService.h index 2c0c3ef7c..e47e19232 100644 --- a/src/web/WebAPIService.h +++ b/src/web/WebAPIService.h @@ -34,9 +34,11 @@ class WebAPIService { void webAPIService(AsyncWebServerRequest * request, JsonVariant json); -#ifdef EMSESP_TEST - // for test.cpp - void webAPIService(AsyncWebServerRequest * request); +#if defined(EMSESP_TEST) + // for test.cpp and running unit tests + void webAPIService(AsyncWebServerRequest * request); + void storeResponse(JsonObject response); + const char * getResponse(); #endif static uint32_t api_count() { diff --git a/test/README.txt b/test/README.txt deleted file mode 100644 index 292cb501d..000000000 --- a/test/README.txt +++ /dev/null @@ -1,6 +0,0 @@ -This folder contains the default data used when testing in standalone mode, purely for reference purposes. - -It is used for simulation and testing and can be invoked by compiling with -DEMSESP_TEST and from the Console using the command `test general` or via an API call like http://ems-esp.local/api?device=system&cmd=test&data=general - -To run in standalone mode without an ESP32 microcontroller use `make run` or `pio run -e standalone -t exec` - diff --git a/test/standalone_file_export/emsesp_customizations.json b/test/standalone_file_export/emsesp_customizations.json deleted file mode 100644 index 34523dfc7..000000000 --- a/test/standalone_file_export/emsesp_customizations.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "type": "customizations", - "Customizations": { - "ts": [ - { - "id": "01_0203_0405_0607", - "name": "test_sensor1", - "offset": 0 - }, - { - "id": "0B_0C0D_0E0F_1011", - "name": "test_sensor2", - "offset": 4 - } - ], - "as": [], - "masked_entities": [ - { - "product_id": 123, - "device_id": 8, - "custom_name": "Trendline", - "entity_ids": [ - "08heatingactive|is my heating on?" - ] - } - ] - } -} \ No newline at end of file diff --git a/test/standalone_file_export/emsesp_entities.json b/test/standalone_file_export/emsesp_entities.json deleted file mode 100644 index d31abfe07..000000000 --- a/test/standalone_file_export/emsesp_entities.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "type": "entities", - "Entities": { - "entities": [ - { - "id": 0, - "ram": 0, - "device_id": 8, - "type_id": 24, - "offset": 0, - "factor": 1, - "name": "test_custom", - "uom": 1, - "value_type": 1, - "writeable": true - }, - { - "id": 1, - "ram": 0, - "device_id": 24, - "type_id": 677, - "offset": 3, - "factor": 1, - "name": "test_read_only", - "uom": 0, - "value_type": 2, - "writeable": false - }, - { - "id": 2, - "ram": 1, - "device_id": 0, - "type_id": 0, - "offset": 0, - "factor": 1, - "name": "test_ram", - "uom": 0, - "value_type": 8, - "writeable": true, - "value": "14" - } - ] - } -} \ No newline at end of file diff --git a/test/standalone_file_export/emsesp_schedule.json b/test/standalone_file_export/emsesp_schedule.json deleted file mode 100644 index 4292ab6a8..000000000 --- a/test/standalone_file_export/emsesp_schedule.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "type": "schedule", - "Schedule": { - "schedule": [ - { - "id": 0, - "active": true, - "flags": 1, - "time": "12:00", - "cmd": "system/fetch", - "value": "10", - "name": "test_scheduler" - } - ] - } -} \ No newline at end of file diff --git a/test/standalone_file_export/emsesp_settings.json b/test/standalone_file_export/emsesp_settings.json deleted file mode 100644 index 8d8d69f63..000000000 --- a/test/standalone_file_export/emsesp_settings.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "type": "settings", - "System": { - "version": "3.7.0" - }, - "Network": { - "ssid": "fake", - "bssid": "", - "password": "fake", - "hostname": "ems-esp", - "static_ip_config": false, - "bandwidth20": false, - "tx_power": 0, - "nosleep": false, - "enableMDNS": true, - "enableCORS": false, - "CORSOrigin": "*" - }, - "AP": { - "provision_mode": 1, - "ssid": "ems-esp", - "password": "ems-esp-neo", - "channel": 1, - "ssid_hidden": false, - "max_clients": 4, - "local_ip": "192.168.4.1", - "gateway_ip": "192.168.4.1", - "subnet_mask": "255.255.255.0" - }, - "MQTT": { - "enabled": true, - "host": "192.168.1.200", - "port": 1883, - "base": "ems-espS", - "username": "fake", - "password": "fake", - "client_id": "ems-esp", - "keep_alive": 60, - "clean_session": false, - "entity_format": 1, - "publish_time_boiler": 10, - "publish_time_thermostat": 10, - "publish_time_solar": 10, - "publish_time_mixer": 10, - "publish_time_other": 10, - "publish_time_sensor": 10, - "publish_time_heartbeat": 60, - "mqtt_qos": 0, - "mqtt_retain": false, - "ha_enabled": true, - "nested_format": 1, - "discovery_prefix": "homeassistant", - "discovery_type": 0, - "publish_single": false, - "publish_single2cmd": false, - "send_response": false - }, - "NTP": { - "enabled": false, - "server": "time.google.com", - "tz_label": "Europe/Amsterdam", - "tz_format": "CET-1CEST,M3.5.0,M10.5.0/3" - }, - "Security": { - "jwt_secret": "ems-esp-neo", - "users": [ - { - "username": "admin", - "password": "admin", - "admin": true - }, - { - "username": "guest", - "password": "guest", - "admin": false - } - ] - }, - "Settings": { - "version": "3.7.0", - "locale": "en", - "tx_mode": 1, - "ems_bus_id": 11, - "syslog_enabled": false, - "syslog_level": 3, - "trace_raw": false, - "syslog_mark_interval": 0, - "syslog_host": "", - "syslog_port": 514, - "boiler_heatingoff": false, - "shower_timer": false, - "shower_alert": false, - "shower_alert_coldshot": 10, - "shower_alert_trigger": 7, - "rx_gpio": 23, - "tx_gpio": 5, - "dallas_gpio": 18, - "dallas_parasite": false, - "led_gpio": 2, - "hide_led": false, - "low_clock": false, - "telnet_enabled": true, - "notoken_api": false, - "readonly_mode": false, - "analog_enabled": true, - "pbutton_gpio": 0, - "solar_maxflow": 30, - "board_profile": "S32", - "fahrenheit": false, - "bool_format": 1, - "bool_dashboard": 1, - "enum_format": 1, - "weblog_level": 6, - "weblog_buffer": 50, - "weblog_compact": true, - "phy_type": 0, - "eth_power": 0, - "eth_phy_addr": 0, - "eth_clock_mode": 0, - "platform": "ESP32" - } -} diff --git a/test/test_api/test_api.cpp b/test/test_api/test_api.cpp new file mode 100644 index 000000000..8b65a23cb --- /dev/null +++ b/test/test_api/test_api.cpp @@ -0,0 +1,166 @@ +#include +#include + +#include +#include "ESPAsyncWebServer.h" +#include "ESP8266React.h" +#include "web/WebAPIService.h" + +using namespace emsesp; + +AsyncWebServer * webServer; +ESP8266React * esp8266React; +WebAPIService * webAPIService; +AsyncWebServerRequest request; +EMSESP application; +FS dummyFS; + +// simulates a telegram straight from UART, but without the CRC which is added automatically +void uart_telegram(const std::vector & rx_data) { + uint8_t len = rx_data.size(); + uint8_t data[50]; + uint8_t i = 0; + while (len--) { + data[i] = rx_data[i]; + i++; + } + data[i] = EMSESP::rxservice_.calculate_crc(data, i); + EMSESP::incoming_telegram(data, i + 1); +} + +void uart_telegram(const char * rx_data) { + // since the telegram data is a const, make a copy. add 1 to grab the \0 EOS + char telegram[(EMS_MAX_TELEGRAM_LENGTH * 3) + 1]; + for (uint8_t i = 0; i < strlen(rx_data); i++) { + telegram[i] = rx_data[i]; + } + telegram[strlen(rx_data)] = '\0'; // make sure its terminated + + uint8_t count = 0; + char * p; + char value[10] = {0}; + + uint8_t data[EMS_MAX_TELEGRAM_LENGTH]; + + // get first value, which should be the src + if ((p = strtok(telegram, " ,"))) { // delimiter + strlcpy(value, p, 10); + data[0] = (uint8_t)strtol(value, 0, 16); + } + + // and iterate until end + while (p != 0) { + if ((p = strtok(nullptr, " ,"))) { + strlcpy(value, p, 10); + uint8_t val = (uint8_t)strtol(value, 0, 16); + data[++count] = val; + } + } + + if (count == 0) { + return; // nothing to send + } + + data[count + 1] = EMSESP::rxservice_.calculate_crc(data, count + 1); // add CRC + + EMSESP::incoming_telegram(data, count + 2); +} + +void add_device(uint8_t device_id, uint8_t product_id) { + uart_telegram({device_id, 0x0B, EMSdevice::EMS_TYPE_VERSION, 0, product_id, 1, 0}); +} + +void setUp() { + JsonDocument doc; + JsonVariant json; + + webServer = new AsyncWebServer(80); + esp8266React = new ESP8266React(webServer, &dummyFS); + webAPIService = new WebAPIService(webServer, esp8266React->getSecurityManager()); + + application.start(); // calls begin() + + EMSESP::webCustomEntityService.test(); // custom entities + EMSESP::webCustomizationService.test(); // set customizations - this will overwrite any settings in the FS + EMSESP::temperaturesensor_.test(); // add temperature sensors + EMSESP::webSchedulerService.test(); // run scheduler tests, and conditions + + // + // boiler + // + add_device(0x08, 123); // Nefit Trendline + + // UBAuptime + uart_telegram({0x08, 0x0B, 0x14, 00, 0x3C, 0x1F, 0xAC, 0x70}); + + // Boiler -> Me, UBAMonitorFast(0x18), telegram: 08 00 18 00 00 02 5A 73 3D 0A 10 65 40 02 1A 80 00 01 E1 01 76 0E 3D 48 00 C9 44 02 00 (#data=25) + uart_telegram({0x08, 0x00, 0x18, 0x00, 0x00, 0x02, 0x5A, 0x73, 0x3D, 0x0A, 0x10, 0x65, 0x40, 0x02, 0x1A, + 0x80, 0x00, 0x01, 0xE1, 0x01, 0x76, 0x0E, 0x3D, 0x48, 0x00, 0xC9, 0x44, 0x02, 0x00}); + + // Boiler -> Me, UBAParameterWW(0x33), telegram: 08 0B 33 00 08 FF 34 FB 00 28 00 00 46 00 FF FF 00 (#data=13) + uart_telegram({0x08, 0x0B, 0x33, 0x00, 0x08, 0xFF, 0x34, 0xFB, 0x00, 0x28, 0x00, 0x00, 0x46, 0x00, 0xFF, 0xFF, 0x00}); + + // + // thermostat + // + add_device(0x10, 192); // FW120 + + // HC1 - 3 + uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x6F, 0x03, 0x02, 0x00, 0xCD, 0x00, 0xE4}); + uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x70, 0x02, 0x01, 0x00, 0xCE, 0x00, 0xE5}); + uart_telegram({0x90, 0x00, 0xFF, 0x00, 0x00, 0x71, 0x01, 0x02, 0x00, 0xCF, 0x00, 0xE6}); + + // send the telegrams + EMSESP::rxservice_.loop(); +} + +void tearDown() { +} + +void get_url(const char * url) { + request.method(HTTP_GET); + request.url(url); + webAPIService->webAPIService(&request); +} + +void test1() { + get_url("/api/system"); + + // escape strings with https://dolitools.com/text-tools/escape-unescape-string/ + auto a = + "[{\"system\":{\"version\":\"3.7.0-dev.28\",\"uptime\":\"000+00:00:00.000\",\"uptimeSec\":0,\"resetReason\":\"Unknown / " + "Unknown\"},\"network\":{\"network\":\"WiFi\",\"hostname\":\"ems-esp\",\"RSSI\":-23,\"TxPowerSetting\":0,\"staticIP\":false,\"lowBandwidth\":false," + "\"disableSleep\":false,\"enableMDNS\":true,\"enableCORS\":false},\"ntp\":{},\"mqtt\":{\"MQTTStatus\":\"disconnected\",\"MQTTPublishes\":0," + "\"MQTTQueued\":0,\"MQTTPublishFails\":0,\"MQTTConnects\":1,\"enabled\":true,\"clientID\":\"ems-esp\",\"keepAlive\":60,\"cleanSession\":false," + "\"entityFormat\":1,\"base\":\"ems-esp\",\"discoveryPrefix\":\"homeassistant\",\"discoveryType\":0,\"nestedFormat\":1,\"haEnabled\":true,\"mqttQos\":0," + "\"mqttRetain\":false,\"publishTimeHeartbeat\":60,\"publishTimeBoiler\":10,\"publishTimeThermostat\":10,\"publishTimeSolar\":10,\"publishTimeMixer\":" + "10,\"publishTimeWater\":0,\"publishTimeOther\":10,\"publishTimeSensor\":10,\"publishSingle\":false,\"publish2command\":false,\"sendResponse\":false}," + "\"syslog\":{\"enabled\":false},\"sensor\":{\"temperatureSensors\":2,\"temperatureSensorReads\":0,\"temperatureSensorFails\":0,\"analogSensors\":2," + "\"analogSensorReads\":0,\"analogSensorFails\":0},\"api\":{\"APICalls\":0,\"APIFails\":0},\"bus\":{\"busStatus\":\"connected\",\"busProtocol\":" + "\"Buderus\",\"busTelegramsReceived\":8,\"busReads\":0,\"busWrites\":0,\"busIncompleteTelegrams\":0,\"busReadsFailed\":0,\"busWritesFailed\":0," + "\"busRxLineQuality\":100,\"busTxLineQuality\":100},\"settings\":{\"boardProfile\":\"S32\",\"locale\":\"en\",\"txMode\":8,\"emsBusID\":11," + "\"showerTimer\":false,\"showerMinDuration\":180,\"showerAlert\":false,\"hideLed\":false,\"noTokenApi\":false,\"readonlyMode\":false,\"fahrenheit\":" + "false,\"dallasParasite\":false,\"boolFormat\":1,\"boolDashboard\":1,\"enumFormat\":1,\"analogEnabled\":true,\"telnetEnabled\":true," + "\"maxWebLogBuffer\":50,\"webLogBuffer\":33,\"modbusEnabled\":false},\"devices\":[{\"type\":\"boiler\",\"name\":\"Custom " + "Name!!\",\"deviceID\":\"0x08\",\"productID\":123,\"brand\":\"\",\"version\":\"01.00\",\"entities\":37,\"handlersReceived\":\"0x18\"," + "\"handlersFetched\":\"0x14 0x33\",\"handlersPending\":\"0xBF 0x10 0x11 0xC2 0x15 0x1C 0x19 0x1A 0x35 0x34 0x2A 0xD1 0xE3 0xE4 0xE5 0xE9 0x2E " + "0x3B\"},{\"type\":\"thermostat\",\"name\":\"FW120\",\"deviceID\":\"0x10\",\"productID\":192,\"brand\":\"\",\"version\":\"01.00\",\"entities\":15," + "\"handlersReceived\":\"0x016F\",\"handlersFetched\":\"0x0170 0x0171\",\"handlersPending\":\"0xA3 0x06 0xA2 0x12 0x13 0x0172 0x0165 0x0168\"}]}]"; + + TEST_ASSERT_EQUAL_STRING(a, webAPIService->getResponse()); +} + +void test2() { + get_url("/api/custom"); + auto a = "[{\"test_custom\":0.00,\"test_read_only\":0.00,\"test_ram\":\"14\",\"seltemp\":\"14\"}]"; + TEST_ASSERT_EQUAL_STRING(a, webAPIService->getResponse()); +} + +int main() { + UNITY_BEGIN(); + + RUN_TEST(test1); + RUN_TEST(test2); + + return UNITY_END(); +} diff --git a/test/unity_config.h b/test/unity_config.h new file mode 100644 index 000000000..b7524f2d8 --- /dev/null +++ b/test/unity_config.h @@ -0,0 +1,251 @@ +/* ========================================================================= + Unity - A Test Framework for C + ThrowTheSwitch.org + Copyright (c) 2007-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +/* Unity Configuration + * As of May 11th, 2016 at ThrowTheSwitch/Unity commit 837c529 + * Update: December 29th, 2016 + * See Also: Unity/docs/UnityConfigurationGuide.pdf + * + * Unity is designed to run on almost anything that is targeted by a C compiler. + * It would be awesome if this could be done with zero configuration. While + * there are some targets that come close to this dream, it is sadly not + * universal. It is likely that you are going to need at least a couple of the + * configuration options described in this document. + * + * All of Unity's configuration options are `#defines`. Most of these are simple + * definitions. A couple are macros with arguments. They live inside the + * unity_internals.h header file. We don't necessarily recommend opening that + * file unless you really need to. That file is proof that a cross-platform + * library is challenging to build. From a more positive perspective, it is also + * proof that a great deal of complexity can be centralized primarily to one + * place in order to provide a more consistent and simple experience elsewhere. + * + * Using These Options + * It doesn't matter if you're using a target-specific compiler and a simulator + * or a native compiler. In either case, you've got a couple choices for + * configuring these options: + * + * 1. Because these options are specified via C defines, you can pass most of + * these options to your compiler through command line compiler flags. Even + * if you're using an embedded target that forces you to use their + * overbearing IDE for all configuration, there will be a place somewhere in + * your project to configure defines for your compiler. + * 2. You can create a custom `unity_config.h` configuration file (present in + * your toolchain's search paths). In this file, you will list definitions + * and macros specific to your target. All you must do is define + * `UNITY_INCLUDE_CONFIG_H` and Unity will rely on `unity_config.h` for any + * further definitions it may need. + */ + +#ifndef UNITY_CONFIG_H +#define UNITY_CONFIG_H + +/* ************************* AUTOMATIC INTEGER TYPES *************************** + * C's concept of an integer varies from target to target. The C Standard has + * rules about the `int` matching the register size of the target + * microprocessor. It has rules about the `int` and how its size relates to + * other integer types. An `int` on one target might be 16 bits while on another + * target it might be 64. There are more specific types in compilers compliant + * with C99 or later, but that's certainly not every compiler you are likely to + * encounter. Therefore, Unity has a number of features for helping to adjust + * itself to match your required integer sizes. It starts off by trying to do it + * automatically. + **************************************************************************** */ + +/* The first attempt to guess your types is to check `limits.h`. Some compilers + * that don't support `stdint.h` could include `limits.h`. If you don't + * want Unity to check this file, define this to make it skip the inclusion. + * Unity looks at UINT_MAX & ULONG_MAX, which were available since C89. + */ +/* #define UNITY_EXCLUDE_LIMITS_H */ + +/* The second thing that Unity does to guess your types is check `stdint.h`. + * This file defines `UINTPTR_MAX`, since C99, that Unity can make use of to + * learn about your system. It's possible you don't want it to do this or it's + * possible that your system doesn't support `stdint.h`. If that's the case, + * you're going to want to define this. That way, Unity will know to skip the + * inclusion of this file and you won't be left with a compiler error. + */ +/* #define UNITY_EXCLUDE_STDINT_H */ + +/* ********************** MANUAL INTEGER TYPE DEFINITION *********************** + * If you've disabled all of the automatic options above, you're going to have + * to do the configuration yourself. There are just a handful of defines that + * you are going to specify if you don't like the defaults. + **************************************************************************** */ + +/* Define this to be the number of bits an `int` takes up on your system. The + * default, if not auto-detected, is 32 bits. + * + * Example: + */ +/* #define UNITY_INT_WIDTH 16 */ + +/* Define this to be the number of bits a `long` takes up on your system. The + * default, if not autodetected, is 32 bits. This is used to figure out what + * kind of 64-bit support your system can handle. Does it need to specify a + * `long` or a `long long` to get a 64-bit value. On 16-bit systems, this option + * is going to be ignored. + * + * Example: + */ +/* #define UNITY_LONG_WIDTH 16 */ + +/* Define this to be the number of bits a pointer takes up on your system. The + * default, if not autodetected, is 32-bits. If you're getting ugly compiler + * warnings about casting from pointers, this is the one to look at. + * + * Example: + */ +/* #define UNITY_POINTER_WIDTH 64 */ + +/* Unity will automatically include 64-bit support if it auto-detects it, or if + * your `int`, `long`, or pointer widths are greater than 32-bits. Define this + * to enable 64-bit support if none of the other options already did it for you. + * There can be a significant size and speed impact to enabling 64-bit support + * on small targets, so don't define it if you don't need it. + */ +/* #define UNITY_INCLUDE_64 */ + + +/* *************************** FLOATING POINT TYPES **************************** + * In the embedded world, it's not uncommon for targets to have no support for + * floating point operations at all or to have support that is limited to only + * single precision. We are able to guess integer sizes on the fly because + * integers are always available in at least one size. Floating point, on the + * other hand, is sometimes not available at all. Trying to include `float.h` on + * these platforms would result in an error. This leaves manual configuration as + * the only option. + **************************************************************************** */ + +/* By default, Unity guesses that you will want single precision floating point + * support, but not double precision. It's easy to change either of these using + * the include and exclude options here. You may include neither, just float, + * or both, as suits your needs. + */ +/* #define UNITY_EXCLUDE_FLOAT */ +/* #define UNITY_INCLUDE_DOUBLE */ +/* #define UNITY_EXCLUDE_DOUBLE */ + +/* For features that are enabled, the following floating point options also + * become available. + */ + +/* Unity aims for as small of a footprint as possible and avoids most standard + * library calls (some embedded platforms don't have a standard library!). + * Because of this, its routines for printing integer values are minimalist and + * hand-coded. To keep Unity universal, though, we eventually chose to develop + * our own floating point print routines. Still, the display of floating point + * values during a failure are optional. By default, Unity will print the + * actual results of floating point assertion failures. So a failed assertion + * will produce a message like "Expected 4.0 Was 4.25". If you would like less + * verbose failure messages for floating point assertions, use this option to + * give a failure message `"Values Not Within Delta"` and trim the binary size. + */ +/* #define UNITY_EXCLUDE_FLOAT_PRINT */ + +/* If enabled, Unity assumes you want your `FLOAT` asserts to compare standard C + * floats. If your compiler supports a specialty floating point type, you can + * always override this behavior by using this definition. + * + * Example: + */ +/* #define UNITY_FLOAT_TYPE float16_t */ + +/* If enabled, Unity assumes you want your `DOUBLE` asserts to compare standard + * C doubles. If you would like to change this, you can specify something else + * by using this option. For example, defining `UNITY_DOUBLE_TYPE` to `long + * double` could enable gargantuan floating point types on your 64-bit processor + * instead of the standard `double`. + * + * Example: + */ +/* #define UNITY_DOUBLE_TYPE long double */ + +/* If you look up `UNITY_ASSERT_EQUAL_FLOAT` and `UNITY_ASSERT_EQUAL_DOUBLE` as + * documented in the Unity Assertion Guide, you will learn that they are not + * really asserting that two values are equal but rather that two values are + * "close enough" to equal. "Close enough" is controlled by these precision + * configuration options. If you are working with 32-bit floats and/or 64-bit + * doubles (the normal on most processors), you should have no need to change + * these options. They are both set to give you approximately 1 significant bit + * in either direction. The float precision is 0.00001 while the double is + * 10^-12. For further details on how this works, see the appendix of the Unity + * Assertion Guide. + * + * Example: + */ +/* #define UNITY_FLOAT_PRECISION 0.001f */ +/* #define UNITY_DOUBLE_PRECISION 0.001f */ + + +/* *************************** MISCELLANEOUS *********************************** + * Miscellaneous configuration options for Unity + **************************************************************************** */ + +/* Unity uses the stddef.h header included in the C standard library for the + * "NULL" macro. Define this in order to disable the include of stddef.h. If you + * do this, you have to make sure to provide your own "NULL" definition. + */ +/* #define UNITY_EXCLUDE_STDDEF_H */ + +/* Define this to enable the unity formatted print macro: + * "TEST_PRINTF" + */ +/* #define UNITY_INCLUDE_PRINT_FORMATTED */ + + +/* *************************** TOOLSET CUSTOMIZATION *************************** + * In addition to the options listed above, there are a number of other options + * which will come in handy to customize Unity's behavior for your specific + * toolchain. It is possible that you may not need to touch any of these but + * certain platforms, particularly those running in simulators, may need to jump + * through extra hoops to operate properly. These macros will help in those + * situations. + **************************************************************************** */ + +/* By default, Unity prints its results to `stdout` as it runs. This works + * perfectly fine in most situations where you are using a native compiler for + * testing. It works on some simulators as well so long as they have `stdout` + * routed back to the command line. There are times, however, where the + * simulator will lack support for dumping results or you will want to route + * results elsewhere for other reasons. In these cases, you should define the + * `UNITY_OUTPUT_CHAR` macro. This macro accepts a single character at a time + * (as an `int`, since this is the parameter type of the standard C `putchar` + * function most commonly used). You may replace this with whatever function + * call you like. + * + * Example: + * Say you are forced to run your test suite on an embedded processor with no + * `stdout` option. You decide to route your test result output to a custom + * serial `RS232_putc()` function you wrote like thus: + */ +/* #define UNITY_OUTPUT_CHAR(a) RS232_putc(a) */ +/* #define UNITY_OUTPUT_CHAR_HEADER_DECLARATION RS232_putc(int) */ +/* #define UNITY_OUTPUT_FLUSH() RS232_flush() */ +/* #define UNITY_OUTPUT_FLUSH_HEADER_DECLARATION RS232_flush(void) */ +/* #define UNITY_OUTPUT_START() RS232_config(115200,1,8,0) */ +/* #define UNITY_OUTPUT_COMPLETE() RS232_close() */ + +/* Some compilers require a custom attribute to be assigned to pointers, like + * `near` or `far`. In these cases, you can give Unity a safe default for these + * by defining this option with the attribute you would like. + * + * Example: + */ +/* #define UNITY_PTR_ATTRIBUTE __attribute__((far)) */ +/* #define UNITY_PTR_ATTRIBUTE near */ + +/* Print execution time of each test when executed in verbose mode + * + * Example: + * + * TEST - PASS (10 ms) + */ +/* #define UNITY_INCLUDE_EXEC_TIME */ + +#endif /* UNITY_CONFIG_H */ \ No newline at end of file