From 2216c566db122bec58d04bd3ab757bdd056bfe46 Mon Sep 17 00:00:00 2001 From: Sebastian Kopf Date: Fri, 16 May 2025 23:22:38 +0200 Subject: [PATCH 01/19] add oled testing framework --- .github/workflows/compile-oled.yaml | 40 ++++++++++++++++++++++++++++ .gitmodules | 9 +++++++ README.md | 8 +++--- lib/Adafruit_BusIO_RK | 1 + lib/Adafruit_GFX_RK | 1 + lib/Adafruit_SSD1306_RK | 1 + src/oled/oled_test.cpp | 41 +++++++++++++++++++++++++++++ src/oled/project.properties | 11 ++++++++ 8 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/compile-oled.yaml create mode 160000 lib/Adafruit_BusIO_RK create mode 160000 lib/Adafruit_GFX_RK create mode 160000 lib/Adafruit_SSD1306_RK create mode 100644 src/oled/oled_test.cpp create mode 100644 src/oled/project.properties diff --git a/.github/workflows/compile-oled.yaml b/.github/workflows/compile-oled.yaml new file mode 100644 index 0000000..50862e0 --- /dev/null +++ b/.github/workflows/compile-oled.yaml @@ -0,0 +1,40 @@ +# name of the job +name: Compile oled + +# specify which paths to watch for changes +on: + push: + paths: + - src/oled + - .github/workflows/compile.yaml + - .github/workflows/compile-oled.yaml + +# run compile via the compile.yaml +jobs: + compile: + strategy: + fail-fast: false + matrix: + # CHANGE program/lib/aux as needed + program: + - src: 'oled' + lib: 'Adafruit_SSD1306_RK/src Adafruit_GFX_RK/src Adafruit_BusIO_RK/src' + aux: '' + # CHANGE platforms as needed + platform: + - {name: 'photon', version: '2.3.1'} + - {name: 'argon', version: '4.2.0'} + - {name: 'p2', version: '6.3.2'} + + # program name + name: ${{ matrix.program.src }}-${{ matrix.platform.name }}-${{ matrix.platform.version }} + + # workflow call + uses: ./.github/workflows/compile.yaml + secrets: inherit + with: + platform: ${{ matrix.platform.name }} + version: ${{ matrix.platform.version }} + src: ${{ matrix.program.src }} + lib: ${{ matrix.program.lib }} + aux: ${{ matrix.program.aux }} \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 6c0b555..c31b2e5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,12 @@ [submodule "lib/SparkFun_Qwiic_OpenLog_Arduino_Library"] path = lib/SparkFun_Qwiic_OpenLog_Arduino_Library url = https://github.com/sparkfun/SparkFun_Qwiic_OpenLog_Arduino_Library.git +[submodule "lib/Adafruit_SSD1306_RK"] + path = lib/Adafruit_SSD1306_RK + url = https://github.com/rickkas7/Adafruit_SSD1306_RK +[submodule "lib/Adafruit_GFX_RK"] + path = lib/Adafruit_GFX_RK + url = https://github.com/rickkas7/Adafruit_GFX_RK +[submodule "lib/Adafruit_BusIO_RK"] + path = lib/Adafruit_BusIO_RK + url = https://github.com/rickkas7/Adafruit_BusIO_RK diff --git a/README.md b/README.md index ba65e49..b4f22ef 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ The `LoggerCore` library provides the following functionality. ## Dependencies -The following third-party software is used in the ***LabLogger*** libraries: +The following third-party software is used in the ***LabLogger*** libraries. See the linked GitHub repositories for the respective licensing text and license files. | **Library** | **Dependency** | **Website** | **License** | |-------------|----------------------------------------|--------------------------------------------------------------------|-------------| @@ -90,8 +90,8 @@ The following third-party software is used in the ***LabLogger*** libraries: | LoggerCore | SequentialFileRK | https://github.com/rickkas7/SequentialFileRK | MIT | | LoggerCore | PublishQueueExtRK | https://github.com/rickkas7/PublishQueueExtRK | MIT | | LoggerCore | SparkFun_Qwiic_OpenLog_Arduino_Library | https://github.com/sparkfun/SparkFun_Qwiic_OpenLog_Arduino_Library | MIT | -| LoggerOled | Adafruit_SSD1306 | https://github.com/adafruit/Adafruit_SSD1306 | BSD | -| LoggerOled | Adafruit-GFX-Library | https://github.com/adafruit/Adafruit-GFX-Library | BSD | -| LoggerOled | Adafruit_BusIO | https://github.com/adafruit/Adafruit_BusIO | MIT | +| LoggerOled | Adafruit_SSD1306_RK | https://github.com/rickkas7/Adafruit_SSD1306_RK | BSD | +| LoggerOled | Adafruit_GFX_RK | https://github.com/rickkas7/Adafruit_GFX_RK | BSD | +| LoggerOled | Adafruit_BusIO | https://github.com/rickkas7/Adafruit_BusIO_RK | MIT | diff --git a/lib/Adafruit_BusIO_RK b/lib/Adafruit_BusIO_RK new file mode 160000 index 0000000..1b7c946 --- /dev/null +++ b/lib/Adafruit_BusIO_RK @@ -0,0 +1 @@ +Subproject commit 1b7c946a6107a247b26707d80bc2e2d9e26d4340 diff --git a/lib/Adafruit_GFX_RK b/lib/Adafruit_GFX_RK new file mode 160000 index 0000000..3e32a67 --- /dev/null +++ b/lib/Adafruit_GFX_RK @@ -0,0 +1 @@ +Subproject commit 3e32a67cf17889144e5227ef064f787e0506a794 diff --git a/lib/Adafruit_SSD1306_RK b/lib/Adafruit_SSD1306_RK new file mode 160000 index 0000000..b5ebf2c --- /dev/null +++ b/lib/Adafruit_SSD1306_RK @@ -0,0 +1 @@ +Subproject commit b5ebf2cb1a1a229e3814bfdc9af21fcfcc9942d9 diff --git a/src/oled/oled_test.cpp b/src/oled/oled_test.cpp new file mode 100644 index 0000000..54be08a --- /dev/null +++ b/src/oled/oled_test.cpp @@ -0,0 +1,41 @@ +#include "Particle.h" +#include "Wire.h" +#include "Adafruit_GFX.h" +#include "Adafruit_SSD1306.h" + +// enable system treading +#ifndef SYSTEM_VERSION_v620 +SYSTEM_THREAD(ENABLED); +#endif + +// log handler +SerialLogHandler logHandler(LOG_LEVEL_INFO); + +// Use I2C with OLED RESET pin +#define OLED_RESET D10 // D10 on photon2, D8 on argon/boron +Adafruit_SSD1306 display(OLED_RESET); + +void setup() { + display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3C (for the 128x64) + display.display(); // show splashscreen + display.setTextSize(1); + display.setTextColor(WHITE); +} + +unsigned long last_run = 0; +int counter = 0; +const std::chrono::milliseconds wait = 5s; + +void loop() { + if (millis() - last_run > wait.count()) { + last_run = millis(); + Log.info("hello %d", counter); + + display.setCursor(0, 0); + display.clearDisplay(); + display.printf("hello %d...", counter); + display.display(); + + counter++; + } +} \ No newline at end of file diff --git a/src/oled/project.properties b/src/oled/project.properties new file mode 100644 index 0000000..0b9128a --- /dev/null +++ b/src/oled/project.properties @@ -0,0 +1,11 @@ +name=oled + +## DEPENDENCIES ## +# dependencies added in lib/ as submodules to include full codebase in repo +# these will be included in `rake PROGRA` compile as long as they are listed +# in .github/workflows/compile-PROGRAM.yaml under program -> lib +# if a dependency is not available locally in lib/, comment it in here + +# dependencies.Adafruit_SSD1306_RK=1.3.3 +# dependencies.Adafruit_GFX_RK=1.11.10 +# dependencies.Adafruit_BusIO_RK=1.16.1 \ No newline at end of file From 2511762cfd8424b70f68e8080a41bfea7480acd4 Mon Sep 17 00:00:00 2001 From: Sebastian Kopf Date: Sat, 17 May 2025 01:57:52 +0200 Subject: [PATCH 02/19] add first draft of logger function --- .github/workflows/compile-function.yaml | 38 +++++++ Rakefile | 1 + src/function/LoggerFunction.h | 137 ++++++++++++++++++++++++ src/function/LoggerModule.h | 19 ++++ src/function/function_test.cpp | 59 ++++++++++ src/function/project.properties | 1 + 6 files changed, 255 insertions(+) create mode 100644 .github/workflows/compile-function.yaml create mode 100644 src/function/LoggerFunction.h create mode 100644 src/function/LoggerModule.h create mode 100644 src/function/function_test.cpp create mode 100644 src/function/project.properties diff --git a/.github/workflows/compile-function.yaml b/.github/workflows/compile-function.yaml new file mode 100644 index 0000000..2fd985a --- /dev/null +++ b/.github/workflows/compile-function.yaml @@ -0,0 +1,38 @@ +# name of the job +name: Compile function + +# specify which paths to watch for changes +on: + push: + paths: + - src/function + - .github/workflows/compile.yaml + - .github/workflows/compile-function.yaml + +# run compile via the compile.yaml +jobs: + compile: + strategy: + fail-fast: false + matrix: + # CHANGE program/lib/aux as needed + program: + - src: 'function' + lib: '' + aux: '' + # CHANGE platforms as needed + platform: + - {name: 'p2', version: '6.3.2'} + + # program name + name: ${{ matrix.program.src }}-${{ matrix.platform.name }}-${{ matrix.platform.version }} + + # workflow call + uses: ./.github/workflows/compile.yaml + secrets: inherit + with: + platform: ${{ matrix.platform.name }} + version: ${{ matrix.platform.version }} + src: ${{ matrix.program.src }} + lib: ${{ matrix.program.lib }} + aux: ${{ matrix.program.aux }} \ No newline at end of file diff --git a/Rakefile b/Rakefile index 39ed652..9758cae 100644 --- a/Rakefile +++ b/Rakefile @@ -17,6 +17,7 @@ task :blink => :compile task :publish => :compile task :i2c_scanner => :compile task :oled => :compile +task :function => :compile ### SETUP ### diff --git a/src/function/LoggerFunction.h b/src/function/LoggerFunction.h new file mode 100644 index 0000000..7c6da1e --- /dev/null +++ b/src/function/LoggerFunction.h @@ -0,0 +1,137 @@ +#pragma once +#include "Particle.h" + +class LoggerFunction { + + private: + + // name of the function that's registered with Particle.function + const char* m_function; + + protected: + + struct Command { + std::function callback; + const char* module; + const char* cmd; + }; + Variant commandToVariant(Command cmd) { + Variant var; + var.set("m", cmd.module); + var.set("c", cmd.cmd); + return(var); + } + + std::vector m_commands; + + + public: + + LoggerFunction(const char* function) : m_function(function) {} + + void setup() { + Log.info("registering particle function '%s'", m_function); + Particle.function(m_function, &LoggerFunction::receiveCall, this); + } + + // register cloud command + void registerCommand(const std::function& cb, const char* module, const char* cmd) { + Log.info("registering command '%s' for module '%s'", cmd, module); + m_commands.push_back({cb, module, cmd}); + } + + Variant getCommands() { + Variant cmds; + for (auto& cmd : m_commands) { + cmds.append(commandToVariant(cmd)); + } + return(cmds); + } + + // receive cloud command + int receiveCall (String call) { + Log.info("received: %s", call.c_str()); + Variant parsed = parseCall(call); + Log.info("parsed: %s", parsed.toJSON().c_str()); + + bool success = false; + // run all callbacks + //for (auto& cmd : m_commands) { + // success = cmd.callback(parsed); + //} + // FIXME: only call the one that was identified as the correct one + // FIXME: provide some sort of static method in LoggerFunction::error and LoggerFunction.warning + // that register parset.set("ret") and parsed.set("err") / parsed.set("warn") + // find a good way to register the error codes, probably with a namespace and structure so everything always has a text AND a retval!! + + // register success or fail + parsed.set("success", success); + + // if no ret val set yet + if (success && !parsed.has("ret")) { + parsed.set("ret", 0L); // FIXME: use placeholder for success + } else if (!success && !parsed.has("ret")) { + parsed.set("ret", -1L); + // FIXME: use placeholders for these codes + //LoggerFunction::error(ERROR_UNKNOWN); // registers the unknown error and the return value + //LoggerFunction::warn(WARN_BLA); // errors overwrite all warnings, all warnings are reported if there are multiple (but no error) - Q: how to deal with return codes? + } + + // check + Log.info("after callbacks: %s", parsed.toJSON().c_str()); + + // report command to cloud / FIXME implement + // add dt: timestamp + // add t: type = cmd + // LoggerPublisher::queueData(parsed); + + // return function + return(parsed.get("ret").asInt()); + } + + Variant parseCall(String& call) { + // started the parsed call variant + Variant parsed; + parsed.set("call", call.c_str()); + + // make a mutable local copy for thread safety + char *copy = strdup(call.c_str()); + + // empty call? + if (copy == nullptr) { + parsed.set("err", "call is empty"); + return(parsed); + } + + // get module + char *part = strtok(copy, " "); + parsed.set("m", part); + + // module exists? + bool mod_found = false; + for (auto& cmd : m_commands) { + if (strcmp(part, cmd.cmd) == 0) { + Log.trace("module match: %s", cmd.cmd); + mod_found = true; + break; + } + } + + if (!mod_found) { + parsed.set("err", "module not recognized"); + return(parsed); + } + + // command exists? + // FIXME: check all commands that match the module and the command + // FIXME: pull out params user= and note= + // FIXME: in database, store ret as return_code, store err / warn in return_value + // continue here + + //if (!part) { + // parsed.set("error", ""); + //} + // return the parsed command + return(parsed); + } +}; \ No newline at end of file diff --git a/src/function/LoggerModule.h b/src/function/LoggerModule.h new file mode 100644 index 0000000..650eb28 --- /dev/null +++ b/src/function/LoggerModule.h @@ -0,0 +1,19 @@ +#pragma once +#include "Particle.h" +#include "LoggerFunction.h" + +// module class +class LoggerModule { + + protected: + + // name of the module + const char* m_name; + + public: + + LoggerModule(const char* name) : m_name(name) {} + + virtual void registerCommands(LoggerFunction* func); + +}; diff --git a/src/function/function_test.cpp b/src/function/function_test.cpp new file mode 100644 index 0000000..959e307 --- /dev/null +++ b/src/function/function_test.cpp @@ -0,0 +1,59 @@ +#include "Particle.h" +#include "LoggerFunction.h" +#include "LoggerModule.h" + +// enable system treading +#ifndef SYSTEM_VERSION_v620 +SYSTEM_THREAD(ENABLED); +#endif + +// log handler +SerialLogHandler logHandler(LOG_LEVEL_INFO); + +// my component class +class MyModule : LoggerModule { + + public: + + MyModule(const char* name) : LoggerModule(name) {} + + void registerCommands(LoggerFunction* func) { + func->registerCommand(std::bind(&MyModule::myCmd, this, _1), m_name, "hello"); + func->registerCommand(std::bind(&MyModule::myCmd2, this, _1), m_name, "whatup"); + } + + bool myCmd(Variant& call) { + Log.info("'hello' triggered %s", call.toJSON().c_str()); + call.set("new", 42); + return(true); + } + + bool myCmd2(Variant& call) { + Log.info("'whatup' triggered: %s", call.toJSON().c_str()); + call.set("new2", 3.234); + return(false); + } + +}; + +// example +LoggerFunction* func = new LoggerFunction("test"); +MyModule* mod = new MyModule("mod1"); +String cmds; + +// setup +void setup() { + + // register all commands + mod->registerCommands(func); + cmds = func->getCommands().toJSON(); + Particle.variable("commands", cmds); + + // start listening to function calls + func->setup(); +} + +// loop +void loop() { + +} \ No newline at end of file diff --git a/src/function/project.properties b/src/function/project.properties new file mode 100644 index 0000000..3dcc813 --- /dev/null +++ b/src/function/project.properties @@ -0,0 +1 @@ +name=function \ No newline at end of file From 344653499d239088ff78521fa5fbd83066ed793f Mon Sep 17 00:00:00 2001 From: Sebastian Kopf Date: Sat, 17 May 2025 01:58:16 +0200 Subject: [PATCH 03/19] update vscode settings --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d9cd3ea..1a309cd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -74,6 +74,7 @@ "charconv": "cpp", "clocale": "cpp", "span": "cpp", - "variant": "cpp" + "variant": "cpp", + "deque": "cpp" } } \ No newline at end of file From e266efdd708942cd39437fb3190033b3ee4fbeca Mon Sep 17 00:00:00 2001 From: Sebastian Kopf Date: Sat, 17 May 2025 02:10:06 +0200 Subject: [PATCH 04/19] focus on photon test --- .github/workflows/compile-oled.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/compile-oled.yaml b/.github/workflows/compile-oled.yaml index 50862e0..e5a15fb 100644 --- a/.github/workflows/compile-oled.yaml +++ b/.github/workflows/compile-oled.yaml @@ -22,8 +22,6 @@ jobs: aux: '' # CHANGE platforms as needed platform: - - {name: 'photon', version: '2.3.1'} - - {name: 'argon', version: '4.2.0'} - {name: 'p2', version: '6.3.2'} # program name From c43971d1763e4b2df6fbfcc48d1692be286065c2 Mon Sep 17 00:00:00 2001 From: Sebastian Kopf Date: Mon, 19 May 2025 00:13:52 +0200 Subject: [PATCH 05/19] implementation of logger return types --- src/function/LoggerFunction.cpp | 172 +++++++++++++++++++++++++ src/function/LoggerFunction.h | 146 +++++++-------------- src/function/LoggerFunctionReturns.cpp | 45 +++++++ src/function/LoggerFunctionReturns.h | 57 ++++++++ src/function/LoggerModule.h | 23 +++- src/function/function_test.cpp | 30 ++++- 6 files changed, 364 insertions(+), 109 deletions(-) create mode 100644 src/function/LoggerFunction.cpp create mode 100644 src/function/LoggerFunctionReturns.cpp create mode 100644 src/function/LoggerFunctionReturns.h diff --git a/src/function/LoggerFunction.cpp b/src/function/LoggerFunction.cpp new file mode 100644 index 0000000..623290c --- /dev/null +++ b/src/function/LoggerFunction.cpp @@ -0,0 +1,172 @@ +#include "Particle.h" +#include "LoggerFunction.h" +#include "LoggerFunctionReturns.h" + +void LoggerFunction::setup() { + Log.info("registering particle function '%s'", m_function); + Particle.function(m_function, &LoggerFunction::receiveCall, this); +} + +void LoggerFunction::registerCommand(const std::function& cb, const char* module, const char* cmd) { + Log.info("registering command '%s' for module '%s'", cmd, module); + + // check for issues + size_t i = 0; + bool overwrite = false; + for (; i < m_commands.size(); ++i) { + if (strcmp(module, m_commands[i].module) == 0 && strcmp(cmd, m_commands[i].cmd) == 0) { + Log.warn("cmd (%s) already exists for this module (%s), overwriting existing", cmd, module); + overwrite = true; + break; + } + if (strcmp(cmd, m_commands[i].module) == 0 || strcmp(module, m_commands[i].cmd) == 0 || strcmp(cmd, module) == 0) { + // should this be here or elsewhere? not sure it's visible on logger startup + Log.error("identically named module and command (%s) can cause confusion and is not permitted", cmd); + return; + } + } + + // add/overwrite + if (!overwrite) { + m_commands.push_back({cb, module, cmd}); // add + } else { + m_commands[i] = {cb, module, cmd}; // overwrite + } +} + +Variant LoggerFunction::getCommands() { + Variant cmds; + for (auto& cmd : m_commands) { + cmds.append(commandToVariant(cmd)); + } + return(cmds); +} + + +int LoggerFunction::receiveCall (String call) { + + using namespace LoggerFunctionReturns; + + Log.trace("received: %s", call.c_str()); + + // store call and basic info in parsed Variant and send it off to parse + Variant parsed; + parsed.set("call", call.c_str()); + parsed.set("dt", Time.format(Time.now(), "%Y-%m-%d %H:%M:%S %Z")); + parsed.set("lt", "cmd"); // log type + size_t cmd_idx = parseCall(parsed); + + // any issues? + if (cmd_idx == PARSING_ERROR) { + Log.trace("parsing error: %s", parsed.toJSON().c_str()); + parsed.set("success", false); + return(getReturnValue(parsed)); + } + + // found a command while parsing, execute the callback + Log.trace("call parsed: %s", parsed.toJSON().c_str()); + bool success = m_commands[cmd_idx].callback(parsed); + parsed.set("success", success); + + // if no ret val set yet + if (success && !hasReturnValue(parsed)) { + setSuccess(parsed); + } else if (!success && !hasReturnValue(parsed)) { + setReturnValue(parsed, CMD_ERR_UNKNOWN); + } + + // debug check + Log.trace("after callback: %s", parsed.toJSON().c_str()); + + // report command to cloud / FIXME implement + // LoggerPublisher::queueData(parsed); + + // return function + return(getReturnValue(parsed)); +} + +size_t LoggerFunction::parseCall(Variant& parsed) { + + // logger function returns + using namespace LoggerFunctionReturns; + + // make a mutable local copy for thread safety + char *copy = strdup(parsed.get("call").toString().c_str()); + + // empty call? + if (copy == nullptr) { + setReturnValue(parsed, CMD_ERR_EMPTY); + return(PARSING_ERROR); + } + + // get module + char *part = strtok(copy, " "); + + // module or cmd exists? + bool mod_found = false; + size_t cmd_idx = 0; + uint n_cmds_found = 0; + for (size_t i = 0; i < m_commands.size(); ++i) { + if (strcmp(part, m_commands[i].module) == 0) { + Log.trace("module match: %s", part); + parsed.set("m", part); + mod_found = true; + } + if (strcmp(part, m_commands[i].cmd) == 0) { + Log.trace("cmd match: %s", part); + parsed.set("c", part); + n_cmds_found++; + cmd_idx = i; + } + } + + // what was found? + if (mod_found && n_cmds_found == 0) { + // all good, found a module and now looking for a command + part = strtok(nullptr, " "); + if (part == nullptr) { + // no command provided --> error + setReturnValue(parsed, CMD_ERR_CMD_MISS); + return(PARSING_ERROR); + } + for (size_t i = 0; i < m_commands.size(); ++i) { + Log.trace("mod: %s = %s?, cmd: %s = %s?", parsed.get("m").asString().c_str(), m_commands[i].module, part, m_commands[i].cmd); + if (strcmp(parsed.get("m").asString().c_str(), m_commands[i].module) == 0 && strcmp(part, m_commands[i].cmd) == 0) { + // found the command + Log.trace("cmd match: %s", part); + parsed.set("c", part); + n_cmds_found++; + cmd_idx = i; + break; + } + } + if (n_cmds_found == 0) { + // no command of those that are registered for the module fits + setReturnValue(parsed, CMD_ERR_CMD); + return(PARSING_ERROR); + } + } else if (!mod_found && n_cmds_found == 1) { + // all good, found a single command --> set the module accordingly + parsed.set("m", m_commands[cmd_idx].module); + } else if (!mod_found && n_cmds_found == 0) { + // was neither a comand nor a module -> error + setReturnValue(parsed, CMD_ERR_CMD_MOD); + return(PARSING_ERROR); + } else if (!mod_found && n_cmds_found > 1) { + // command is ambiguous (might be in multiple modules) + setReturnValue(parsed, CMD_ERR_AMBIGUOUS); + return(PARSING_ERROR); + } + + // so far so good, continue parsing + // FIXME: check all commands that match the module and the command + // FIXME: pull out params user= and note= + // FIXME: in database, store ret as return_code, store err / warn in return_value + // continue here + + //if (!part) { + // parsed.set("error", ""); + //} + // succesfully parsed + return(cmd_idx); +} diff --git a/src/function/LoggerFunction.h b/src/function/LoggerFunction.h index 7c6da1e..3dafe22 100644 --- a/src/function/LoggerFunction.h +++ b/src/function/LoggerFunction.h @@ -1,12 +1,29 @@ #pragma once #include "Particle.h" - +#include "LoggerFunctionReturns.h" + +/** + * extension of return codes + */ +namespace LoggerFunctionReturns { + inline constexpr Error CMD_ERR_UNKNOWN = {-1, "undefined error"}; + inline constexpr Error CMD_ERR_EMPTY = {-2, "call is empty"}; + inline constexpr Error CMD_ERR_AMBIGUOUS = {-3, "command ambiguous (exists in multiple modules), specify module"}; + inline constexpr Error CMD_ERR_CMD_MOD = {-4, "module/command not recognized"}; + inline constexpr Error CMD_ERR_CMD_MISS = {-5, "module found but no command provided"}; + inline constexpr Error CMD_ERR_CMD = {-6, "module found but command not recognized"}; +} + +/** + * class that manages a function call for the logger + */ class LoggerFunction { private: // name of the function that's registered with Particle.function const char* m_function; + const size_t PARSING_ERROR = std::numeric_limits::max(); protected: @@ -29,109 +46,36 @@ class LoggerFunction { LoggerFunction(const char* function) : m_function(function) {} - void setup() { - Log.info("registering particle function '%s'", m_function); - Particle.function(m_function, &LoggerFunction::receiveCall, this); - } + void setup(); - // register cloud command - void registerCommand(const std::function& cb, const char* module, const char* cmd) { - Log.info("registering command '%s' for module '%s'", cmd, module); - m_commands.push_back({cb, module, cmd}); + /** + * @brief register a cloud command with an instance and method + */ + template + void registerCommand(T* instance, bool (T::*method)(Variant&), const char* module, const char* cmd) { + std::function cb = std::bind(method, instance, _1); + registerCommand(cb, module, cmd); } - Variant getCommands() { - Variant cmds; - for (auto& cmd : m_commands) { - cmds.append(commandToVariant(cmd)); - } - return(cmds); - } + /** + * @brief register a cloud command directly with a std:function call + */ + void registerCommand(const std::function& cb, const char* module, const char* cmd); - // receive cloud command - int receiveCall (String call) { - Log.info("received: %s", call.c_str()); - Variant parsed = parseCall(call); - Log.info("parsed: %s", parsed.toJSON().c_str()); - - bool success = false; - // run all callbacks - //for (auto& cmd : m_commands) { - // success = cmd.callback(parsed); - //} - // FIXME: only call the one that was identified as the correct one - // FIXME: provide some sort of static method in LoggerFunction::error and LoggerFunction.warning - // that register parset.set("ret") and parsed.set("err") / parsed.set("warn") - // find a good way to register the error codes, probably with a namespace and structure so everything always has a text AND a retval!! - - // register success or fail - parsed.set("success", success); - - // if no ret val set yet - if (success && !parsed.has("ret")) { - parsed.set("ret", 0L); // FIXME: use placeholder for success - } else if (!success && !parsed.has("ret")) { - parsed.set("ret", -1L); - // FIXME: use placeholders for these codes - //LoggerFunction::error(ERROR_UNKNOWN); // registers the unknown error and the return value - //LoggerFunction::warn(WARN_BLA); // errors overwrite all warnings, all warnings are reported if there are multiple (but no error) - Q: how to deal with return codes? - } - - // check - Log.info("after callbacks: %s", parsed.toJSON().c_str()); - - // report command to cloud / FIXME implement - // add dt: timestamp - // add t: type = cmd - // LoggerPublisher::queueData(parsed); - - // return function - return(parsed.get("ret").asInt()); - } + /** + * @brief get back a Variant with all the commands + */ + Variant getCommands(); + + /** + * @brief internal function that's registered with the Particle cloud to process user commands + */ + int receiveCall (String call); + + /** + * @brief parse the function call and store the results in the parsed Variant + * @return the m_commands index of the command that fits the call (or PARSED_ERROR if parsing error) + */ + size_t parseCall(Variant& parsed); - Variant parseCall(String& call) { - // started the parsed call variant - Variant parsed; - parsed.set("call", call.c_str()); - - // make a mutable local copy for thread safety - char *copy = strdup(call.c_str()); - - // empty call? - if (copy == nullptr) { - parsed.set("err", "call is empty"); - return(parsed); - } - - // get module - char *part = strtok(copy, " "); - parsed.set("m", part); - - // module exists? - bool mod_found = false; - for (auto& cmd : m_commands) { - if (strcmp(part, cmd.cmd) == 0) { - Log.trace("module match: %s", cmd.cmd); - mod_found = true; - break; - } - } - - if (!mod_found) { - parsed.set("err", "module not recognized"); - return(parsed); - } - - // command exists? - // FIXME: check all commands that match the module and the command - // FIXME: pull out params user= and note= - // FIXME: in database, store ret as return_code, store err / warn in return_value - // continue here - - //if (!part) { - // parsed.set("error", ""); - //} - // return the parsed command - return(parsed); - } }; \ No newline at end of file diff --git a/src/function/LoggerFunctionReturns.cpp b/src/function/LoggerFunctionReturns.cpp new file mode 100644 index 0000000..e94b969 --- /dev/null +++ b/src/function/LoggerFunctionReturns.cpp @@ -0,0 +1,45 @@ +#include "Particle.h" +#include "LoggerFunctionReturns.h" + +bool LoggerFunctionReturns::hasReturnValue(Variant &call) { + return(call.has("ret")); +} + +int LoggerFunctionReturns::getReturnValue(Variant &call) { + // return copy of return value + return(call.get("ret").toInt()); +} + +void LoggerFunctionReturns::setReturnValue(Variant& call, int code, const char* message, bool overwrite) { + + if (call.has("ret") && call.get("ret").asInt() != LoggerFunctionReturns::CMD_SUCCESS) { + // call already has a return value that is NOT the marker for success + if (overwrite) { + // something other than success is getting overwritten + Log.warn("overwriting existing return value %d with new value %d = %s", call.get("ret").asInt(), code, message); + } else { + // keeping existing value (overwrite is false) + Log.warn("keeping existing return value (%d) and discarding new value %d = %s", call.get("ret").asInt(), code, message); + return; + } + } + call.set("ret", code); + call.set("msg", message); +} + +void LoggerFunctionReturns::setReturnValue(Variant& call, LoggerFunctionReturns::Warning warn) { + setReturnValue(call, warn.code, warn.message, false); +} + +void LoggerFunctionReturns::setReturnValue(Variant& call, LoggerFunctionReturns::Error err) { + setReturnValue(call, err.code, err.message, true); +} + +void LoggerFunctionReturns::setSuccess(Variant& call) { + if (!hasReturnValue(call)) { + call.set("ret", LoggerFunctionReturns::CMD_SUCCESS); + } else if (call.get("ret").asInt() != LoggerFunctionReturns::CMD_SUCCESS) { + // call already has a state set + Log.warn("could not set call to success (%d), already has a different return code (%d)", LoggerFunctionReturns::CMD_SUCCESS, call.get("ret").asInt()); + } +} diff --git a/src/function/LoggerFunctionReturns.h b/src/function/LoggerFunctionReturns.h new file mode 100644 index 0000000..4dd4dc2 --- /dev/null +++ b/src/function/LoggerFunctionReturns.h @@ -0,0 +1,57 @@ +#pragma once + +#include "Particle.h" + +/** + * @brief namespace that defines return codes for LoggerFunctions + * expand this namespace in other classes to register additional return codes + */ +namespace LoggerFunctionReturns { + + struct Error { + const int code; + const char* message; + constexpr Error(int c, const char* msg) : code(c), message(msg) {} + }; + + struct Warning { + const int code; + const char* message; + constexpr Warning(int c, const char* msg) : code(c), message(msg) {} + }; + + const int CMD_SUCCESS = 0; + + /** + * @brief check if the call has a return value set + */ + bool hasReturnValue(Variant &call); + + /** + * @brief get the return value from the call Variant as an integer + */ + int getReturnValue(Variant &call); + + /** + * @brief set the return value from code and message + * (usually not called directly but via void setReturnValue(Variant& call, Warning warn) andvoid setReturnValue(Variant& call, Error err)) + */ + void setReturnValue(Variant& call, int code, const char* message, bool overwrite); + + /** + * @brief set the return value to a Warning + */ + void setReturnValue(Variant& call, Warning warn); + + /** + * @brief set the return value to an Error + */ + void setReturnValue(Variant& call, Error err); + + /** + * @brief set the return value to success + */ + void setSuccess(Variant& call); + +} + diff --git a/src/function/LoggerModule.h b/src/function/LoggerModule.h index 650eb28..06c5dfe 100644 --- a/src/function/LoggerModule.h +++ b/src/function/LoggerModule.h @@ -1,6 +1,7 @@ #pragma once #include "Particle.h" #include "LoggerFunction.h" +#include "LoggerFunctionReturns.h" // module class class LoggerModule { @@ -14,6 +15,26 @@ class LoggerModule { LoggerModule(const char* name) : m_name(name) {} - virtual void registerCommands(LoggerFunction* func); + const char* getName() { + return(m_name); + } + + void registerCommands(LoggerFunction* func) { + + }; + + /** + * @brief call this while parsing a command to indicate a warning to the user that does not fail the command + */ + void setReturnValue(Variant& call, LoggerFunctionReturns::Warning warn) { + LoggerFunctionReturns::setReturnValue(call, warn); + } + + /** + * @brief call this while parsing a command to indicate an error to the user that fails the command + */ + void setReturnValue(Variant& call, LoggerFunctionReturns::Error err) { + LoggerFunctionReturns::setReturnValue(call, err); + } }; diff --git a/src/function/function_test.cpp b/src/function/function_test.cpp index 959e307..e375f89 100644 --- a/src/function/function_test.cpp +++ b/src/function/function_test.cpp @@ -1,5 +1,6 @@ #include "Particle.h" #include "LoggerFunction.h" +#include "LoggerFunctionReturns.h" #include "LoggerModule.h" // enable system treading @@ -8,7 +9,12 @@ SYSTEM_THREAD(ENABLED); #endif // log handler -SerialLogHandler logHandler(LOG_LEVEL_INFO); +SerialLogHandler logHandler(LOG_LEVEL_TRACE); + +namespace LoggerFunctionReturns { + inline constexpr Error MY_WARNING = {100, "my favorite warning"}; + inline constexpr Error MY_ERROR = {-100, "my favorite error"}; +} // my component class class MyModule : LoggerModule { @@ -17,20 +23,26 @@ class MyModule : LoggerModule { MyModule(const char* name) : LoggerModule(name) {} - void registerCommands(LoggerFunction* func) { - func->registerCommand(std::bind(&MyModule::myCmd, this, _1), m_name, "hello"); - func->registerCommand(std::bind(&MyModule::myCmd2, this, _1), m_name, "whatup"); + // command 1 + void registerHelloCommand(LoggerFunction* func, const char* cmd = "hello") { + func->registerCommand(this, &MyModule::hello, getName(), cmd); } - bool myCmd(Variant& call) { + bool hello(Variant& call) { Log.info("'hello' triggered %s", call.toJSON().c_str()); call.set("new", 42); return(true); } - bool myCmd2(Variant& call) { + // command 2 + void registerWhatupCommand(LoggerFunction* func, const char* cmd = "whatup") { + func->registerCommand(this, &MyModule::whatup, getName(), cmd); + } + + bool whatup(Variant& call) { Log.info("'whatup' triggered: %s", call.toJSON().c_str()); call.set("new2", 3.234); + setReturnValue(call, LoggerFunctionReturns::MY_ERROR); return(false); } @@ -45,7 +57,11 @@ String cmds; void setup() { // register all commands - mod->registerCommands(func); + mod->registerHelloCommand(func); + mod->registerWhatupCommand(func); + mod->registerWhatupCommand(func, "WHATUP"); + + // safe commands in the commands variable cmds = func->getCommands().toJSON(); Particle.variable("commands", cmds); From 74cf3c3a4f04e586294510628f4f9171f396b7a1 Mon Sep 17 00:00:00 2001 From: Sebastian Kopf Date: Tue, 20 May 2025 15:34:26 +0200 Subject: [PATCH 06/19] update loggerfunction to interpret text and numeric values and units --- README.md | 1 + src/function/LoggerFunction.cpp | 376 ++++++++++++++++++++++++++------ src/function/LoggerFunction.h | 161 +++++++++++--- src/function/function_test.cpp | 105 +++++++-- 4 files changed, 529 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index b4f22ef..a777c47 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ The following firmware is included in the repository to provide frequently used | blink | ![blink](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=main) | ![blink-dev](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=dev) | | i2c_scanner | ![blink](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=main) | ![blink-dev](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=dev) | | publish | ![publish](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml/badge.svg?branch=main) | ![publish-dev](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml/badge.svg?branch=dev) | +| function | ![function](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml/badge.svg?branch=main) | ![function-dev](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/function-publish.yaml/badge.svg?branch=dev) | ### Compile diff --git a/src/function/LoggerFunction.cpp b/src/function/LoggerFunction.cpp index 623290c..ac9ca18 100644 --- a/src/function/LoggerFunction.cpp +++ b/src/function/LoggerFunction.cpp @@ -2,12 +2,79 @@ #include "LoggerFunction.h" #include "LoggerFunctionReturns.h" +Variant LoggerFunction::Command::toVariant() { + Variant var; + var.set("c", cmd); + if (allow_numeric_values) { + // numeric values are allowed (1 = shorter in JSON than true) + var.set("n", 1); + } + if ( expect_value && value_optional ) { + // value attribute is optional (1 = shorter in JSON than true) + var.set("o", 1); + } + if (!text_values.isEmpty()) { + // what are the allowed values? + Variant vals; + for (size_t i = 0; i < text_values.size(); ++i) + vals.append(text_values[i].c_str()); + var.set("v", vals); + } + if (allow_numeric_values && !numeric_units.isEmpty()) { + // what units are allowed? + Variant units; + for (size_t i = 0; i < numeric_units.size(); ++i) + units.append(numeric_units[i].c_str()); + var.set("u",units); + } + return(var); +} + +Variant LoggerFunction::getCommands() { + Variant cmds; + for (auto& cmd : m_commands) { + // group by module to further optimize the JSON + if (!cmds.has(cmd.module)) + cmds.set(cmd.module, Variant()); + cmds[cmd.module].append(cmd.toVariant()); + } + return(cmds); +} + void LoggerFunction::setup() { Log.info("registering particle function '%s'", m_function); Particle.function(m_function, &LoggerFunction::receiveCall, this); + + // available commands variable + if (m_var_available_commands != nullptr) { + Log.info("registering particle variable '%s'", m_var_available_commands); + String cmds_json = getCommands().toJSON(); + if (cmds_json.length() < particle::protocol::MAX_FUNCTION_ARG_LENGTH) { + // commands fit into the particle variable + snprintf(m_value_available_commands, particle::protocol::MAX_FUNCTION_ARG_LENGTH, "%s", cmds_json.c_str()); + } else { + // commands don't fit + Variant trunc; + trunc.set("trunc", true); + trunc.set("error", + String::format("commands are too long (%d chars) and don't fit into the size limit of a particle variable (%d)", + cmds_json.length(), particle::protocol::MAX_FUNCTION_ARG_LENGTH)); + snprintf(m_value_available_commands, particle::protocol::MAX_FUNCTION_ARG_LENGTH, "%s", trunc.toJSON().c_str()); + } + Particle.variable(m_var_available_commands, m_value_available_commands); + } + + // last call variable + if (m_var_last_calls != nullptr) { + // starting value is just an empty json array since there are no commands yet + snprintf(m_value_last_calls, particle::protocol::MAX_FUNCTION_ARG_LENGTH, "%s", "[]"); + Particle.variable(m_var_last_calls, m_value_last_calls); + } } -void LoggerFunction::registerCommand(const std::function& cb, const char* module, const char* cmd) { +void LoggerFunction::registerCommand(const std::function& cb, const char* module, const char* cmd, + const Vector& text_values, bool allow_numeric_values, + const Vector& numeric_units, bool value_optional) { Log.info("registering command '%s' for module '%s'", cmd, module); // check for issues @@ -27,75 +94,108 @@ void LoggerFunction::registerCommand(const std::function& cb, co } // add/overwrite - if (!overwrite) { - m_commands.push_back({cb, module, cmd}); // add - } else { - m_commands[i] = {cb, module, cmd}; // overwrite - } + if (overwrite) + m_commands[i].use = false; // flag for ignoring (=overwrite) + m_commands.append({cb, module, cmd, text_values, allow_numeric_values, numeric_units, value_optional}); // add } - -Variant LoggerFunction::getCommands() { - Variant cmds; - for (auto& cmd : m_commands) { - cmds.append(commandToVariant(cmd)); - } - return(cmds); -} - int LoggerFunction::receiveCall (String call) { using namespace LoggerFunctionReturns; - - Log.trace("received: %s", call.c_str()); - // store call and basic info in parsed Variant and send it off to parse - Variant parsed; - parsed.set("call", call.c_str()); - parsed.set("dt", Time.format(Time.now(), "%Y-%m-%d %H:%M:%S %Z")); - parsed.set("lt", "cmd"); // log type - size_t cmd_idx = parseCall(parsed); + // store call and basic info in current command Variant, then parse it + m_cur_call = Variant(); + m_cur_call.set("call", call.c_str()); + m_cur_call.set("dt", Time.format(Time.now(), "%Y-%m-%d %H:%M:%S %Z")); + m_cur_call.set("lt", "cmd"); // log type + size_t cmd_idx = parseCall(); // any issues? if (cmd_idx == PARSING_ERROR) { - Log.trace("parsing error: %s", parsed.toJSON().c_str()); - parsed.set("success", false); - return(getReturnValue(parsed)); - } - - // found a command while parsing, execute the callback - Log.trace("call parsed: %s", parsed.toJSON().c_str()); - bool success = m_commands[cmd_idx].callback(parsed); - parsed.set("success", success); - - // if no ret val set yet - if (success && !hasReturnValue(parsed)) { - setSuccess(parsed); - } else if (!success && !hasReturnValue(parsed)) { - setReturnValue(parsed, CMD_ERR_UNKNOWN); - } - - // debug check - Log.trace("after callback: %s", parsed.toJSON().c_str()); - // report command to cloud / FIXME implement - // LoggerPublisher::queueData(parsed); + // parsing error + Log.trace("parsing error: %s", m_cur_call.toJSON().c_str()); + m_cur_call.set("success", false); + + } else { + + // found a command while parsing, execute the callback + Log.trace("execute callback with: %s", m_cur_call.toJSON().c_str()); + bool success = m_commands[cmd_idx].callback(m_cur_call); + m_cur_call.set("success", success); + + // if no ret val set yet + if (success && !hasReturnValue(m_cur_call)) { + setSuccess(m_cur_call); + } else if (!success && !hasReturnValue(m_cur_call)) { + setReturnValue(m_cur_call, CALL_ERR_UNKNOWN); + } + } + + // report command to cloud if logging is on + if (m_log) { + // FIXME: implement + // LoggerPublisher::queueData(m_cur_call); + // this will also print the published data like below instead of here + Log.trace("after callback:"); + Log.print(m_cur_call.toJSON().c_str()); + Log.print("\n"); + } + + // update last call variable? + if (m_var_last_calls != nullptr) { + + /* + * i think this leads ot memory fragmentation + + // store the last call in the call log + m_call_log.append(m_cur_call); + String call_log_json = m_call_log.toJSON(); + while (call_log_json.length() >= particle::protocol::MAX_FUNCTION_ARG_LENGTH && !m_call_log.isEmpty()) { + // remove the oldest entries until they fit + m_call_log.removeAt(0); + call_log_json = m_call_log.toJSON(); + } + + // set m_call_log + if (Log.isTraceEnabled()) { + Log.trace("new value for Particle.variable('%s') from %d commands in call log stack", m_var_last_calls, m_call_log.size()); + Log.print(call_log_json); + Log.print("\n"); + } + + // check if call log now fits + if (call_log_json.length() < particle::protocol::MAX_FUNCTION_ARG_LENGTH) { + // call log now fits into the particle variable + snprintf(m_value_last_calls, particle::protocol::MAX_FUNCTION_ARG_LENGTH, "%s", call_log_json.c_str()); + } else { + // call doesn't fit + Variant trunc; + trunc.set("trunc", true); + trunc.set("error", + String::format("call is too long (%d chars) and doesn't fit into the size limit of a particle variable (%d)", + call_log_json.length(), particle::protocol::MAX_FUNCTION_ARG_LENGTH)); + snprintf(m_value_last_calls, particle::protocol::MAX_FUNCTION_ARG_LENGTH, "%s", trunc.toJSON().c_str()); + } + + */ + } - // return function - return(getReturnValue(parsed)); + // return return value + return(getReturnValue(m_cur_call)); } -size_t LoggerFunction::parseCall(Variant& parsed) { +size_t LoggerFunction::parseCall() { // logger function returns using namespace LoggerFunctionReturns; // make a mutable local copy for thread safety - char *copy = strdup(parsed.get("call").toString().c_str()); + char *copy = strdup(m_cur_call.get("call").toString().c_str()); // empty call? if (copy == nullptr) { - setReturnValue(parsed, CMD_ERR_EMPTY); + setReturnValue(m_cur_call, CALL_ERR_EMPTY); return(PARSING_ERROR); } @@ -107,14 +207,14 @@ size_t LoggerFunction::parseCall(Variant& parsed) { size_t cmd_idx = 0; uint n_cmds_found = 0; for (size_t i = 0; i < m_commands.size(); ++i) { + if (!m_commands[i].use) continue; if (strcmp(part, m_commands[i].module) == 0) { - Log.trace("module match: %s", part); - parsed.set("m", part); + m_cur_call.set("m", part); mod_found = true; } if (strcmp(part, m_commands[i].cmd) == 0) { Log.trace("cmd match: %s", part); - parsed.set("c", part); + m_cur_call.set("c", part); n_cmds_found++; cmd_idx = i; } @@ -126,15 +226,15 @@ size_t LoggerFunction::parseCall(Variant& parsed) { part = strtok(nullptr, " "); if (part == nullptr) { // no command provided --> error - setReturnValue(parsed, CMD_ERR_CMD_MISS); + setReturnValue(m_cur_call, CALL_ERR_CMD_MISS); return(PARSING_ERROR); } for (size_t i = 0; i < m_commands.size(); ++i) { - Log.trace("mod: %s = %s?, cmd: %s = %s?", parsed.get("m").asString().c_str(), m_commands[i].module, part, m_commands[i].cmd); - if (strcmp(parsed.get("m").asString().c_str(), m_commands[i].module) == 0 && strcmp(part, m_commands[i].cmd) == 0) { + if (!m_commands[i].use) continue; + if (strcmp(m_cur_call.get("m").asString().c_str(), m_commands[i].module) == 0 && strcmp(part, m_commands[i].cmd) == 0) { // found the command Log.trace("cmd match: %s", part); - parsed.set("c", part); + m_cur_call.set("c", part); n_cmds_found++; cmd_idx = i; break; @@ -142,31 +242,169 @@ size_t LoggerFunction::parseCall(Variant& parsed) { } if (n_cmds_found == 0) { // no command of those that are registered for the module fits - setReturnValue(parsed, CMD_ERR_CMD); + setReturnValue(m_cur_call, CALL_ERR_CMD_UNREC); return(PARSING_ERROR); } } else if (!mod_found && n_cmds_found == 1) { // all good, found a single command --> set the module accordingly - parsed.set("m", m_commands[cmd_idx].module); + m_cur_call.set("m", m_commands[cmd_idx].module); } else if (!mod_found && n_cmds_found == 0) { // was neither a comand nor a module -> error - setReturnValue(parsed, CMD_ERR_CMD_MOD); + setReturnValue(m_cur_call, CALL_ERR_CMD_MOD_UNREC); return(PARSING_ERROR); } else if (!mod_found && n_cmds_found > 1) { // command is ambiguous (might be in multiple modules) - setReturnValue(parsed, CMD_ERR_AMBIGUOUS); + setReturnValue(m_cur_call, CALL_ERR_AMBIGUOUS); return(PARSING_ERROR); } - // so far so good, continue parsing - // FIXME: check all commands that match the module and the command - // FIXME: pull out params user= and note= - // FIXME: in database, store ret as return_code, store err / warn in return_value - // continue here + // safety check to avoid segfaults // REMOVE? + if (cmd_idx >= m_commands.size()) { + Log.error("this should never happen, cmd_idx (%d) is too large, there are only %d commands", cmd_idx, m_commands.size()); + return(PARSING_ERROR); + } + + // values expected? + if (m_commands[cmd_idx].expect_value) { + part = strtok(nullptr, " "); + if (part == nullptr) { + if (!m_commands[cmd_idx].value_optional) { + // no value provided and value is not optional --> error + setReturnValue(m_cur_call, CALL_ERR_VAL_MISS); + return(PARSING_ERROR); + } + // empty value but that's okay + m_cur_call.set("vtext", Variant()); + } else { + // got a value! + m_cur_call.set("vtext", part); + bool valid_value = false; + + // let's see if it matches any of the allowed text values + if (m_commands[cmd_idx].text_values.size() > 0) { + for (size_t i = 0; i < m_commands[cmd_idx].text_values.size(); ++i) { + if (strcmp(part, m_commands[cmd_idx].text_values[i].c_str()) == 0) { + // found the value (already correctly assigned "vtext") + Log.trace("value match: %s", part); + valid_value = true; + break; + } + } + // nothing found and numeric values not allowed? + if (!valid_value && !m_commands[cmd_idx].allow_numeric_values) { + // --> error + setReturnValue(m_cur_call, CALL_ERR_VAL_UNREC); + return(PARSING_ERROR); + } + } + + // no match yet? let's see if it's a valid numeric value (if they're allowed) + if (!valid_value && m_commands[cmd_idx].allow_numeric_values) { + + char* num_end = nullptr; + + // convert the initial numeric part to double + double number = strtod(part, &num_end); + + if (num_end == part) { + // not a valid number + setReturnValue(m_cur_call, CALL_ERR_VAL_NAN); + return(PARSING_ERROR); + } else { + // yay number + m_cur_call.set("vnum", number); + Log.trace("numeric value: %s", m_cur_call.get("vnum").toString().c_str()); + } + + if (*num_end != '\0' && m_commands[cmd_idx].numeric_units.isEmpty()) { + // found units directly after the number but none were expected! --> error + m_cur_call.set("u", num_end); + setReturnValue(m_cur_call, CALL_ERR_UNIT_UNEXP); + return(PARSING_ERROR); + } + + // check for units + if (!m_commands[cmd_idx].numeric_units.isEmpty()) { + bool valid_units = false; + if (*num_end != '\0') { + // found units directly after the number + m_cur_call.set("u", num_end); + } else { + // fetch next part + part = strtok(nullptr, " "); + if (part == nullptr) { + setReturnValue(m_cur_call, CALL_ERR_UNIT_MISS); + return(PARSING_ERROR); + } + m_cur_call.set("u", part); + } + // check if the units fit any of the expected + for (size_t i = 0; i < m_commands[cmd_idx].numeric_units.size(); ++i) { + if (strcmp(m_cur_call.get("u").asString().c_str(), m_commands[cmd_idx].numeric_units[i].c_str()) == 0) { + // found the unit (already correctly assigned "u") + Log.trace("unit match: %s", m_cur_call.get("u").asString().c_str()); + valid_units = true; + break; + } + } + + // did we find valid units? + if (!valid_units) { + // no --> units not recognized + setReturnValue(m_cur_call, CALL_ERR_UNIT_UNREC); + return(PARSING_ERROR); + } + } + } + } + } + + // check for params if function interprets them + if (!m_params.isEmpty()) { + bool found_param = false; + size_t current_param = 0; + String param_value; + part = strtok(nullptr, " "); + while (part != nullptr) { + bool new_param = false; + + // starts with a 'param='? + for (size_t i = 0; i < m_params.size(); ++i) { + String prefix = m_params[i] + "="; + if (strncmp(part, prefix.c_str(), prefix.length()) == 0) { + // found a param! + if (found_param) { + // store the previous param in the variant + Log.trace("param: %s='%s'", m_params[current_param].c_str(), param_value.c_str()); + m_cur_call.set(m_params[current_param].c_str(), param_value.c_str()); + } + // start the new param + found_param = true; + new_param = true; + current_param = i; + param_value = part + prefix.length(); // start new valuew without the prefix + break; + } + } - //if (!part) { - // parsed.set("error", ""); - //} - // succesfully parsed + // append value to current param value + if (found_param && !new_param) { + param_value += " "; + param_value += part; + } + + // continue the search + part = strtok(nullptr, " "); + } + + // any param to wrap up? + if (found_param) { + // store the previous param in the variant + Log.trace("param: %s='%s'", m_params[current_param].c_str(), param_value.c_str()); + m_cur_call.set(m_params[current_param].c_str(), param_value.c_str()); + } + } + + // parsing complete return(cmd_idx); } diff --git a/src/function/LoggerFunction.h b/src/function/LoggerFunction.h index 3dafe22..c070930 100644 --- a/src/function/LoggerFunction.h +++ b/src/function/LoggerFunction.h @@ -6,12 +6,18 @@ * extension of return codes */ namespace LoggerFunctionReturns { - inline constexpr Error CMD_ERR_UNKNOWN = {-1, "undefined error"}; - inline constexpr Error CMD_ERR_EMPTY = {-2, "call is empty"}; - inline constexpr Error CMD_ERR_AMBIGUOUS = {-3, "command ambiguous (exists in multiple modules), specify module"}; - inline constexpr Error CMD_ERR_CMD_MOD = {-4, "module/command not recognized"}; - inline constexpr Error CMD_ERR_CMD_MISS = {-5, "module found but no command provided"}; - inline constexpr Error CMD_ERR_CMD = {-6, "module found but command not recognized"}; + inline constexpr Error CALL_ERR_UNKNOWN = { -1, "undefined error"}; + inline constexpr Error CALL_ERR_EMPTY = { -2, "call is empty"}; + inline constexpr Error CALL_ERR_AMBIGUOUS = { -3, "command ambiguous (exists in multiple modules), specify module"}; + inline constexpr Error CALL_ERR_CMD_MOD_UNREC = { -4, "module/command not recognized"}; + inline constexpr Error CALL_ERR_CMD_MISS = { -5, "module found but no command provided"}; + inline constexpr Error CALL_ERR_CMD_UNREC = { -6, "module found but command not recognized"}; + inline constexpr Error CALL_ERR_VAL_MISS = { -7, "value required but none provided"}; + inline constexpr Error CALL_ERR_VAL_NAN = { -8, "value is not a valid number"}; + inline constexpr Error CALL_ERR_VAL_UNREC = { -9, "value not recognized"}; + inline constexpr Error CALL_ERR_UNIT_UNEXP = {-11, "unit after number value but no unit was expected"}; + inline constexpr Error CALL_ERR_UNIT_MISS = {-10, "unit required but none provided"}; + inline constexpr Error CALL_ERR_UNIT_UNREC = {-12, "unit not recognized"}; } /** @@ -19,63 +25,158 @@ namespace LoggerFunctionReturns { */ class LoggerFunction { - private: + protected: - // name of the function that's registered with Particle.function + // name of the function and variables that are registered with Particle.function/variable const char* m_function; - const size_t PARSING_ERROR = std::numeric_limits::max(); + const char* m_var_available_commands; + char m_value_available_commands[particle::protocol::MAX_FUNCTION_ARG_LENGTH]; + const char* m_var_last_calls; + Variant m_call_log; + char m_value_last_calls[particle::protocol::MAX_FUNCTION_ARG_LENGTH]; - protected: + // call parameters (xyz=, abc=) to interpret/capture + const Vector m_params; + + // whether to log received calls with LoggerPublisher + bool m_log; + // return value indicating a parsing error + const size_t PARSING_ERROR = std::numeric_limits::max(); + + // command object for registering commands struct Command { std::function callback; const char* module; const char* cmd; + const Vector text_values = {};// if specific text values are allowed (can be fixed number values too) + bool allow_numeric_values = false; + const Vector numeric_units = {}; // if allow_numeric = true and the value should have units + bool value_optional = false; // whether providing a value is required or optional + bool expect_value = true; // if either text_values are provided or numeric_values are allowed + bool use = true; // flag when command is deactivated for some reason + + Command(std::function callback, const char* module, const char* cmd, + const Vector& text_values, bool allow_numeric_values, + const Vector& numeric_units, bool value_optional) : + callback(callback), module(module), cmd(cmd), text_values(text_values), allow_numeric_values(allow_numeric_values), numeric_units(numeric_units), + value_optional(value_optional), expect_value(allow_numeric_values || text_values.size() > 0), use(true) {} + + /** + * generate a variant with the command, this is in an optimized JSON format with + * variables only included when necessary and true/false represented as 1/0 + */ + Variant toVariant(); }; - Variant commandToVariant(Command cmd) { - Variant var; - var.set("m", cmd.module); - var.set("c", cmd.cmd); - return(var); - } - std::vector m_commands; + // vector of commands + Vector m_commands; + // current/last parsed function call + Variant m_cur_call; + + // register a full cloud command with a std:function call, used by other registerCommmand... calls + void registerCommand(const std::function& cb, const char* module, const char* cmd, + const Vector& text_values, bool allow_numeric_values, + const Vector& numeric_units, bool value_optional); + + // parses the function call and store the results in m_cur_call + // returns the m_commands index of the command that fits the call (or PARSED_ERROR if parsing error) + size_t parseCall(); public: - LoggerFunction(const char* function) : m_function(function) {} + // common text values used a lot + inline static const char* on = "on"; + inline static const char* off = "off"; + + // default constructor that's used for lablogger devices + // copy into your class to make these explicit for your device + // don't change the string constants for compatibility with the LabLogger framwork + LoggerFunction() : + LoggerFunction( + "device", // name of the Particle.function call + {"user", "note"}, // parameters (param=) interpreted by the call + true, // whether to log each command using the LoggerPublisher + "commands", // name of the Particle.variable where all available commands are stored (as JSON) + "last_calls" // name of the Particle.variable where the last received function calls are stored (as JSON) + ) + {} + + // constructor with user defined parameters + LoggerFunction(const char* function, const Vector& params, bool log, const char* var_available_commands, const char* var_last_calls) : m_function(function), m_var_available_commands(var_available_commands), m_var_last_calls(var_last_calls), m_params(params), m_log(log) {} + /** + * @brief must be called at the end of setup() to register the cloud function+variables and start listening to commands - note that any registerCommand that is called AFTER setup is not included in the available commands + */ void setup(); /** - * @brief register a cloud command with an instance and method + * @brief register a simple cloud command without any value additions + * usually called during setup */ template + // defined here instead of in cpp for full flexibility void registerCommand(T* instance, bool (T::*method)(Variant&), const char* module, const char* cmd) { - std::function cb = std::bind(method, instance, _1); - registerCommand(cb, module, cmd); + std::function cb = [instance, method](Variant& v) { + return (instance->*method)(v); + }; + Vector empty = {}; + registerCommand(cb, module, cmd, empty, false, empty, false); } /** - * @brief register a cloud command directly with a std:function call + * @brief register a cloud command with a list of specific text values allowed (value required by default) + * usually called during setup */ - void registerCommand(const std::function& cb, const char* module, const char* cmd); + template + // defined here instead of in cpp for full flexibility + void registerCommandWithTextValues(T* instance, bool (T::*method)(Variant&), const char* module, const char* cmd, const Vector& text_values, bool value_optional = false) { + std::function cb = [instance, method](Variant& v) { + return (instance->*method)(v); + }; + Vector empty = {}; + registerCommand(cb, module, cmd, text_values, false, empty, value_optional); + } /** - * @brief get back a Variant with all the commands + * @brief register a cloud command with numeric values and optionally defined units and value required by default + * usually called during setup */ - Variant getCommands(); + template + // defined here instead of in cpp for full flexibility + void registerCommandWithNumericValues(T* instance, bool (T::*method)(Variant&), const char* module, const char* cmd, const Vector& numeric_units = {}, bool value_optional = false) { + std::function cb = [instance, method](Variant& v) { + return (instance->*method)(v); + }; + Vector empty = {}; + registerCommand(cb, module, cmd, empty, true, numeric_units, value_optional); + } + + /** + * @brief register a cloud command with mixed test and numeric values, optionally defined units, and value required by default + * usually called during setup + */ + template + // defined here instead of in cpp for full flexibility + void registerCommandWithMixedValues(T* instance, bool (T::*method)(Variant&), const char* module, const char* cmd, const Vector& text_values, const Vector& numeric_units = {}, bool value_optional = false) { + std::function cb = [instance, method](Variant& v) { + return (instance->*method)(v); + }; + registerCommand(cb, module, cmd, text_values, true, numeric_units, value_optional); + } /** - * @brief internal function that's registered with the Particle cloud to process user commands + * @brief get back a Variant with all the commands + * this information is stored in a Particle.variable() if var_available_commands is set + * it is optimized for minimal JSON (e.g. true/false = 1/0) to accomodate as many commands as possible */ - int receiveCall (String call); + Variant getCommands(); /** - * @brief parse the function call and store the results in the parsed Variant - * @return the m_commands index of the command that fits the call (or PARSED_ERROR if parsing error) + * @brief internal function that's registered with the Particle cloud to process user commands + * can be called directly for testing purposes */ - size_t parseCall(Variant& parsed); + int receiveCall (String call); }; \ No newline at end of file diff --git a/src/function/function_test.cpp b/src/function/function_test.cpp index e375f89..9426b9e 100644 --- a/src/function/function_test.cpp +++ b/src/function/function_test.cpp @@ -9,7 +9,10 @@ SYSTEM_THREAD(ENABLED); #endif // log handler -SerialLogHandler logHandler(LOG_LEVEL_TRACE); +SerialLogHandler logHandler(LOG_LEVEL_INFO, { // Logging level for non-application messages + { "app", LOG_LEVEL_TRACE } // Logging level for application messages (i.e. debug mode) +}); + namespace LoggerFunctionReturns { inline constexpr Error MY_WARNING = {100, "my favorite warning"}; @@ -17,59 +20,131 @@ namespace LoggerFunctionReturns { } // my component class -class MyModule : LoggerModule { +class MyModule : public LoggerModule { public: + bool auto_test_running = false; + MyModule(const char* name) : LoggerModule(name) {} - // command 1 + // 'autotest' + bool auto_test(Variant& call) { + Log.info("starting auto test suite of commands"); + auto_test_running = true; + return(true); + } + + // 'hello' void registerHelloCommand(LoggerFunction* func, const char* cmd = "hello") { func->registerCommand(this, &MyModule::hello, getName(), cmd); } bool hello(Variant& call) { Log.info("'hello' triggered %s", call.toJSON().c_str()); - call.set("new", 42); + setReturnValue(call, LoggerFunctionReturns::MY_WARNING); return(true); } - // command 2 + // 'whatup' void registerWhatupCommand(LoggerFunction* func, const char* cmd = "whatup") { func->registerCommand(this, &MyModule::whatup, getName(), cmd); } bool whatup(Variant& call) { Log.info("'whatup' triggered: %s", call.toJSON().c_str()); - call.set("new2", 3.234); setReturnValue(call, LoggerFunctionReturns::MY_ERROR); return(false); } + // 'test' + bool test(Variant& call) { + Log.info("'test' with: %s", call.toJSON().c_str()); + return(true); + } + }; -// example -LoggerFunction* func = new LoggerFunction("test"); +// example classes +LoggerFunction* func = new LoggerFunction( + "test", // name of the Particle.function + {"user", "note"}, // parameters (param=) interpreted by the call + true, // whether to log each command using the LoggerPublisher + "commands", // name of the Particle.variable where all available commands are stored (as JSON) + "last_calls" // name of the Particle.variable where the last received function calls are stored (as JSON) +); + + MyModule* mod = new MyModule("mod1"); -String cmds; +String available_cmds; +String last_cmd; // setup void setup() { - // register all commands + // register a suite of test commands + // start auto-test + func->registerCommand(mod, &MyModule::auto_test, mod->getName(), "auto-test"); + + // register all commands defined in the module class (usually all of them defined there) mod->registerHelloCommand(func); mod->registerWhatupCommand(func); mod->registerWhatupCommand(func, "WHATUP"); - // safe commands in the commands variable - cmds = func->getCommands().toJSON(); - Particle.variable("commands", cmds); + // simple command + func->registerCommand(mod, &MyModule::test, mod->getName(), "test1"); + + // command that accepts on/off values + func->registerCommandWithTextValues(mod, &MyModule::test, mod->getName(), "test2", {LoggerFunction::on, LoggerFunction::off}); + + // command that accepts a/b/2 values but providing a value is optional (last param) + func->registerCommandWithTextValues(mod, &MyModule::test, mod->getName(), "test3", {"a", "b", "2"}, true); + + // command that accepts numeric values (no units) + func->registerCommandWithNumericValues(mod, &MyModule::test, mod->getName(), "test4"); + + // command that accepts numeric values with specific units, providing the value is optional (last param) + func->registerCommandWithNumericValues(mod, &MyModule::test, mod->getName(), "test5", {"sec", "min"}, true); + + // command that accepts mixed values with a few specific text values OR numeric values with specific units + func->registerCommandWithMixedValues(mod, &MyModule::test, mod->getName(), "test6", {"manual"}, {"ms", "sec"}); // start listening to function calls func->setup(); } +// testing commands +const Vector calls { + "non-existent-cmd", + "whatup", "WHATUP", + "mod-dne hello", "mod1 hello note=whatever is up with=that user=test user", + "test1", + "test2", "test2 on", "test2 blib", "test2 off extra user=test", + "test3", "test3 2", "test3 2 kg", "test3 b note=hello #3", + "test4", "test4 x", "test4 1kg", "test4 -2.352", + "test5", "test5 y", "test5 4.2", "test5 -42what", "test5 1.3e3 myunit", "test5 -1sec user=test", "test5 24.1 min note=hello", + "test6", "test6 manual", "test6 dne", "test6 42", "test6 -4.2ms" +}; + +unsigned long last_call = 0; +size_t call_i = 0; +const std::chrono::milliseconds wait = 1s; + // loop +// instructions: either call any commands directly with particle call DEVICE test "test4" +// or start this set of auto-tests by calling particle call DEVICE test "auto_test" void loop() { - -} \ No newline at end of file + + // mimic commands via the test calls + if (mod->auto_test_running && millis() - last_call > wait.count()) { + if (call_i >= calls.size()) call_i = 0; + Log.print("\n"); + Log.info("CALL #%d (free mem: %.3f KB): '%s'", call_i, (float) System.freeMemory() / 1024., calls[call_i].c_str()); + func->receiveCall(String(calls[call_i])); + Log.print("\n"); + call_i++; + last_call = millis(); + } + +} + From 09a6d3d0636a99808e2ced4f217a6423d9b09034 Mon Sep 17 00:00:00 2001 From: Sebastian Kopf Date: Tue, 20 May 2025 16:52:37 +0200 Subject: [PATCH 07/19] fix memory leak problems no longer using Variant member variables and smart pointers --- src/function/LoggerFunction.cpp | 146 +++++++++++++++----------------- src/function/LoggerFunction.h | 16 ++-- src/function/function_test.cpp | 5 +- 3 files changed, 80 insertions(+), 87 deletions(-) diff --git a/src/function/LoggerFunction.cpp b/src/function/LoggerFunction.cpp index ac9ca18..1a89e05 100644 --- a/src/function/LoggerFunction.cpp +++ b/src/function/LoggerFunction.cpp @@ -103,104 +103,96 @@ int LoggerFunction::receiveCall (String call) { using namespace LoggerFunctionReturns; - // store call and basic info in current command Variant, then parse it - m_cur_call = Variant(); - m_cur_call.set("call", call.c_str()); - m_cur_call.set("dt", Time.format(Time.now(), "%Y-%m-%d %H:%M:%S %Z")); - m_cur_call.set("lt", "cmd"); // log type - size_t cmd_idx = parseCall(); + // store call and basic info in the Variant and then parse it + // important: this is NOT a member variable on purpose because variants + // that are modified lead to memory fragmentation + Variant parsed; + parsed.set("call", call.c_str()); + parsed.set("dt", Time.format(Time.now(), "%Y-%m-%d %H:%M:%S %Z")); + parsed.set("lt", "cmd"); // log type + size_t cmd_idx = parseCall(parsed); // any issues? if (cmd_idx == PARSING_ERROR) { // parsing error - Log.trace("parsing error: %s", m_cur_call.toJSON().c_str()); - m_cur_call.set("success", false); + Log.trace("parsing error: %s", parsed.toJSON().c_str()); + parsed.set("success", false); } else { // found a command while parsing, execute the callback - Log.trace("execute callback with: %s", m_cur_call.toJSON().c_str()); - bool success = m_commands[cmd_idx].callback(m_cur_call); - m_cur_call.set("success", success); + Log.trace("execute callback with: %s", parsed.toJSON().c_str()); + bool success = m_commands[cmd_idx].callback(parsed); + parsed.set("success", success); // if no ret val set yet - if (success && !hasReturnValue(m_cur_call)) { - setSuccess(m_cur_call); - } else if (!success && !hasReturnValue(m_cur_call)) { - setReturnValue(m_cur_call, CALL_ERR_UNKNOWN); + if (success && !hasReturnValue(parsed)) { + setSuccess(parsed); + } else if (!success && !hasReturnValue(parsed)) { + setReturnValue(parsed, CALL_ERR_UNKNOWN); } } // report command to cloud if logging is on if (m_log) { // FIXME: implement - // LoggerPublisher::queueData(m_cur_call); + // LoggerPublisher::queueData(parsed); // this will also print the published data like below instead of here Log.trace("after callback:"); - Log.print(m_cur_call.toJSON().c_str()); + Log.print(parsed.toJSON().c_str()); Log.print("\n"); } // update last call variable? if (m_var_last_calls != nullptr) { - /* - * i think this leads ot memory fragmentation + // restore from JSON (stored in char to avoid memory fragmentation) + Variant call_log = Variant::fromJSON(m_value_last_calls); // store the last call in the call log - m_call_log.append(m_cur_call); - String call_log_json = m_call_log.toJSON(); - while (call_log_json.length() >= particle::protocol::MAX_FUNCTION_ARG_LENGTH && !m_call_log.isEmpty()) { + call_log.append(parsed); + size_t call_log_size = call_log.toJSON().length(); + while (call_log_size >= particle::protocol::MAX_FUNCTION_ARG_LENGTH && !call_log.isEmpty()) { // remove the oldest entries until they fit - m_call_log.removeAt(0); - call_log_json = m_call_log.toJSON(); + call_log.removeAt(0); + call_log_size = call_log.toJSON().length(); } - // set m_call_log + // set call_log if (Log.isTraceEnabled()) { - Log.trace("new value for Particle.variable('%s') from %d commands in call log stack", m_var_last_calls, m_call_log.size()); - Log.print(call_log_json); + Log.trace("new value for Particle.variable('%s') from %d commands in call log stack", m_var_last_calls, call_log.size()); + Log.print(call_log.toJSON().c_str()); Log.print("\n"); } - // check if call log now fits - if (call_log_json.length() < particle::protocol::MAX_FUNCTION_ARG_LENGTH) { - // call log now fits into the particle variable - snprintf(m_value_last_calls, particle::protocol::MAX_FUNCTION_ARG_LENGTH, "%s", call_log_json.c_str()); - } else { - // call doesn't fit - Variant trunc; - trunc.set("trunc", true); - trunc.set("error", - String::format("call is too long (%d chars) and doesn't fit into the size limit of a particle variable (%d)", - call_log_json.length(), particle::protocol::MAX_FUNCTION_ARG_LENGTH)); - snprintf(m_value_last_calls, particle::protocol::MAX_FUNCTION_ARG_LENGTH, "%s", trunc.toJSON().c_str()); - } - - */ + // assign call log + snprintf(m_value_last_calls, particle::protocol::MAX_FUNCTION_ARG_LENGTH, "%s", call_log.toJSON().c_str()); } // return return value - return(getReturnValue(m_cur_call)); + return(getReturnValue(parsed)); } -size_t LoggerFunction::parseCall() { +size_t LoggerFunction::parseCall(Variant& parsed) { // logger function returns using namespace LoggerFunctionReturns; - // make a mutable local copy for thread safety - char *copy = strdup(m_cur_call.get("call").toString().c_str()); - - // empty call? - if (copy == nullptr) { - setReturnValue(m_cur_call, CALL_ERR_EMPTY); + // get call back out + String call = parsed.get("call").toString(); + if (call.length() == 0) { + setReturnValue(parsed, CALL_ERR_EMPTY); return(PARSING_ERROR); } + // make a mutable local copy for thread safety + // use a smart pointer for memory management + auto copy = std::make_unique(call.length() + 1); + std::strcpy(copy.get(), call.c_str()); + // get module - char *part = strtok(copy, " "); + char *part = strtok(copy.get(), " "); // module or cmd exists? bool mod_found = false; @@ -209,12 +201,12 @@ size_t LoggerFunction::parseCall() { for (size_t i = 0; i < m_commands.size(); ++i) { if (!m_commands[i].use) continue; if (strcmp(part, m_commands[i].module) == 0) { - m_cur_call.set("m", part); + parsed.set("m", part); mod_found = true; } if (strcmp(part, m_commands[i].cmd) == 0) { Log.trace("cmd match: %s", part); - m_cur_call.set("c", part); + parsed.set("c", part); n_cmds_found++; cmd_idx = i; } @@ -226,15 +218,15 @@ size_t LoggerFunction::parseCall() { part = strtok(nullptr, " "); if (part == nullptr) { // no command provided --> error - setReturnValue(m_cur_call, CALL_ERR_CMD_MISS); + setReturnValue(parsed, CALL_ERR_CMD_MISS); return(PARSING_ERROR); } for (size_t i = 0; i < m_commands.size(); ++i) { if (!m_commands[i].use) continue; - if (strcmp(m_cur_call.get("m").asString().c_str(), m_commands[i].module) == 0 && strcmp(part, m_commands[i].cmd) == 0) { + if (strcmp(parsed.get("m").asString().c_str(), m_commands[i].module) == 0 && strcmp(part, m_commands[i].cmd) == 0) { // found the command Log.trace("cmd match: %s", part); - m_cur_call.set("c", part); + parsed.set("c", part); n_cmds_found++; cmd_idx = i; break; @@ -242,19 +234,19 @@ size_t LoggerFunction::parseCall() { } if (n_cmds_found == 0) { // no command of those that are registered for the module fits - setReturnValue(m_cur_call, CALL_ERR_CMD_UNREC); + setReturnValue(parsed, CALL_ERR_CMD_UNREC); return(PARSING_ERROR); } } else if (!mod_found && n_cmds_found == 1) { // all good, found a single command --> set the module accordingly - m_cur_call.set("m", m_commands[cmd_idx].module); + parsed.set("m", m_commands[cmd_idx].module); } else if (!mod_found && n_cmds_found == 0) { // was neither a comand nor a module -> error - setReturnValue(m_cur_call, CALL_ERR_CMD_MOD_UNREC); + setReturnValue(parsed, CALL_ERR_CMD_MOD_UNREC); return(PARSING_ERROR); } else if (!mod_found && n_cmds_found > 1) { // command is ambiguous (might be in multiple modules) - setReturnValue(m_cur_call, CALL_ERR_AMBIGUOUS); + setReturnValue(parsed, CALL_ERR_AMBIGUOUS); return(PARSING_ERROR); } @@ -270,14 +262,14 @@ size_t LoggerFunction::parseCall() { if (part == nullptr) { if (!m_commands[cmd_idx].value_optional) { // no value provided and value is not optional --> error - setReturnValue(m_cur_call, CALL_ERR_VAL_MISS); + setReturnValue(parsed, CALL_ERR_VAL_MISS); return(PARSING_ERROR); } // empty value but that's okay - m_cur_call.set("vtext", Variant()); + parsed.set("vtext", Variant()); } else { // got a value! - m_cur_call.set("vtext", part); + parsed.set("vtext", part); bool valid_value = false; // let's see if it matches any of the allowed text values @@ -293,7 +285,7 @@ size_t LoggerFunction::parseCall() { // nothing found and numeric values not allowed? if (!valid_value && !m_commands[cmd_idx].allow_numeric_values) { // --> error - setReturnValue(m_cur_call, CALL_ERR_VAL_UNREC); + setReturnValue(parsed, CALL_ERR_VAL_UNREC); return(PARSING_ERROR); } } @@ -308,18 +300,18 @@ size_t LoggerFunction::parseCall() { if (num_end == part) { // not a valid number - setReturnValue(m_cur_call, CALL_ERR_VAL_NAN); + setReturnValue(parsed, CALL_ERR_VAL_NAN); return(PARSING_ERROR); } else { // yay number - m_cur_call.set("vnum", number); - Log.trace("numeric value: %s", m_cur_call.get("vnum").toString().c_str()); + parsed.set("vnum", number); + Log.trace("numeric value: %s", parsed.get("vnum").toString().c_str()); } if (*num_end != '\0' && m_commands[cmd_idx].numeric_units.isEmpty()) { // found units directly after the number but none were expected! --> error - m_cur_call.set("u", num_end); - setReturnValue(m_cur_call, CALL_ERR_UNIT_UNEXP); + parsed.set("u", num_end); + setReturnValue(parsed, CALL_ERR_UNIT_UNEXP); return(PARSING_ERROR); } @@ -328,21 +320,21 @@ size_t LoggerFunction::parseCall() { bool valid_units = false; if (*num_end != '\0') { // found units directly after the number - m_cur_call.set("u", num_end); + parsed.set("u", num_end); } else { // fetch next part part = strtok(nullptr, " "); if (part == nullptr) { - setReturnValue(m_cur_call, CALL_ERR_UNIT_MISS); + setReturnValue(parsed, CALL_ERR_UNIT_MISS); return(PARSING_ERROR); } - m_cur_call.set("u", part); + parsed.set("u", part); } // check if the units fit any of the expected for (size_t i = 0; i < m_commands[cmd_idx].numeric_units.size(); ++i) { - if (strcmp(m_cur_call.get("u").asString().c_str(), m_commands[cmd_idx].numeric_units[i].c_str()) == 0) { + if (strcmp(parsed.get("u").asString().c_str(), m_commands[cmd_idx].numeric_units[i].c_str()) == 0) { // found the unit (already correctly assigned "u") - Log.trace("unit match: %s", m_cur_call.get("u").asString().c_str()); + Log.trace("unit match: %s", parsed.get("u").asString().c_str()); valid_units = true; break; } @@ -351,7 +343,7 @@ size_t LoggerFunction::parseCall() { // did we find valid units? if (!valid_units) { // no --> units not recognized - setReturnValue(m_cur_call, CALL_ERR_UNIT_UNREC); + setReturnValue(parsed, CALL_ERR_UNIT_UNREC); return(PARSING_ERROR); } } @@ -376,7 +368,7 @@ size_t LoggerFunction::parseCall() { if (found_param) { // store the previous param in the variant Log.trace("param: %s='%s'", m_params[current_param].c_str(), param_value.c_str()); - m_cur_call.set(m_params[current_param].c_str(), param_value.c_str()); + parsed.set(m_params[current_param].c_str(), param_value.c_str()); } // start the new param found_param = true; @@ -401,7 +393,7 @@ size_t LoggerFunction::parseCall() { if (found_param) { // store the previous param in the variant Log.trace("param: %s='%s'", m_params[current_param].c_str(), param_value.c_str()); - m_cur_call.set(m_params[current_param].c_str(), param_value.c_str()); + parsed.set(m_params[current_param].c_str(), param_value.c_str()); } } diff --git a/src/function/LoggerFunction.h b/src/function/LoggerFunction.h index c070930..c496120 100644 --- a/src/function/LoggerFunction.h +++ b/src/function/LoggerFunction.h @@ -32,7 +32,6 @@ class LoggerFunction { const char* m_var_available_commands; char m_value_available_commands[particle::protocol::MAX_FUNCTION_ARG_LENGTH]; const char* m_var_last_calls; - Variant m_call_log; char m_value_last_calls[particle::protocol::MAX_FUNCTION_ARG_LENGTH]; // call parameters (xyz=, abc=) to interpret/capture @@ -70,19 +69,18 @@ class LoggerFunction { }; // vector of commands + // this is a non-const Variant but since it is not modified during runtime (only during startup) + // it is not a memory problem Vector m_commands; - // current/last parsed function call - Variant m_cur_call; - // register a full cloud command with a std:function call, used by other registerCommmand... calls void registerCommand(const std::function& cb, const char* module, const char* cmd, const Vector& text_values, bool allow_numeric_values, const Vector& numeric_units, bool value_optional); - // parses the function call and store the results in m_cur_call + // parses the function call // returns the m_commands index of the command that fits the call (or PARSED_ERROR if parsing error) - size_t parseCall(); + size_t parseCall(Variant& parsed); public: @@ -121,7 +119,7 @@ class LoggerFunction { std::function cb = [instance, method](Variant& v) { return (instance->*method)(v); }; - Vector empty = {}; + const Vector empty = {}; registerCommand(cb, module, cmd, empty, false, empty, false); } @@ -135,7 +133,7 @@ class LoggerFunction { std::function cb = [instance, method](Variant& v) { return (instance->*method)(v); }; - Vector empty = {}; + const Vector empty = {}; registerCommand(cb, module, cmd, text_values, false, empty, value_optional); } @@ -149,7 +147,7 @@ class LoggerFunction { std::function cb = [instance, method](Variant& v) { return (instance->*method)(v); }; - Vector empty = {}; + const Vector empty = {}; registerCommand(cb, module, cmd, empty, true, numeric_units, value_optional); } diff --git a/src/function/function_test.cpp b/src/function/function_test.cpp index 9426b9e..762c7ec 100644 --- a/src/function/function_test.cpp +++ b/src/function/function_test.cpp @@ -128,7 +128,7 @@ const Vector calls { unsigned long last_call = 0; size_t call_i = 0; -const std::chrono::milliseconds wait = 1s; +const std::chrono::milliseconds wait = 2s; // loop // instructions: either call any commands directly with particle call DEVICE test "test4" @@ -139,8 +139,11 @@ void loop() { if (mod->auto_test_running && millis() - last_call > wait.count()) { if (call_i >= calls.size()) call_i = 0; Log.print("\n"); + uint32_t mem_before = System.freeMemory(); Log.info("CALL #%d (free mem: %.3f KB): '%s'", call_i, (float) System.freeMemory() / 1024., calls[call_i].c_str()); func->receiveCall(String(calls[call_i])); + uint32_t mem_after = System.freeMemory(); + Log.info("FREE MEM loss: %d B", mem_before - mem_after); Log.print("\n"); call_i++; last_call = millis(); From d800bbce02730384d5f89ec9be478834135aebd4 Mon Sep 17 00:00:00 2001 From: Sebastian Kopf Date: Tue, 20 May 2025 17:11:45 +0200 Subject: [PATCH 08/19] updatre functions workflow --- .github/workflows/compile-function.yaml | 2 +- README.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/compile-function.yaml b/.github/workflows/compile-function.yaml index 2fd985a..4c626c6 100644 --- a/.github/workflows/compile-function.yaml +++ b/.github/workflows/compile-function.yaml @@ -5,7 +5,7 @@ name: Compile function on: push: paths: - - src/function + - 'src/function/**' - .github/workflows/compile.yaml - .github/workflows/compile-function.yaml diff --git a/README.md b/README.md index a777c47..fc7a72f 100644 --- a/README.md +++ b/README.md @@ -25,10 +25,10 @@ The following firmware is included in the repository to provide frequently used | Program | *main* branch | *dev* branch | | :------- | :--- | :--- | -| blink | ![blink](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=main) | ![blink-dev](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=dev) | -| i2c_scanner | ![blink](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=main) | ![blink-dev](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=dev) | -| publish | ![publish](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml/badge.svg?branch=main) | ![publish-dev](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml/badge.svg?branch=dev) | -| function | ![function](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml/badge.svg?branch=main) | ![function-dev](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/function-publish.yaml/badge.svg?branch=dev) | +| blink | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=main) | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=dev) | +| i2c_scanner | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_sanner.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=main) | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=dev) | +| publish | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml/badge.svg?branch=main) | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml/badge.svg?branch=dev) | +| function | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml/badge.svg?branch=main) | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml/badge.svg?branch=dev) | ### Compile From 4f6bf9f4fb16974202dc06c116ce99b406d51da4 Mon Sep 17 00:00:00 2001 From: Sebastian Kopf Date: Tue, 20 May 2025 17:15:44 +0200 Subject: [PATCH 09/19] test badge links --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc7a72f..c36f3d2 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ The following firmware is included in the repository to provide frequently used | Program | *main* branch | *dev* branch | | :------- | :--- | :--- | -| blink | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=main) | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=dev) | +| blink | [https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml](![](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=main)) | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=dev) | | i2c_scanner | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_sanner.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=main) | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=dev) | | publish | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml/badge.svg?branch=main) | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml/badge.svg?branch=dev) | | function | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml/badge.svg?branch=main) | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml/badge.svg?branch=dev) | From bf98e49829ad8b9a69dfb9a168e2c38bf120cb6e Mon Sep 17 00:00:00 2001 From: Sebastian Kopf Date: Tue, 20 May 2025 17:17:05 +0200 Subject: [PATCH 10/19] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c36f3d2..ec45076 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ The following firmware is included in the repository to provide frequently used | Program | *main* branch | *dev* branch | | :------- | :--- | :--- | -| blink | [https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml](![](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=main)) | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=dev) | +| blink | [![](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml) | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=dev) | | i2c_scanner | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_sanner.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=main) | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=dev) | | publish | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml/badge.svg?branch=main) | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml/badge.svg?branch=dev) | | function | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml/badge.svg?branch=main) | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml/badge.svg?branch=dev) | From 58a19b7ee830877ed846f4cd67cc56d3fae3fce8 Mon Sep 17 00:00:00 2001 From: Sebastian Kopf Date: Tue, 20 May 2025 17:20:18 +0200 Subject: [PATCH 11/19] udpate badges with proper links --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ec45076..dfaa878 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ -![blink](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=main) -![publish](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml/badge.svg?branch=main) +[![blink](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml) +[![i2c scanner](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_sanner.yaml) +[![publish](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml) +[![function](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml) # LabLoggerLibs @@ -25,10 +27,10 @@ The following firmware is included in the repository to provide frequently used | Program | *main* branch | *dev* branch | | :------- | :--- | :--- | -| blink | [![](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml) | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=dev) | -| i2c_scanner | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_sanner.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=main) | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=dev) | -| publish | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml/badge.svg?branch=main) | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml/badge.svg?branch=dev) | -| function | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml/badge.svg?branch=main) | ![https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml/badge.svg?branch=dev) | +| blink | [![blink](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml) | [![blink](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=dev)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml) | +| i2c_scanner | [![i2c scanner](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_sanner.yaml) | [![i2c scanner](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=dev)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_sanner.yaml) | +| publish | [![publish](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml) | [![publish](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml/badge.svg?branch=dev)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml) | +| function | [![function](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml) | [![function](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml/badge.svg?branch=dev)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml) | ### Compile From 902b2e5fd9dd9f35315897527b6ea1559642a3e8 Mon Sep 17 00:00:00 2001 From: Sebastian Kopf Date: Tue, 20 May 2025 17:22:55 +0200 Subject: [PATCH 12/19] update links --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dfaa878..6e19ffb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![blink](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml) -[![i2c scanner](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_sanner.yaml) +[![i2c scanner](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml) [![publish](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml) [![function](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml) @@ -13,7 +13,7 @@ A BibTeX entry for LaTeX users is ``` @Manual{ - microLogger, + LabLogger, title = {LabLogger: modular data logging for research labs}, author = {Sebastian Kopf}, year = {2025}, @@ -28,7 +28,7 @@ The following firmware is included in the repository to provide frequently used | Program | *main* branch | *dev* branch | | :------- | :--- | :--- | | blink | [![blink](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml) | [![blink](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml/badge.svg?branch=dev)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-blink.yaml) | -| i2c_scanner | [![i2c scanner](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_sanner.yaml) | [![i2c scanner](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=dev)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_sanner.yaml) | +| i2c_scanner | [![i2c scanner](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml) | [![i2c scanner](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml/badge.svg?branch=dev)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-i2c_scanner.yaml) | | publish | [![publish](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml) | [![publish](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml/badge.svg?branch=dev)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-publish.yaml) | | function | [![function](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml/badge.svg?branch=main)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml) | [![function](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml/badge.svg?branch=dev)](https://github.com/KopfLab/LabLoggerLibs/actions/workflows/compile-function.yaml) | From b1bad38d80fd32232710e888a0e86a09bf1ae38b Mon Sep 17 00:00:00 2001 From: Sebastian Kopf Date: Tue, 20 May 2025 17:23:52 +0200 Subject: [PATCH 13/19] test trigger workflow --- .github/workflows/compile-function.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/compile-function.yaml b/.github/workflows/compile-function.yaml index 4c626c6..033ee70 100644 --- a/.github/workflows/compile-function.yaml +++ b/.github/workflows/compile-function.yaml @@ -5,7 +5,7 @@ name: Compile function on: push: paths: - - 'src/function/**' + - 'src/function/*' - .github/workflows/compile.yaml - .github/workflows/compile-function.yaml From 3fa83bd8f236db36cfb1214147cad9baa9045164 Mon Sep 17 00:00:00 2001 From: Sebastian Kopf Date: Tue, 20 May 2025 17:26:32 +0200 Subject: [PATCH 14/19] update paths triggers to correctly include the * file wildcard --- .github/workflows/compile-blink.yaml | 2 +- .github/workflows/compile-controller.yaml | 2 +- .github/workflows/compile-i2c_scanner.yaml | 2 +- .github/workflows/compile-oled.yaml | 2 +- .github/workflows/compile-publish.yaml | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/compile-blink.yaml b/.github/workflows/compile-blink.yaml index 57cd396..34e3a48 100644 --- a/.github/workflows/compile-blink.yaml +++ b/.github/workflows/compile-blink.yaml @@ -5,7 +5,7 @@ name: Compile blink on: push: paths: - - src/blink + - 'src/blink/*' - .github/workflows/compile.yaml - .github/workflows/compile-blink.yaml diff --git a/.github/workflows/compile-controller.yaml b/.github/workflows/compile-controller.yaml index 645c6a5..1fcb671 100644 --- a/.github/workflows/compile-controller.yaml +++ b/.github/workflows/compile-controller.yaml @@ -5,7 +5,7 @@ name: Compile controller on: push: paths: - - src/controller + - 'src/controller/*' - LoggerCore/src - .github/workflows/compile.yaml - .github/workflows/compile-controller.yaml diff --git a/.github/workflows/compile-i2c_scanner.yaml b/.github/workflows/compile-i2c_scanner.yaml index 839e840..2c8e616 100644 --- a/.github/workflows/compile-i2c_scanner.yaml +++ b/.github/workflows/compile-i2c_scanner.yaml @@ -5,7 +5,7 @@ name: Compile i2c scanner on: push: paths: - - src/i2c_scanner + - 'src/i2c_scanner/*' - .github/workflows/compile.yaml - .github/workflows/compile-i2c_scanner.yaml diff --git a/.github/workflows/compile-oled.yaml b/.github/workflows/compile-oled.yaml index e5a15fb..a5ba047 100644 --- a/.github/workflows/compile-oled.yaml +++ b/.github/workflows/compile-oled.yaml @@ -5,7 +5,7 @@ name: Compile oled on: push: paths: - - src/oled + - 'src/oled/*' - .github/workflows/compile.yaml - .github/workflows/compile-oled.yaml diff --git a/.github/workflows/compile-publish.yaml b/.github/workflows/compile-publish.yaml index c39c572..3dfb3f4 100644 --- a/.github/workflows/compile-publish.yaml +++ b/.github/workflows/compile-publish.yaml @@ -5,8 +5,8 @@ name: Compile publish on: push: paths: - - src/publish - - LoggerCore/src + - 'src/publish/*' + - 'LoggerCore/src/*' - .github/workflows/compile.yaml - .github/workflows/compile-publish.yaml From c789d3640786241a3bf95f19255f8b561729c3fe Mon Sep 17 00:00:00 2001 From: Sebastian Kopf Date: Tue, 20 May 2025 17:26:48 +0200 Subject: [PATCH 15/19] update documentation --- src/function/function_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/function/function_test.cpp b/src/function/function_test.cpp index 762c7ec..06fda4f 100644 --- a/src/function/function_test.cpp +++ b/src/function/function_test.cpp @@ -13,13 +13,13 @@ SerialLogHandler logHandler(LOG_LEVEL_INFO, { // Logging level for non-applicati { "app", LOG_LEVEL_TRACE } // Logging level for application messages (i.e. debug mode) }); - +// custom return value examples namespace LoggerFunctionReturns { inline constexpr Error MY_WARNING = {100, "my favorite warning"}; inline constexpr Error MY_ERROR = {-100, "my favorite error"}; } -// my component class +// custom component class examples class MyModule : public LoggerModule { public: From f11f8d89247f40745264d9a1815d66ee4a36ea94 Mon Sep 17 00:00:00 2001 From: Sebastian Kopf Date: Tue, 20 May 2025 17:30:42 +0200 Subject: [PATCH 16/19] workflow trigger test --- src/function/function_test.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/function/function_test.cpp b/src/function/function_test.cpp index 06fda4f..65f2a8e 100644 --- a/src/function/function_test.cpp +++ b/src/function/function_test.cpp @@ -1,3 +1,11 @@ +/** + * test for LoggerFunction class + * to use: + * - flash to device of joice + * - either call any commands directly with particle call DEVICE test "test4" + * - or start the set of auto-tests by calling particle call DEVICE test "auto_test" + */ + #include "Particle.h" #include "LoggerFunction.h" #include "LoggerFunctionReturns.h" @@ -131,8 +139,6 @@ size_t call_i = 0; const std::chrono::milliseconds wait = 2s; // loop -// instructions: either call any commands directly with particle call DEVICE test "test4" -// or start this set of auto-tests by calling particle call DEVICE test "auto_test" void loop() { // mimic commands via the test calls From eb028c2fb90e5acf116a8a6c3c0d47b1ef56abdc Mon Sep 17 00:00:00 2001 From: Sebastian Kopf Date: Tue, 20 May 2025 17:33:02 +0200 Subject: [PATCH 17/19] move sources into logger core --- .github/workflows/compile-function.yaml | 3 ++- {src/function => LoggerCore/src}/LoggerFunction.cpp | 0 {src/function => LoggerCore/src}/LoggerFunction.h | 0 {src/function => LoggerCore/src}/LoggerFunctionReturns.cpp | 0 {src/function => LoggerCore/src}/LoggerFunctionReturns.h | 0 {src/function => LoggerCore/src}/LoggerModule.h | 0 6 files changed, 2 insertions(+), 1 deletion(-) rename {src/function => LoggerCore/src}/LoggerFunction.cpp (100%) rename {src/function => LoggerCore/src}/LoggerFunction.h (100%) rename {src/function => LoggerCore/src}/LoggerFunctionReturns.cpp (100%) rename {src/function => LoggerCore/src}/LoggerFunctionReturns.h (100%) rename {src/function => LoggerCore/src}/LoggerModule.h (100%) diff --git a/.github/workflows/compile-function.yaml b/.github/workflows/compile-function.yaml index 033ee70..8c57b66 100644 --- a/.github/workflows/compile-function.yaml +++ b/.github/workflows/compile-function.yaml @@ -6,6 +6,7 @@ on: push: paths: - 'src/function/*' + - 'LoggerCore/src/*' - .github/workflows/compile.yaml - .github/workflows/compile-function.yaml @@ -19,7 +20,7 @@ jobs: program: - src: 'function' lib: '' - aux: '' + aux: 'LoggerCore/src/LoggerFunctionm* LoggerCore/src/LoggerFunctionReturns* LoggerCore/src/LoggerModule*' # CHANGE platforms as needed platform: - {name: 'p2', version: '6.3.2'} diff --git a/src/function/LoggerFunction.cpp b/LoggerCore/src/LoggerFunction.cpp similarity index 100% rename from src/function/LoggerFunction.cpp rename to LoggerCore/src/LoggerFunction.cpp diff --git a/src/function/LoggerFunction.h b/LoggerCore/src/LoggerFunction.h similarity index 100% rename from src/function/LoggerFunction.h rename to LoggerCore/src/LoggerFunction.h diff --git a/src/function/LoggerFunctionReturns.cpp b/LoggerCore/src/LoggerFunctionReturns.cpp similarity index 100% rename from src/function/LoggerFunctionReturns.cpp rename to LoggerCore/src/LoggerFunctionReturns.cpp diff --git a/src/function/LoggerFunctionReturns.h b/LoggerCore/src/LoggerFunctionReturns.h similarity index 100% rename from src/function/LoggerFunctionReturns.h rename to LoggerCore/src/LoggerFunctionReturns.h diff --git a/src/function/LoggerModule.h b/LoggerCore/src/LoggerModule.h similarity index 100% rename from src/function/LoggerModule.h rename to LoggerCore/src/LoggerModule.h From e4119100b4bf2d2ca1ec84302c91c62e9f393837 Mon Sep 17 00:00:00 2001 From: Sebastian Kopf Date: Tue, 20 May 2025 17:34:12 +0200 Subject: [PATCH 18/19] rename base workflow --- .github/workflows/compile-function.yaml | 2 +- .github/workflows/compile.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/compile-function.yaml b/.github/workflows/compile-function.yaml index 8c57b66..1fe6350 100644 --- a/.github/workflows/compile-function.yaml +++ b/.github/workflows/compile-function.yaml @@ -20,7 +20,7 @@ jobs: program: - src: 'function' lib: '' - aux: 'LoggerCore/src/LoggerFunctionm* LoggerCore/src/LoggerFunctionReturns* LoggerCore/src/LoggerModule*' + aux: 'LoggerCore/src/LoggerFunction* LoggerCore/src/LoggerFunctionReturns* LoggerCore/src/LoggerModule*' # CHANGE platforms as needed platform: - {name: 'p2', version: '6.3.2'} diff --git a/.github/workflows/compile.yaml b/.github/workflows/compile.yaml index 6a449cc..bf4a78d 100644 --- a/.github/workflows/compile.yaml +++ b/.github/workflows/compile.yaml @@ -1,4 +1,4 @@ -name: Base workflow_call for compile actions +name: Base workflow for compile on: workflow_call: From 14aa1a5ac11870a7d8efea79331587996c5887bd Mon Sep 17 00:00:00 2001 From: Sebastian Kopf Date: Tue, 20 May 2025 17:38:05 +0200 Subject: [PATCH 19/19] fixe aux files for workflow --- .github/workflows/compile-function.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/compile-function.yaml b/.github/workflows/compile-function.yaml index 1fe6350..f5c1212 100644 --- a/.github/workflows/compile-function.yaml +++ b/.github/workflows/compile-function.yaml @@ -20,7 +20,7 @@ jobs: program: - src: 'function' lib: '' - aux: 'LoggerCore/src/LoggerFunction* LoggerCore/src/LoggerFunctionReturns* LoggerCore/src/LoggerModule*' + aux: 'LoggerCore/src/LoggerFunction* LoggerCore/src/LoggerModule*' # CHANGE platforms as needed platform: - {name: 'p2', version: '6.3.2'}