From f4476b67b11b411193733d7829b147e34d063773 Mon Sep 17 00:00:00 2001 From: babayaga Date: Fri, 23 May 2025 08:56:55 +0200 Subject: [PATCH] init --- .gitignore | 2 + README.md | 41 +++- library.properties | 11 + package.json | 56 ++---- src/.gitignore | 0 src/Addon.cpp | 18 ++ src/Addon.h | 58 ++++++ src/App.cpp | 149 ++++++++++++++ src/App.h | 131 ++++++++++++ src/Bridge.cpp | 151 ++++++++++++++ src/Bridge.h | 58 ++++++ src/CommandMessage.h | 92 +++++++++ src/Component.cpp | 2 + src/Component.h | 457 ++++++++++++++++++++++++++++++++++++++++++ src/Logger.h | 154 ++++++++++++++ src/Polymech-Base.h | 20 ++ src/SRegister.h | 100 +++++++++ src/SerialMessage.cpp | 119 +++++++++++ src/SerialMessage.h | 92 +++++++++ src/StringUtils.cpp | 76 +++++++ src/StringUtils.h | 54 +++++ src/constants.h | 7 + src/enums.h | 160 +++++++++++++++ src/error_codes.h | 6 + src/macros.h | 390 +++++++++++++++++++++++++++++++++++ src/utils.cpp | 15 ++ src/utils.h | 17 ++ src/xmath.h | 23 +++ src/xstatistics.cpp | 94 +++++++++ src/xstatistics.h | 116 +++++++++++ src/xtimer.h | 136 +++++++++++++ src/xtypes.h | 16 ++ 32 files changed, 2780 insertions(+), 41 deletions(-) create mode 100644 library.properties delete mode 100644 src/.gitignore create mode 100644 src/Addon.cpp create mode 100644 src/Addon.h create mode 100644 src/App.cpp create mode 100644 src/App.h create mode 100644 src/Bridge.cpp create mode 100644 src/Bridge.h create mode 100644 src/CommandMessage.h create mode 100644 src/Component.cpp create mode 100644 src/Component.h create mode 100644 src/Logger.h create mode 100644 src/Polymech-Base.h create mode 100644 src/SRegister.h create mode 100644 src/SerialMessage.cpp create mode 100644 src/SerialMessage.h create mode 100644 src/StringUtils.cpp create mode 100644 src/StringUtils.h create mode 100644 src/constants.h create mode 100644 src/enums.h create mode 100644 src/error_codes.h create mode 100644 src/macros.h create mode 100644 src/utils.cpp create mode 100644 src/utils.h create mode 100644 src/xmath.h create mode 100644 src/xstatistics.cpp create mode 100644 src/xstatistics.h create mode 100644 src/xtimer.h create mode 100644 src/xtypes.h diff --git a/.gitignore b/.gitignore index cab85ca2..59df6ed5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ /coverage *.log .DS_Store +CppPotpourri +./tmp \ No newline at end of file diff --git a/README.md b/README.md index dc27f0f2..4e0bb6d5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,40 @@ -# osr-package-template -Package basics \ No newline at end of file +# OSR Firmware Library + +## Logging + +- [x] Serial ('Arduino-Log') +- [ ] Transport : TCP +- [ ] Flags +- [ ] Template (Plotter) + +## Compononents + +- [ ] Run-Time Flags +- [ ] Network Flags +- [ ] Variable Calling Convention +- [ ] Callback Signature Register +- [ ] Lifecycle: Start, Stop, Reset, Remove, Add +- [ ] Name: Optional +- [ ] Linked List +- [ ] Error Codes + +## Debugging + +## Networking + +- [ ] RS485 Proxy +- [ ] CAN +- [ ] TCP Raw +- [ ] BL + +## Compiling + +- [ ] Platform.IO Module +- [ ] Arduino-Lib Release + +## Documentation + +## References + +- [CPP Commons - CppPotpourri](https://github.com/jspark311/CppPotpourri.git) diff --git a/library.properties b/library.properties new file mode 100644 index 00000000..0695150a --- /dev/null +++ b/library.properties @@ -0,0 +1,11 @@ +name=polymech-base +version=1.0.0 +author=mc007 +maintainer=mc007 +sentence=polymech-base Library +paragraph= +category=Uncategorized +url=https://github/osr_base +architectures=* +includes=PolymechBase.h +depends=ArduinoLog,Vector,Streaming diff --git a/package.json b/package.json index e67de546..e870d5da 100644 --- a/package.json +++ b/package.json @@ -1,45 +1,23 @@ + { - "name": "@plastichub/template", - "description": "", - "version": "0.3.1", - "main": "main.js", - "typings": "index.d.ts", - "publishConfig": { - "access": "public" - }, - "bin": { - "osr-bin": "main.js" - }, - "dependencies": { - "@types/node": "^14.17.5", - "@types/yargs": "^17.0.2", - "chalk": "^2.4.1", - "convert-units": "^2.3.4", - "env-var": "^7.0.1", - "typescript": "^4.3.5", - "yargs": "^14.2.3", - "yargs-parser": "^15.0.3" - }, - "scripts": { - "test": "tsc; mocha --full-trace mocha \"spec/**/*.spec.js\"", - "test-with-coverage": "istanbul cover node_modules/.bin/_mocha -- 'spec/**/*.spec.js'", - "lint": "tslint --project=./tsconfig.json", - "build": "tsc -p .", - "dev": "tsc -p . --declaration -w", - "typings": "tsc --declaration", - "docs": "npx typedoc src/index.ts", - "dev-test-watch": "mocha-typescript-watch" - }, - "homepage": "https://git.osr-plastic.org/plastichub/lib-content", + "name": "polymech", + "description": "A simple and efficient JSON library for embedded C++. ⭐ 6849 stars on GitHub! Supports serialization, deserialization, MessagePack, streams, filtering, and more. Fully tested and documented.", + "homepage": "https://arduinojson.org/?utm_source=meta&utm_medium=library.json", "repository": { "type": "git", - "url": "https://git.osr-plastic.org/plastichub/lib-content.git" + "url": "https://github.com/bblanchon/ArduinoJson.git" }, - "engines": { - "node": ">= 14.0.0" + "version": "7.3.1", + "authors": { + "name": "Benoit Blanchon", + "url": "https://blog.benoitblanchon.fr" }, - "license": "BSD-3-Clause", - "keywords": [ - "typescript" - ] + "export": { + "include": ["src", "examples", "LICENSE.txt", "polymech.h"] + }, + "frameworks": "*", + "platforms": "*", + "build": { + "libArchive": false + } } diff --git a/src/.gitignore b/src/.gitignore deleted file mode 100644 index e69de29b..00000000 diff --git a/src/Addon.cpp b/src/Addon.cpp new file mode 100644 index 00000000..e27c2a1f --- /dev/null +++ b/src/Addon.cpp @@ -0,0 +1,18 @@ +#include "Addon.h" +#include +#include +#include + +Addon *byId(Addons addons, uchar id) +{ + uchar s = addons.size(); + for (uchar i = 0; i < s; i++) + { + Addon *addon = addons[i]; + if (addon->id == id) + { + return addon; + } + } + return NULL; +} \ No newline at end of file diff --git a/src/Addon.h b/src/Addon.h new file mode 100644 index 00000000..c270aa98 --- /dev/null +++ b/src/Addon.h @@ -0,0 +1,58 @@ +#ifndef ADDON_H +#define ADDON_H + +#include +#include + +#include "enums.h" +#include "error_codes.h" +#include "macros.h" +#include "Component.h" + +// back compat +class Addon : public Component +{ + +public: + static const int ADDON_FLAGS_DEFAULT = 1 << OBJECT_RUN_FLAGS::E_OF_LOOP | 1 << OBJECT_RUN_FLAGS::E_OF_INFO | 1 << OBJECT_RUN_FLAGS::E_OF_SETUP; + + /* + Addon( + String _name, + short _id) : Component(_name, _id), + name(_name), + id(_id), + now(0), + flags(COMPONENT_DEFAULT) + { + } + + Addon( + String _name, + short _id, + short _flags) : Component(_name, _id, _flags), + name(_name), + id(_id), + flags(_flags) + { + } + + Addon( + String _name, + short _id, + short _flags, + Addon *_owner) : Component(_name, _id, _flags, _owner), + name(_name), + id(_id), + flags(_flags), + owner(_owner) + { + } + */ +}; + +typedef Vector Addons; +Addon *byId(Addons addons, uchar id); +typedef short (Addon::*AddonFnPtr)(short); + +#endif \ No newline at end of file diff --git a/src/App.cpp b/src/App.cpp new file mode 100644 index 00000000..20eb89f7 --- /dev/null +++ b/src/App.cpp @@ -0,0 +1,149 @@ +#include +#include + +#include "App.h" +#include "Bridge.h" +#include "error_codes.h" +#include "enums.h" +#include "constants.h" +#include "config.h" + +static Component *componentsArray[MAX_COMPONENTS]; +App::App() : Component("APP", COMPONENT_KEY_APP, Component::COMPONENT_DEFAULT) +{ + DEBUG_INTERVAL = DEFAULT_DEBUG_INTERVAL; + components.setStorage(componentsArray); + debugTS = 0; +} +//////////////////////////////////////////////// +// +// Component related functions +// +short App::setup() +{ + short s = components.size(); + for (short i = 0; i < s; i++) + { + Component *component = components[i]; + if (!component) + { + Log.errorln(F("App::setup - Found NULL component at index %d"), i); + continue; + } + if (!component->owner) + { + component->owner = this; + } + if (component->hasFlag(OBJECT_RUN_FLAGS::E_OF_SETUP)) + { + component->setup(); + } + else + { + Log.verboseln(F("App::setup - Skipping setup() for component (no flag): ID=%d, Name=%s"), component->id, component->name.c_str()); + } + } + return E_OK; +} + +short App::registerComponents(Bridge *bridge) +{ + short s = components.size(); + for (short i = 0; i < s; i++) + { + Component *component = components[i]; + if (component->hasFlag(OBJECT_RUN_FLAGS::E_OF_DISABLED)) + { + continue; + } + component->serial_register(bridge); + } + return E_OK; +} + +short App::loop() +{ + timer.tick(); + now = millis(); + short s = components.size(); + for (short i = 0; i < s; i++) + { + Component *component = components[i]; + if (component->hasFlag(OBJECT_RUN_FLAGS::E_OF_LOOP)) + { + component->now = now; + component->loop(); + } + } + debug(); + return E_OK; +} + +short App::numByFlag(ushort flag) +{ + short s = components.size(); + short l = 0; + for (short i = 0; i < s; i++) + { + Component *component = components[i]; + if (!!(component->hasFlag(flag))) + { + l++; + } + } + return l; +} + +short App::setDebugParams(short val0, short val1) +{ + DEBUG_INTERVAL = val0; + return E_OK; +} + +short App::debug() +{ + if (millis() - debugTS < DEBUG_INTERVAL) + { + return E_OK; + } + + debugTS = millis(); + short s = components.size(); + for (short i = 0; i < s; i++) + { + Component *component = components[i]; + if (component->hasFlag(OBJECT_RUN_FLAGS::E_OF_DEBUG)) + { + component->debug(); + } + } + return E_OK; +} + +short App::info() +{ + short s = components.size(); + for (short i = 0; i < s; i++) + { + Component *component = components[i]; + if (component->hasFlag(OBJECT_RUN_FLAGS::E_OF_INFO)) + { + component->info(); + } + } + return E_OK; +} + +Component *App::byId(ushort id) +{ + short s = components.size(); + for (short i = 0; i < s; i++) + { + Component *component = components[i]; + if (component->id == id) + { + return component; + } + } + return NULL; +} diff --git a/src/App.h b/src/App.h new file mode 100644 index 00000000..04c7a607 --- /dev/null +++ b/src/App.h @@ -0,0 +1,131 @@ +#ifndef APP_H +#define APP_H + +#include +#include "xtypes.h" +#include "Component.h" +#include "xtimer.h" +#include "Bridge.h" + +class Bridge; + +/** + * @brief The App class represents the main application component. + * + * This class inherits from the Component class and provides functionality + * for setting up, running the main loop, debugging, and providing information + * about the application. + */ +class App : public Component +{ + +public: + /** + * @brief Default constructor for the App class. + */ + App(); + + App(String _name, short _id, int _flags) : Component(_name, _id, _flags) {} + + /** + * @brief Retrieves a component by its ID. + * + * @param id The ID of the component to retrieve. + * @return A pointer to the component with the specified ID, or nullptr if not found. + */ + virtual Component *byId(ushort id); + + /** + * @brief Performs the setup operations for the application and it's components. + * + * @return A status code indicating the result of the setup operation. + */ + virtual short setup(); + + /** + * @brief Runs the main loop of the application. + * + * @return A status code indicating the result of the loop operation. + */ + virtual short loop(); + + /** + * @brief Performs debugging operations for the application. + * + * @return A status code indicating the result of the debug operation. + */ + virtual short debug(); + + /** + * @brief Retrieves information about the application. + * + * @return A status code indicating the result of the info operation. + */ + virtual short info(); + + /** + * @brief Retrieves the number of components with a specific flag. + * + * @param flag The flag to filter the components by. + * @return The number of components with the specified flag. + */ + virtual short numByFlag(ushort flag); + + /** + * @brief The list of components in the application. + */ + Vector components; + + /** + * @brief The timestamp for the last debug operation. + */ + millis_t debugTS; + + /** + * @brief The timestamp for the last loop operation. + */ + millis_t loopTS; + + /** + * @brief The wait time between loop iterations. + */ + millis_t wait; + + /** + * @brief The timestamp for the last wait operation. + */ + millis_t waitTS; + + /** + * @brief The timer used for scheduling tasks. + */ + Timer<10, millis> timer; + + /** + * @brief The interval for debugging operations. + */ + millis_t DEBUG_INTERVAL; + + ////////////////////////////////////////// + // + // Messaging / Binding + // + /** + * @brief Register all components in `Bridge` + * + * @return A status code indicating the result of the setup operation. + */ + virtual short registerComponents(Bridge *bridge); + + Bridge *bridge; + + virtual void printRegisters() { } + + ////////////////////////////////////////// + // + // Debugging + // + virtual short setDebugParams(short val0, short val1); +}; + +#endif \ No newline at end of file diff --git a/src/Bridge.cpp b/src/Bridge.cpp new file mode 100644 index 00000000..5c660eec --- /dev/null +++ b/src/Bridge.cpp @@ -0,0 +1,151 @@ +#include "macros.h" +#include +#include "Bridge.h" +#include +#include +#include "constants.h" +#include +#include "./enums.h" +#include "config.h" + +#define BRIDGE_DEBUG_REGISTER +#define BRIDGE_DEBUG_CALL_METHOD +#define NB_PAYLOAD_ELEMENTS 3 + +class SComponentInfo; +SComponentInfo *componentsArray[MAX_COMPONENTS]; +Vector componentList; + +Bridge::Bridge(Component *_owner) : Component("Bridge", COMPONENT_KEY_MB_BRIDGE, Component::COMPONENT_DEFAULT, _owner) +{ + componentList.setStorage(componentsArray); +} +short Bridge::setup() +{ + return E_OK; +} + +SComponentInfo *Bridge::hasMethod(ushort id, String methodName) +{ + uchar s = componentList.size(); + for (uchar i = 0; i < s; i++) + { + SComponentInfo *val = componentList.at(i); + if (val->key == id && val->methodName.equals(methodName)) + { + return val; + } + } + return NULL; +} +short Bridge::debug() +{ + return E_OK; +} +short Bridge::list() +{ + uchar s = componentList.size(); + Log.verboseln("Bridge::list - Registered methods: %d", s); + for (uchar i = 0; i < s; i++) + { + SComponentInfo *val = componentList.at(i); + Log.verboseln("\tRegistered Method: %d::%s::%s", val->key, ((Component *)val->instance)->name.c_str(), val->methodName.c_str()); + } + return E_OK; +} + +SComponentInfo *Bridge::registerMemberFunction(ushort id, Component *clazz, char *method, ComponentFnPtr ptr) +{ +#ifdef DISABLE_BRIDGE + // #pragma message("Bridge::registerMemberFunction : Bridge is disabled!") + return NULL; +#endif +#ifndef DISABLE_BRIDGE + if (componentList.size() > MAX_COMPONENTS) + { + Log.errorln("Bridge::registerMemberFunction : Max components reached : %d", MAX_COMPONENTS); + return NULL; + } + SComponentInfo *meth = hasMethod(id, method); + if (meth) + { +#ifdef BRIDGE_DEBUG_REGISTER + Log.verboseln("Register class member: %s::%s already registered!", clazz, method); +#endif + } + else + { + meth = new SComponentInfo(id, clazz, method, ptr); + componentList.push_back(meth); + } + return meth; +#endif + return NULL; +} + +short Bridge::onMessage(int id, E_CALLS verb, E_MessageFlags flags, String user, Component *src) +{ + +#ifdef BRIDGE_DEBUG_CALL_METHOD + Log.verboseln("Bridge::onMessage: %d::%d::%d::%s - User: %s", id, verb, flags, src->name.c_str(), user.c_str()); +#endif + + if (strlen(user.c_str()) == 0) + { + Log.verboseln("Bridge::onMessage: %d : invalid payload", id); + return E_NOT_FOUND; + } + + switch (verb) + { + case E_CALLS::EC_USER: + case E_CALLS::EC_NONE: + case E_CALLS::EC_COMMAND: + case E_CALLS::EC_FUNC: + { + return E_OK; + } + case E_CALLS::EC_METHOD: + { + char *strings[NB_PAYLOAD_ELEMENTS]; + char *ptr = NULL; + byte index = 0; + ptr = strtok(C_STR(user.c_str()), CC_STR(Bridge::METHOD_DELIMITER)); + while (ptr != NULL && index <= NB_PAYLOAD_ELEMENTS) + { + strings[index] = ptr; + index++; + ptr = strtok(NULL, CC_STR(Bridge::METHOD_DELIMITER)); + } + char *_method = strings[0]; + if (strlen(_method) == 0) + { + Log.errorln("Bridge::onMessage: %d : invalid method name", id); + return E_NOT_FOUND; + } + + SComponentInfo *method = hasMethod(id, _method); + if (method) + { + short arg0 = convertTo(CC_STR(strings[1])); + short arg1 = convertTo(CC_STR(strings[2])); + Component *component = (Component *)method->instance; + ComponentFnPtr ptr = method->mPtr; + short ret = (component->*ptr)(arg0, arg1); +#ifdef BRIDGE_DEBUG_CALL_METHOD + Log.verboseln("Called method: %s(%d)::%s with : %d | %d = %d", component->name.c_str(), id, _method, arg0, arg1, ret); +#endif + return ret; + } + else + { +#ifdef BRIDGE_DEBUG_CALL_METHOD + Log.errorln("Method not found: %d::%s - register size %d", id, _method, componentList.size()); + list(); +#endif + } + return E_OK; + } + } + return E_NOT_FOUND; +} diff --git a/src/Bridge.h b/src/Bridge.h new file mode 100644 index 00000000..f79e2695 --- /dev/null +++ b/src/Bridge.h @@ -0,0 +1,58 @@ +#ifndef BRIDGE_H +#define BRIDGE_H + +#include "Component.h" +#include +#include +#include +#include +#include + +class SComponentInfo; + +class SComponentInfo +{ +public: + short key; + void *instance; + String methodName; + ComponentFnPtr mPtr; + SComponentInfo() {} + SComponentInfo(ushort _key, void *_instance, String _methodName, ComponentFnPtr _mPtr) : key(_key), + instance(_instance), + methodName(_methodName), + mPtr(_mPtr) {} +}; + +class Bridge : public Component +{ +public: + Bridge(Component *_owner); + + SComponentInfo *registerMemberFunction( + ushort id, + Component *clazz, + char *method, + ComponentFnPtr ptr); + + SComponentInfo *hasMethod(ushort id, String method); + short onMessage(int id, E_CALLS verb, E_MessageFlags flags, String user, Component *src); + short list(); + // Component implementation + short debug(); + short setup(); + + // --- Methods for ModbusManager --- + + /** + * @brief Retrieves a list of all registered component instances. + * NOTE: This requires careful memory management. Consider returning pointers or references. + * This current implementation returns pointers stored in the internal vector. + * @return A vector of Component pointers. + */ + Vector getAllComponents(); + + static constexpr char *METHOD_DELIMITER = C_STR(":"); +}; +#endif + diff --git a/src/CommandMessage.h b/src/CommandMessage.h new file mode 100644 index 00000000..64d220ea --- /dev/null +++ b/src/CommandMessage.h @@ -0,0 +1,92 @@ +#ifndef COMMAND_MESSAGE_H +#define COMMAND_MESSAGE_H + +#include +#include +#include +#include "./enums.h" + +//#define DEBUG_MESSAGES_PARSE + +#ifdef DEBUG_MESSAGES_PARSE +#define DEBUG_MESSAGES_PARSER(format, ...) Log.verboseln(format, ##__VA_ARGS__) +#else +#define DEBUG_MESSAGES_PARSER(format, ...) +#endif + +#define MESSAGE_TOKENS 4 + +class CommandMessage +{ +public: + short id; + E_CALLS verb; + E_MessageFlags flags; + String payload; + millis_t ts; + + static constexpr char *START_STR = C_STR("<<"); + static constexpr char *END_STR = C_STR(">>"); + static constexpr char *DELIMITER = C_STR(";"); + + static constexpr int START_LENGTH = 2; + static constexpr int END_LENGTH = 2; + + CommandMessage(short _id, E_CALLS _verb, E_MessageFlags _flags) : id(_id), + verb(_verb), + flags(_flags) + { + } + + // Add default constructor + CommandMessage() : id(0), + verb(E_CALLS::EC_NONE), + flags(E_MessageFlags::E_MF_NONE) {} + + void clear() + { + id = 0; + verb = E_CALLS::EC_NONE; + flags = E_MessageFlags::E_MF_NONE; + ts = 0; + } + + bool matches(String string) + { + return string.startsWith(CommandMessage::START_STR) && string.endsWith(CommandMessage::END_STR); + } + + bool parse(String message) + { + clear(); + String data = message.substring(CommandMessage::START_LENGTH, message.length() - CommandMessage::END_LENGTH); + char dataBuffer[data.length() + 1]; + strcpy(dataBuffer, data.c_str()); + const char *strings[MESSAGE_TOKENS]; + char *ptr = NULL; + byte index = 0; + ptr = strtok(dataBuffer, CC_STR(CommandMessage::DELIMITER)); + while (ptr != NULL && index < MESSAGE_TOKENS) + { + strings[index] = ptr; + index++; + ptr = strtok(NULL, CC_STR(CommandMessage::DELIMITER)); + } + + if (index < MESSAGE_TOKENS) + { + DEBUG_MESSAGES_PARSER("CommandMessage::parse: invalid message - incomplete - got %d tokens", index); + return false; + } + + id = atoi(strings[0]); + verb = (E_CALLS)convertTo(strings[1]); + flags = (E_MessageFlags)convertTo(strings[2]); + payload = String(strings[3]); + ts = millis(); + DEBUG_MESSAGES_PARSER("CommandMessage::parse: id: %d, verb: %d, flags: %d, payload: %s", id, verb, flags, payload.c_str()); + return true; + } +}; + +#endif diff --git a/src/Component.cpp b/src/Component.cpp new file mode 100644 index 00000000..f43c4915 --- /dev/null +++ b/src/Component.cpp @@ -0,0 +1,2 @@ +#include "./Component.h" +#include "./Bridge.h" diff --git a/src/Component.h b/src/Component.h new file mode 100644 index 00000000..3c8ee7d2 --- /dev/null +++ b/src/Component.h @@ -0,0 +1,457 @@ +#ifndef COMPONENT_H +#define COMPONENT_H + +#include +#include +#include +#include "./enums.h" +#include "constants.h" +#include "error_codes.h" +#include "macros.h" +#include "xtypes.h" + + +class Bridge; +class ModbusTCP; +class ModbusBlockView; +class MB_Registers; +/** + * @brief The Component class represents a generic component. + */ +class Component +{ +public: + /** + * @brief The default run flags for a component. + */ + static const int COMPONENT_DEFAULT = 1 << OBJECT_RUN_FLAGS::E_OF_LOOP | 1 << OBJECT_RUN_FLAGS::E_OF_SETUP; + + /** + * @brief The default ID for a component. + */ + static const ushort COMPONENT_NO_ID = 0; + + /** + * @brief Default constructor for the Component class. + */ + Component() : name("NO_NAME"), id(0), + flags(OBJECT_RUN_FLAGS::E_OF_NONE), + nFlags(OBJECT_NET_CAPS::E_NCAPS_NONE), owner(nullptr) {} + + /** + * @brief Constructor for the Component class with a specified name. + * @param _name The name of the component. + */ + Component(String _name) : name(_name), id(COMPONENT_NO_ID), + flags(OBJECT_RUN_FLAGS::E_OF_NONE), + nFlags(OBJECT_NET_CAPS::E_NCAPS_NONE), owner(nullptr) {} + + /** + * @brief Constructor for the Component class with a specified name and ID. + * @param _name The name of the component. + * @param _id The ID of the component. + */ + Component(String _name, ushort _id) : name(_name), + id(_id), + flags(OBJECT_RUN_FLAGS::E_OF_NONE), + nFlags(OBJECT_NET_CAPS::E_NCAPS_NONE), owner(nullptr) {} + + /** + * @brief Constructor for the Component class with a specified name, ID, and flags. + * @param _name The name of the component. + * @param _id The ID of the component. + * @param _flags The run flags for the component. + */ + Component(String _name, short _id, int _flags) : name(_name), + id(_id), + flags(_flags), + nFlags(OBJECT_NET_CAPS::E_NCAPS_NONE), owner(nullptr) + { + } + + /** + * @brief Constructor for the Component class with a specified name, ID, flags, and owner. + * @param _name The name of the component. + * @param _id The ID of the component. + * @param _flags The run flags for the component. + * @param _owner The owner of the component. + */ + Component(String _name, ushort _id, uint16_t _flags, Component *_owner) : name(_name), + id(_id), + flags(_flags), + owner(_owner), + nFlags(OBJECT_NET_CAPS::E_NCAPS_NONE) {} + + /** + * @brief Destructor for the Component class. + */ + virtual ~Component() = default; + + /** + * @brief Virtual function to destroy the component. + * @return The error code indicating the success or failure of the operation. + */ + virtual short destroy() { return E_OK; }; + + /** + * @brief Virtual function to debug the component. + * @param stream The stream to output the debug information to. + * @return The error code indicating the success or failure of the operation. + */ + virtual short debug() { return E_OK; }; + + /** + * @brief Virtual function to debug the component. + * @param stream The stream to output the debug information to. + * @return The error code indicating the success or failure of the operation. + */ + virtual short debug(short val0, short val1) { return E_OK; }; + + /** + * @brief Virtual function to display information about the component. + * @param stream The stream to output the information to. + * @return The error code indicating the success or failure of the operation. + */ + virtual short info() { return E_OK; }; + + /** + * @brief Virtual function to display information about the component. + * @param stream The stream to output the information to. + * @return The error code indicating the success or failure of the operation. + */ + virtual short info(short val0, short val1) { return E_OK; }; + + /** + * @brief Virtual function to set up the component. + * @return The error code indicating the success or failure of the operation. + */ + virtual short setup() { return E_OK; }; + + /** + * @brief Virtual function to run the component in a loop. + * @return The error code indicating the success or failure of the operation. + */ + virtual short loop() { return E_OK; }; + + /** + * @brief Checks if the component has a specific flag. + * @param flag The flag to check. + * @return True if the component has the flag, false otherwise. + */ + bool hasFlag(byte flag) + { + return TEST(flags, flag); + } + + /** + * @brief Sets a specific flag for the component. + * @param flag The flag to set. + */ + void setFlag(byte flag) + { + SBI(flags, flag); + } + + /** + * @brief Sets a specific flag for the component. + * @param flag The flag to set. + */ + short toggleFlag(short flag, short value) + { + if (value) + { + SBI(flags, flag); + } + else + { + CBI(flags, flag); + } + return flags; + } + + /** + * @brief Clears a specific flag for the component. + * @param flag The flag to clear. + */ + void clearFlag(byte flag) + { + CBI(flags, flag); + } + + /** + * @brief Enables the component. + */ + void enable() + { + clearFlag(OBJECT_RUN_FLAGS::E_OF_DISABLED); + } + + /** + * @brief Disables the component. + */ + void disable() + { + setFlag(OBJECT_RUN_FLAGS::E_OF_DISABLED); + } + + /** + * @brief Checks if the component is enabled. + * @return True if the component is enabled, false otherwise. + */ + bool enabled() + { + return !hasFlag(OBJECT_RUN_FLAGS::E_OF_DISABLED); + } + + /** + * @brief The name of the component. + */ + String name; + + /** + * @brief The ID of the component. + */ + const ushort id; + + /** + * @brief The run flags for the component. + */ + uint16_t flags; + + /** + * @brief The network capabilities of the component. + */ + uint16_t nFlags; + + /** + * @brief The owner of the component. + */ + Component *owner; + + /** + * @brief The current time in milliseconds. + */ + millis_t now; + + /** + * @brief The last tick time in milliseconds. + */ + millis_t last; + + ////////////////////////////////////////// + // + // Component Hierarchy / Lookup + // + + /** + * @brief Virtual method to retrieve a component managed by this component (or its children) by ID. + * The base implementation returns nullptr. + * Owners like PHApp should override this to provide actual lookup. + * @param id The ID of the component to find. + * @return Pointer to the component if found, nullptr otherwise. + */ + virtual Component* getComponent(short id) { return nullptr; } + + ////////////////////////////////////////// + // + // Messaging + // @todo: extract to a separate class + + /** + * @brief Handles incoming messages. + * + * This function is called when a message is received by the component. + * It processes the message and returns a short value indicating the status of the operation. + * + * @param id The ID of the message. + * @param verb The type of operation to be performed. + * @param flags The flags associated with the message. + * @param user A pointer to user-defined data. + * @param src The source component that sent the message. + * @return A short value indicating the status of the operation. + */ + virtual short onMessage(int id, E_CALLS verb, E_MessageFlags flags, String user, Component *src) + { + return E_OK; + }; + + /** + * @brief Handles incoming messages with a generic void* payload. + * + * @param id The ID of the message. + * @param verb The type of operation to be performed. + * @param flags The flags associated with the message. + * @param user A pointer to user-defined data (nullptr if not provided). + * @param src The source component that sent the message (nullptr if not provided). + * @return A short value indicating the status of the operation. + */ + virtual short onMessage(int id, E_CALLS verb, E_MessageFlags flags, void* user = nullptr, Component *src = nullptr) + { + return E_OK; + }; + /** + * @brief Handles errors. + * @param id The ID of the error. + * @param error The error code. + * @return The error code indicating the success or failure of the operation. + */ + virtual short onError(short id, short error) { return E_OK; }; + + /** + * @brief Handles responses. + * @param id The ID of the response. + * @param response The response code. + * @return The response code indicating the success or failure of the operation. + */ + virtual short onResponse(short id, short response) { return E_OK; }; + + ////////////////////////////////////////// + // + // Binding + + /** + * Registers methods for the component with the specified bridge. + * This method should be overridden by derived classes to provide custom method registration logic. + * + * @param bridge The bridge to register methods with. + * @return The status code indicating the success or failure of the method registration. + */ + virtual short serial_register(Bridge *bridge) { return E_OK; } + + /** + * @brief Sets a specific network capability flag for the component. + * @param flag The network capability flag to set (from OBJECT_NET_CAPS). + */ + void setNetCapability(OBJECT_NET_CAPS flag) + { + SBI(nFlags, flag); + } + + /** + * @brief Checks if the component has a specific network capability flag. + * @param flag The network capability flag to check (from OBJECT_NET_CAPS). + * @return True if the component has the capability, false otherwise. + */ + bool hasNetCapability(OBJECT_NET_CAPS flag) const + { + return TEST(nFlags, flag); + } + + /** + * @brief Clears a specific network capability flag for the component. + * @param flag The network capability flag to clear (from OBJECT_NET_CAPS). + */ + void clearNetCapability(OBJECT_NET_CAPS flag) + { + CBI(nFlags, flag); + } + + ////////////////////////////////////////// + // + // Network Interface (Modbus, Serial, CAN, etc.) + // + + /** + * @brief Called by a network manager (e.g., ModbusTCP) to write a value to this component. + * Derived classes should implement this to handle incoming network writes specific to their function. + * @param address The specific Modbus address being written to within the component's range. + * @param value The value received from the network. + * @return E_OK on success, or an appropriate error code. + */ + virtual short mb_tcp_write(short address, short value) { + return 0; + }; + + /** + * @brief Variant of mb_tcp_write accepting MB_Registers context. + * @param reg The MB_Registers block associated with this write request. + * @param value The value received from the network. + * @return E_OK on success, or an appropriate error code. + */ + virtual short mb_tcp_write(MB_Registers * reg, short value) { + return 0; + }; + + /** + * @brief Called by a network manager (e.g., ModbusTCP) to read a value from this component. + * Derived classes should implement this to provide their current state to the network. + * @param address The specific Modbus address being read within the component's range. + * @return The current value for the given address, or potentially an error indicator. + */ + virtual short mb_tcp_read(short address) { + return 0; + } + + /** + * @brief Variant of mb_tcp_read accepting MB_Registers context. + * @param reg The MB_Registers block associated with this read request. + * @return The current value for the register block, or potentially an error indicator. + */ + virtual short mb_tcp_read(MB_Registers * reg) { + return 0; + } + + /** + * @brief Get the last error code + */ + virtual ushort mb_tcp_error(MB_Registers * reg) { return 0; } + + /** + * @brief Called during setup to allow the component to register its Modbus blocks. + * + * Derived classes should override this. It's recommended to call mb_tcp_blocks() + * inside this function, iterate through the returned view, add the runtime + * component ID to each MB_Registers struct, and then register it with the manager. + * + * @param manager Pointer to the ModbusTCP instance. + */ + virtual void mb_tcp_register(ModbusTCP* manager) const { + // Base implementation does nothing. + } + + /** + * @brief Gets a view of the static Modbus block definitions for this component type. + * + * @note The componentId field within the returned MB_Registers structs may not be + * populated, as the definitions are typically static/constexpr. + * Use mb_tcp_register to handle registration with the correct runtime ID. + * + * @return A ModbusBlockView describing the blocks handled by this component type. + * Default implementation returns an empty view {nullptr, 0}. + */ + virtual ModbusBlockView* mb_tcp_blocks() const { return nullptr; } + + /** + * @brief The Modbus slave ID for this component (satisfies the Modbus interfaces) + */ + ushort slaveId; + +protected: + /** + * @brief Called by derived classes when their internal state changes in a way that should be reflected on the network. + * The base class (or a network manager observing this) should handle queuing the update. + */ + virtual void notifyStateChange() { + // Base implementation could potentially interact with a NetworkManager singleton/instance + // Log.verboseln("Component::notifyStateChange - ID %d", id); + } + +public: + ////////////////////////////////////////// + // + // Component Hierarchy / Lookup + virtual Component *byId(ushort id) { return nullptr; } +}; + +/** + * @brief Function pointer type for component member functions. + */ +typedef short (Component::*ComponentFnPtr)(short, short); +/** + * @brief Function pointer type for component member functions with variable arguments. + */ +typedef short (Component::*ComponentVarArgsFnPtr)(...); + +typedef short (Component::*ComponentRxFn)(short size, uint8_t rxBuffer[]); + +#endif diff --git a/src/Logger.h b/src/Logger.h new file mode 100644 index 00000000..945d8650 --- /dev/null +++ b/src/Logger.h @@ -0,0 +1,154 @@ +#ifndef CIRCULAR_LOG_PRINTER_H +#define CIRCULAR_LOG_PRINTER_H + +#include +#include +#include + +// ----------------------------------------------------------------------------- +// Configuration macros --------------------------------------------------------- +// • LOG_BUFFER_LINES – number of lines kept in RAM (default 100) +// • LOG_BUFFER_LINE_LENGTH – max chars per line incl. null (default 120) +// • LOG_BUFFER_THREAD_SAFE – 1 = wrap writes in a FreeRTOS critical section +// ----------------------------------------------------------------------------- +#ifndef LOG_BUFFER_LINES +#define LOG_BUFFER_LINES 100 +#endif + +#ifndef LOG_BUFFER_LINE_LENGTH +#define LOG_BUFFER_LINE_LENGTH 120 +#endif + +#ifndef LOG_BUFFER_THREAD_SAFE +#define LOG_BUFFER_THREAD_SAFE 1 +#endif + +using LogRingBuffer = char[LOG_BUFFER_LINES][LOG_BUFFER_LINE_LENGTH]; + +// ----------------------------------------------------------------------------- +// CircularLogPrinter ----------------------------------------------------------- +// Stores the last N lines in a ring buffer and optionally mirrors everything to +// an arbitrary Print stream. 100 % reinterpret‑cast‑free for MISRA/CPPCHECK +// compliance: the code never converts between pointer types, only between +// scalar values. +// ----------------------------------------------------------------------------- +class CircularLogPrinter final : public Print { +public: + explicit CircularLogPrinter(Print* out = &Serial) : _out(out) { clear(); } + + void setOutput(Print* out) { _out = out; } + + void clear() { + memset(_buf, 0, sizeof(_buf)); + _head = 0; + _filled = 0; + _idx = 0; + } + + const char* getLine(size_t i) const { + if (i >= lines()) return nullptr; + size_t index = (_head + LOG_BUFFER_LINES - i - 1U) % LOG_BUFFER_LINES; + return _buf[index]; + } + + size_t lines() const { return (_filled < LOG_BUFFER_LINES) ? _filled : LOG_BUFFER_LINES; } + + // Redirect ESP‑IDF logging macros (ESP_LOGx) into this printer + void attachToEspLog() { esp_log_set_vprintf(&vprintfShim); } + + // --------------------------------------------------------------------- + // Print single byte + // --------------------------------------------------------------------- + size_t write(uint8_t c) override { +#if LOG_BUFFER_THREAD_SAFE + portENTER_CRITICAL(&_mux); +#endif + size_t res = writeByte(static_cast(c)); +#if LOG_BUFFER_THREAD_SAFE + portEXIT_CRITICAL(&_mux); +#endif + return res; + } + + // --------------------------------------------------------------------- + // Faster multi‑byte write – no pointer casting + // --------------------------------------------------------------------- + size_t write(const uint8_t* data, size_t size) override { +#if LOG_BUFFER_THREAD_SAFE + portENTER_CRITICAL(&_mux); +#endif + size_t i = 0U; + while (i < size) { + if (static_cast(data[i]) == '\n') { + commitLine(); + ++i; // skip newline + continue; + } + appendChar(static_cast(data[i++])); + } +#if LOG_BUFFER_THREAD_SAFE + portEXIT_CRITICAL(&_mux); +#endif + return size; + } + +private: + // --------------------------------------------------------------------- + // ESP‑IDF vprintf shim – feeds characters individually (no cast needed) + // --------------------------------------------------------------------- + static int vprintfShim(const char* fmt, va_list args) { + char tmp[LOG_BUFFER_LINE_LENGTH]; + int len = vsnprintf(tmp, sizeof(tmp), fmt, args); + if (len < 0) return len; + auto& logger = instance(); + for (int i = 0; i < len; ++i) { + logger.write(static_cast(tmp[i])); + } + return len; + } + + static CircularLogPrinter& instance() { + static CircularLogPrinter logger; + return logger; + } + + void appendChar(char c) { + if (_idx < LOG_BUFFER_LINE_LENGTH - 1U) { + _line[_idx++] = c; + } + } + + void commitLine() { + _line[_idx] = '\0'; + if (_out) _out->println(_line); + strncpy(_buf[_head], _line, LOG_BUFFER_LINE_LENGTH); + _head = (_head + 1U) % LOG_BUFFER_LINES; + if (_filled < LOG_BUFFER_LINES) ++_filled; + _idx = 0U; + } + + size_t writeByte(char c) { + if (c == '\r') return 1U; // ignore CR + if (c == '\n') { + commitLine(); + } else { + appendChar(c); + } + return 1U; + } + + // --------------------------------------------------------------------- + // Members + // --------------------------------------------------------------------- + LogRingBuffer _buf{}; + Print* _out = nullptr; + size_t _head = 0U; + size_t _filled = 0U; + char _line[LOG_BUFFER_LINE_LENGTH]; + size_t _idx = 0U; +#if LOG_BUFFER_THREAD_SAFE + portMUX_TYPE _mux = portMUX_INITIALIZER_UNLOCKED; +#endif +}; + +#endif // CIRCULAR_LOG_PRINTER_H diff --git a/src/Polymech-Base.h b/src/Polymech-Base.h new file mode 100644 index 00000000..c044c44c --- /dev/null +++ b/src/Polymech-Base.h @@ -0,0 +1,20 @@ +#ifndef _osr_base_h +#define _osr_base_h + +#if defined(ARDUINO) && ARDUINO >= 100 + #include "arduino.h" +#else + #include "WProgram.h" +#endif + +int init_osr_base(); + +#endif + +#include "Component.h" +#include "Addon.h" +#include "App.h" +#include "constants.h" +#include "macros.h" +#include "xtypes.h" + diff --git a/src/SRegister.h b/src/SRegister.h new file mode 100644 index 00000000..8adf117c --- /dev/null +++ b/src/SRegister.h @@ -0,0 +1,100 @@ +#ifndef SREGISTER_H +#define SREGISTER_H + +#include + +template +class ShiftRegister { +private: + T data; + uint8_t length; + uint8_t position; + int mapping[MAX_LENGTH]; + +public: + ShiftRegister() : data(0), length(0), position(0) { + for (uint8_t i = 0; i < MAX_LENGTH; i++) { + mapping[i] = 0; + } + } + + ShiftRegister(const int (&_mapping)[MAX_LENGTH]) : data(0), length(0), position(0) { + for (uint8_t i = 0; i < MAX_LENGTH; i++) { + mapping[i] = _mapping[i]; + } + } + + uint8_t add(T newData){ + data = (data << 1) | (newData & 1); + length = length == MAX_LENGTH ? MAX_LENGTH : length + 1; + return position; + } + + void map(uint8_t pos, int value) { + mapping[pos] = value; + } + + int val() const { + return mapping[position]; + } + + uint8_t incr() { + position = (position + 1) % MAX_LENGTH; + data = (data << 1) | ((data >> (MAX_LENGTH - 1)) & 1); + return position; + } + + uint8_t decr() { + position = (position == 0) ? MAX_LENGTH - 1 : position - 1; + data = (data >> 1) | ((data & 1) << (MAX_LENGTH - 1)); + return position; + } + + uint8_t pos() const { + return position; + } + + uint8_t reset() { + data = 0; + length = 0; + position = 0; + return position; + } + + uint8_t move(int direction, int steps) { + for (int i = 0; i < steps; i++) { + if (direction > 0) { + incr(); + } else if (direction < 0) { + decr(); + } + } + return position; + } + + bool isEnd() const { + return position == (length - 1) % MAX_LENGTH; + } + + ShiftRegister& operator++() { + incr(); + return *this; + } + + ShiftRegister& operator--() { + decr(); + return *this; + } +}; + + +/* +Write a class, for C++, implementing a shift register +- as template, to specify the type for the storage, eg: int, unsigned int, ... +- implement this methods : incr, decr, position, reset, move(int direction, int steps), isEnd +- let me specify the max. length of the register +- dont use std, at all +- use bit shift operators +- return the current position in all methods, using uint8_t as return type +*/ +#endif \ No newline at end of file diff --git a/src/SerialMessage.cpp b/src/SerialMessage.cpp new file mode 100644 index 00000000..f9f7e474 --- /dev/null +++ b/src/SerialMessage.cpp @@ -0,0 +1,119 @@ +#include +#include + +#include "macros.h" +#include +#include + +#include "SerialMessage.h" +#include "CommandMessage.h" +#include "bridge.h" + +void printStringAsHex(const char *str) +{ + Serial.print(" :: "); + for (int i = 0; str[i] != '\0'; i++) + { + Serial.print("0x"); + if (str[i] < 0x10) + { + Serial.print("0"); + } + Serial.print(str[i], HEX); + Serial.print(" "); + } + Serial.println(" :: "); +} + +#ifndef SERIAL_COMMAND_PARSE_INTERVAL +#define SERIAL_COMMAND_PARSE_INTERVAL 50 +#endif + +// #define DEBUG_SERIAL_MESSAGES + +#ifdef DEBUG_SERIAL_MESSAGES +#define _DEBUG_MESSAGE_HANDLING(format, ...) Log.verboseln(format, ##__VA_ARGS__) +#else +#define _DEBUG_MESSAGE_HANDLING(format, ...) +#endif + +// static CommandMessage *_messages[10]; // Removed unused static array + +short SerialMessage::setup() +{ + // messages.setStorage(_messages); // Removed - uses deleted static array + // msg = new CommandMessage(0, E_CALLS::EC_NONE, E_MessageFlags::E_MF_NONE); // Removed - msg is now an object member + return E_OK; +} + +// Removed unused parse method implementation +// CommandMessage *SerialMessage::parse(char *string) +// { +// return NULL; +// } + +short SerialMessage::debug() +{ + return E_OK; +} + +String SerialMessage::readStringFromSerial() +{ + String message; + while (stream.available()) + { + message = stream.readString(); + message.trim(); + } + return message; +} + +CommandMessage *SerialMessage::read() +{ + String message = readStringFromSerial(); + if (!message.length()) + { + return NULL; + } + _DEBUG_MESSAGE_HANDLING("SerialMessage::read: message: %s", message.c_str()); + // Use the member 'msg' object directly + if (!this->msg.matches(message.c_str())) + { + _DEBUG_MESSAGE_HANDLING("SerialMessage::read : Invalid message - no match : %s", message.c_str()); + return NULL; + } + + bool validMessage = this->msg.parse(message); + if (!validMessage) + { + _DEBUG_MESSAGE_HANDLING("SerialMessage::read : invalid message - incomplete : %s", message.c_str()); + return NULL; + } + // Return address of the member object + return &this->msg; +} + +short SerialMessage::loop() +{ + if (now - lastRead < SERIAL_COMMAND_PARSE_INTERVAL) + { + return E_OK; + } + lastRead = now; + // Use the pointer returned by read() + CommandMessage *parsedMsg = read(); + if (parsedMsg) + { + _DEBUG_MESSAGE_HANDLING("SerialMessage::loop:received message %d :: %s", parsedMsg->id, parsedMsg->payload.c_str()); + if (owner) + { + // Pass the pointer to the parsed message (which is &this->msg) + owner->onMessage(parsedMsg->id, parsedMsg->verb, parsedMsg->flags, parsedMsg->payload, this); + } + else + { + _DEBUG_MESSAGE_HANDLING("SerialMessage::loop: have no owner"); + } + } + return E_OK; +} \ No newline at end of file diff --git a/src/SerialMessage.h b/src/SerialMessage.h new file mode 100644 index 00000000..6cb7167b --- /dev/null +++ b/src/SerialMessage.h @@ -0,0 +1,92 @@ +#ifndef SERIAL_MESSAGE_H +#define SERIAL_MESSAGE_H + +#include +#include +#include // Add for Stream, String etc. if not implicit + +#include "xtypes.h" +#include "Component.h" +#include "CommandMessage.h" +#include "config.h" + +#ifndef SERIAL_RX_BUFFER_SIZE +#define SERIAL_RX_BUFFER_SIZE 256 +#endif + +class CommandMessage; +/** + * @class SerialMessage + * @brief Represents a serial message component. + * + * The SerialMessage class is a component that handles serial communication. + * It provides methods for reading, parsing, and handling command messages. + */ +class SerialMessage : public Component +{ +public: + + /** + * @brief Constructs a SerialMessage object. + * + * @param _stream The serial stream used for communication. + * @param _owner The owner component of this SerialMessage. + */ + SerialMessage(Stream &_stream, Component *_owner) : Component("SerialMessage", COMPONENT_KEY_MB_SERIAL, Component::COMPONENT_DEFAULT, _owner), + stream(_stream), + _rxBufferIdx(0) // Initialize buffer index + { + lastRead = 0; + _rxBuffer[0] = '\0'; // Initialize buffer (Use single quotes for single char) + } + + /** + * @brief Reads a command message from the serial stream. + * + * @return A pointer to the CommandMessage object read from the stream. + */ + CommandMessage *read(); // Removed - logic moved to loop() + + /** + * @brief Parses a string into a CommandMessage object. + * + * @param string The string to be parsed. + * @return A pointer to the parsed CommandMessage object. + */ + CommandMessage *parse(char *string); // Removed unused method + + /** + * @brief Executes the main loop of the SerialMessage component. + * + * @return A status code indicating the result of the loop execution. + */ + short loop(); + /** + * @brief Performs the setup operations for the SerialMessage component. + * + * @return A status code indicating the result of the setup. + */ + short setup(); + + /** + * @brief Performs debug operations for the SerialMessage component. + * @return A status code indicating the result of the debug operation. + */ + short debug(); + +private: + // Private members and methods + // Vector messages; // Removed unused vector + millis_t lastRead; + char _rxBuffer[SERIAL_RX_BUFFER_SIZE]; // Receive buffer + uint8_t _rxBufferIdx; // Current index in buffer + bool _serialConnected = false; // Track connection status + +protected: + Stream &stream; + CommandMessage msg; // Removed - Will create local instance in loop + String readStringFromSerial(); // Removed - logic moved to loop() + char _delimiter = '\n'; // Message delimiter (Use single quotes) +}; + +#endif \ No newline at end of file diff --git a/src/StringUtils.cpp b/src/StringUtils.cpp new file mode 100644 index 00000000..8b0ecfdc --- /dev/null +++ b/src/StringUtils.cpp @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include "StringUtils.h" +#include "xtypes.h" + + +// Specialization for int +template<> int convertTo(cchar* str) { + return atoi(str); +} +template<> short convertTo(cchar* str) { + return atoi(str); +} + +// Specialization for long int +template<> long int convertTo(cchar* str) { + return strtol(str, nullptr, 10); +} + +// Specialization for long long int +/* +template<> long long int convertTo(cchar* str) { + return strtoll(str, nullptr, 10); +} +*/ + +// Specialization for float +template<> float convertTo(cchar* str) { + return (float)atof(str); +} + +// Specialization for bool +template<> bool convertTo(cchar* str) { + return (strcmp(str, "true") == 0 || strcmp(str, "false") == 0 || strcmp(str, "1") == 0 || strcmp(str, "0") == 0 || strcmp(str, "on") == 0 || strcmp(str, "off") == 0); +} + +// Specializations for int, long long int, float, bool, and possibly other types... + +// Function to detect if a string is a valid integer +bool isInteger(cchar* str) { + if (*str == '-' || *str == '+') str++; // Skip sign + while (*str) { + if (!isdigit(*str)) return false; // Check if all characters are digits + str++; + } + return true; +} + +// Function to detect if a string is a valid float +bool isFloat(cchar* str) { + bool hasPeriod = false; + if (*str == '-' || *str == '+') str++; // Skip sign + while (*str) { + if (*str == '.') { + if (hasPeriod) return false; // Only one period allowed + hasPeriod = true; + } else if (!isdigit(*str)) { + return false; // All other characters must be digits + } + str++; + } + return true; +} + +E_VALUE_TYPE detectType(cchar* str) { + if (isInteger(str)) { + return TYPE_INT; + } else if (isFloat(str)) { + return TYPE_FLOAT; + }else if (*str != '\0') { // Non-empty string considered as TYPE_STRING + return TYPE_STRING; + } + return TYPE_UNKNOWN; +} \ No newline at end of file diff --git a/src/StringUtils.h b/src/StringUtils.h new file mode 100644 index 00000000..8e7dc86c --- /dev/null +++ b/src/StringUtils.h @@ -0,0 +1,54 @@ +#ifndef STRINGUTILS_H +#define STRINGUTILS_H + +#include +#include +#include "./xtypes.h" + +// stricmp() is not available in Visual Studio 2013 and earlier +# if defined(_MSC_VER) +#define strtok_r strtok_s +# ifndef _CRT_SECURE_NO_DEPRECATE +# define _CRT_SECURE_NO_DEPRECATE (1) +# endif +# pragma warning(disable : 4996) +# endif + +typedef enum E_VALUE_TYPE { + TYPE_INT, + TYPE_FLOAT, + TYPE_BOOL, + TYPE_STRING, + TYPE_UNKNOWN +} E_VALUE_TYPE; + + +// Function to convert a string to a native type +template T convertTo(cchar* str); + +// Specialization for int +template<> int convertTo(cchar* str); + + +// Specialization for long int +template<> long int convertTo(cchar* str); + +// Specialization for long long int +//template<> long long int convertTo(cchar* str); + + + +// Specialization for float +template<> float convertTo(cchar* str); + +// Specialization for bool +template<> bool convertTo(cchar* str); + +// Function to detect if a string is a valid integer +bool isInteger(cchar* str); +// Function to detect if a string is a valid float +bool isFloat(cchar* str); + +E_VALUE_TYPE detectType(cchar* str); + +#endif diff --git a/src/constants.h b/src/constants.h new file mode 100644 index 00000000..2fb8aea0 --- /dev/null +++ b/src/constants.h @@ -0,0 +1,7 @@ +#ifndef CONSTANTS_H +#define CONSTANTS_H + +#define MAX_COMPONENTS 30 +#define DEFAULT_DEBUG_INTERVAL 1000 + +#endif \ No newline at end of file diff --git a/src/enums.h b/src/enums.h new file mode 100644 index 00000000..6c73f8cf --- /dev/null +++ b/src/enums.h @@ -0,0 +1,160 @@ +#ifndef ENUMS_H +#define ENUMS_H + +typedef enum COMPONENT_KEY_BASE +{ + COMPONENT_KEY_NONE = 0, + COMPONENT_KEY_APP = 1, + COMPONENT_KEY_MB_SERIAL = 2, + COMPONENT_KEY_MB_BRIDGE = 3 +} COMPONENT_KEY_BASE; + +////////////////////////////////////////////////////////////////// +// +// Error Codes + +#define E_OK 0 //all good +#define E_SKIP 10 //all good +#define E_QUEUED 20 //all good +#define E_INVALID_PARAMETER 30 //all good +#define E_NO_SUCH_PID 2001 //cant find PID +#define E_QUERY_BUFFER_END 99 //have no free query buffer slot + +#define E_USER_START 1000 // base offset for sub system errors + +// PID +#define E_PID_CUSTOM 2000 // Custom PID error +#define E_PID_TIMEOUT E_PID_CUSTOM + 2 // Timeout +#define E_PID_OVERHEAT E_PID_CUSTOM + 3 // Timeout + +// WiFi/Network Errors +#define E_WIFI_CONNECTION_FAILED 5001 +#define E_WEBSERVER_INIT_FAILED 5002 +#define E_DEPENDENCY_NOT_MET 5003 + +#define E_ALLOCATION_FAILED 200 +#define E_SERIAL_INIT_FAILED 201 +#define E_NOT_INITIALIZED 202 +#define E_NOT_IMPLEMENTED 204 +#define E_MODBUS_INIT_FAILED 205 // Added Modbus RTU Init Error + +#define E_WARNING 10 +#define E_NOT_FOUND 20 +#define E_FATAL 100 + +typedef enum OBJECT_RUN_FLAGS +{ + E_OF_NONE = 0, /**< No run flag */ + E_OF_DEBUG = 1, /**< Enable debug call, per frame */ + E_OF_INFO = 2, /**< Enable info print during boot */ + E_OF_LOOP = 3, /**< Enable loop invocation on the main loop */ + E_OF_DISABLED = 4, /**< Disable component entirely */ + E_OF_SETUP = 5, /**< Enable setup invocation during boot */ + E_OF_MAIN = 6, /**< reserved */ + E_OF_BRIDGE = 7 /**< Component registers methods on the Bridge */ +} OBJECT_RUN_FLAGS; + +typedef enum OBJECT_NET_CAPS +{ + E_NCAPS_NONE = 0, + E_NCAPS_MODBUS = 1, + E_NCAPS_SERIAL = 2, +} OBJECT_NET_CAPS; + +typedef enum MB_REGISTER_MODE +{ + E_MB_REGISTER_MODE_NONE = 0, + E_MB_REGISTER_MODE_READ = 1, + E_MB_REGISTER_MODE_WRITE = 2, + E_MB_REGISTER_MODE_READ_WRITE = 3, +} MB_REGISTER_MODE; + +typedef enum E_CALLS +{ + /**< Call a global registered command . */ + EC_NONE = 0x00000000, + /**< Call a global registered command . */ + EC_COMMAND = 0x00000001, + /**< Call component method (See Bridge::registerMemberFunction & Component::serial_register) */ + EC_METHOD = 0x00000002, + /**< Function call type. */ + EC_FUNC = 0x00000004, + /**< User-defined call type. */ + EC_USER = 0x00000008 +} E_CALLS; + +/** + * @brief Enumeration representing different message flags. + * + * This enumeration defines the possible flags that can be associated with a message. + * Each flag represents a different state or attribute of the message. + */ +typedef enum E_MessageFlags +{ + /** read coils or digital outputs + MB_FC_READ_DISCRETE_INPUT = 2, //!< FCT=2 -> read digital inputs + MB_FC_READ_REGISTERS = 3, //!< FCT=3 -> read registers or analog outputs + // MB_FC_READ_HOLDING_REGISTERS = 3, //!< FCT=3 -> read registers or analog outputs + MB_FC_READ_INPUT_REGISTER = 4, //!< FCT=4 -> read analog inputs + MB_FC_WRITE_COIL = 5, //!< FCT=5 -> write single coil or output + MB_FC_WRITE_REGISTER = 6, //!< FCT=6 -> write single register + MB_FC_WRITE_MULTIPLE_COILS = 15, //!< FCT=15 -> write multiple coils or outputs + MB_FC_WRITE_MULTIPLE_REGISTERS = 16 //!< FCT=16 -> write multiple registers +} MB_FC; + +typedef enum MODBUS_ERRORS +{ + MODBUS_ERROR_NONE = 0, /**< No error */ + MODBUS_ERROR_ILLEGAL_FUNCTION = 1, /**< Illegal function error */ + MODBUS_ERROR_ILLEGAL_DATA_ADDRESS = 2, /**< Illegal data address error */ + MODBUS_ERROR_ILLEGAL_DATA_VALUE = 3, /**< Illegal data value error */ + MODBUS_ERROR_SLAVE_DEVICE_FAILURE = 4, /**< Slave device failure error */ + MODBUS_ERROR_ACKNOWLEDGE = 5, /**< Acknowledge error */ + MODBUS_ERROR_SLAVE_DEVICE_BUSY = 6, /**< Slave device busy error */ + MODBUS_ERROR_MEMORY_PARITY = 8, /**< Memory parity error */ + MODBUS_ERROR_GATEWAY_PATH_UNAVAILABLE = 10, /**< Gateway path unavailable error */ + MODBUS_ERROR_GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 11 /**< Gateway target device failed to respond error */ +} MODBUS_ERRORS; + +/** + * @brief Defines the type of Modbus object. + */ +enum E_ModbusType { + MB_TYPE_UNKNOWN = 0, + MB_TYPE_COIL = 1, + MB_TYPE_DISCRETE_INPUT = 2, + MB_TYPE_HOLDING_REGISTER = 3, + MB_TYPE_INPUT_REGISTER = 4 +}; + +/** + * @brief Defines the access mode for a Modbus address range. + */ +enum E_ModbusAccess { + MB_ACCESS_NONE = 0, + MB_ACCESS_READ_ONLY = 1, + MB_ACCESS_WRITE_ONLY = 2, + MB_ACCESS_READ_WRITE = 3 +}; + +#endif diff --git a/src/error_codes.h b/src/error_codes.h new file mode 100644 index 00000000..a3f3c22b --- /dev/null +++ b/src/error_codes.h @@ -0,0 +1,6 @@ +#ifndef ERROR_CODES_H +#define ERROR_CODES_H + + + +#endif diff --git a/src/macros.h b/src/macros.h new file mode 100644 index 00000000..2d7a29b6 --- /dev/null +++ b/src/macros.h @@ -0,0 +1,390 @@ +#ifndef MACROS_H +#define MACROS_H + +#include "./xtypes.h" + +// Macros for adding +#define INC_0 1 +#define INC_1 2 +#define INC_2 3 +#define INC_3 4 +#define INC_4 5 +#define INC_5 6 +#define INC_6 7 +#define INC_7 8 +#define INC_8 9 +#define INCREMENT_(n) INC_##n +#define INCREMENT(n) INCREMENT_(n) + +// Macros for subtracting +#define DEC_1 0 +#define DEC_2 1 +#define DEC_3 2 +#define DEC_4 3 +#define DEC_5 4 +#define DEC_6 5 +#define DEC_7 6 +#define DEC_8 7 +#define DEC_9 8 +#define DECREMENT_(n) DEC_##n +#define DECREMENT(n) DECREMENT_(n) + +// compiler - & C quirks +#ifndef FORCE_INLINE + #define FORCE_INLINE __attribute__((always_inline)) inline +#endif + +#define _UNUSED __attribute__((unused)) + +// fallback noop +#define NOOP (void(0)) + +// Option testing + +// Use NUM_ARGS(__VA_ARGS__) to get the number of variadic arguments +#define _NUM_ARGS(_, n, m, l, k, j, i, h, g, f, e, d, c, b, a, Z, Y, X, W, V, U, T, S, R, Q, P, O, N, M, L, K, J, I, H, G, F, E, D, C, B, A, OUT, ...) OUT +#define NUM_ARGS(V...) _NUM_ARGS(0, V, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + +// Use TWO_ARGS(__VA_ARGS__) to get whether there are 1, 2, or >2 arguments +#define _TWO_ARGS(_, n, m, l, k, j, i, h, g, f, e, d, c, b, a, Z, Y, X, W, V, U, T, S, R, Q, P, O, N, M, L, K, J, I, H, G, F, E, D, C, B, A, OUT, ...) OUT +#define TWO_ARGS(V...) _TWO_ARGS(0, V, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 0) + +// Concatenate symbol names, without or with pre-expansion +#define _CAT(a, V...) a##V +#define CAT(a, V...) _CAT(a, V) + +// Macros to chain up to 40 conditions +#define _DO_1(W, C, A) (_##W##_1(A)) +#define _DO_2(W, C, A, B) (_##W##_1(A) C _##W##_1(B)) +#define _DO_3(W, C, A, V...) (_##W##_1(A) C _DO_2(W, C, V)) +#define _DO_4(W, C, A, V...) (_##W##_1(A) C _DO_3(W, C, V)) +#define _DO_5(W, C, A, V...) (_##W##_1(A) C _DO_4(W, C, V)) +#define _DO_6(W, C, A, V...) (_##W##_1(A) C _DO_5(W, C, V)) +#define _DO_7(W, C, A, V...) (_##W##_1(A) C _DO_6(W, C, V)) +#define _DO_8(W, C, A, V...) (_##W##_1(A) C _DO_7(W, C, V)) +#define _DO_9(W, C, A, V...) (_##W##_1(A) C _DO_8(W, C, V)) +#define _DO_10(W, C, A, V...) (_##W##_1(A) C _DO_9(W, C, V)) +#define _DO_11(W, C, A, V...) (_##W##_1(A) C _DO_10(W, C, V)) +#define _DO_12(W, C, A, V...) (_##W##_1(A) C _DO_11(W, C, V)) +#define _DO_13(W, C, A, V...) (_##W##_1(A) C _DO_12(W, C, V)) +#define _DO_14(W, C, A, V...) (_##W##_1(A) C _DO_13(W, C, V)) +#define _DO_15(W, C, A, V...) (_##W##_1(A) C _DO_14(W, C, V)) +#define _DO_16(W, C, A, V...) (_##W##_1(A) C _DO_15(W, C, V)) +#define _DO_17(W, C, A, V...) (_##W##_1(A) C _DO_16(W, C, V)) +#define _DO_18(W, C, A, V...) (_##W##_1(A) C _DO_17(W, C, V)) +#define _DO_19(W, C, A, V...) (_##W##_1(A) C _DO_18(W, C, V)) +#define _DO_20(W, C, A, V...) (_##W##_1(A) C _DO_19(W, C, V)) +#define _DO_21(W, C, A, V...) (_##W##_1(A) C _DO_20(W, C, V)) +#define _DO_22(W, C, A, V...) (_##W##_1(A) C _DO_21(W, C, V)) +#define _DO_23(W, C, A, V...) (_##W##_1(A) C _DO_22(W, C, V)) +#define _DO_24(W, C, A, V...) (_##W##_1(A) C _DO_23(W, C, V)) +#define _DO_25(W, C, A, V...) (_##W##_1(A) C _DO_24(W, C, V)) +#define _DO_26(W, C, A, V...) (_##W##_1(A) C _DO_25(W, C, V)) +#define _DO_27(W, C, A, V...) (_##W##_1(A) C _DO_26(W, C, V)) +#define _DO_28(W, C, A, V...) (_##W##_1(A) C _DO_27(W, C, V)) +#define _DO_29(W, C, A, V...) (_##W##_1(A) C _DO_28(W, C, V)) +#define _DO_30(W, C, A, V...) (_##W##_1(A) C _DO_29(W, C, V)) +#define _DO_31(W, C, A, V...) (_##W##_1(A) C _DO_30(W, C, V)) +#define _DO_32(W, C, A, V...) (_##W##_1(A) C _DO_31(W, C, V)) +#define _DO_33(W, C, A, V...) (_##W##_1(A) C _DO_32(W, C, V)) +#define _DO_34(W, C, A, V...) (_##W##_1(A) C _DO_33(W, C, V)) +#define _DO_35(W, C, A, V...) (_##W##_1(A) C _DO_34(W, C, V)) +#define _DO_36(W, C, A, V...) (_##W##_1(A) C _DO_35(W, C, V)) +#define _DO_37(W, C, A, V...) (_##W##_1(A) C _DO_36(W, C, V)) +#define _DO_38(W, C, A, V...) (_##W##_1(A) C _DO_37(W, C, V)) +#define _DO_39(W, C, A, V...) (_##W##_1(A) C _DO_38(W, C, V)) +#define _DO_40(W, C, A, V...) (_##W##_1(A) C _DO_39(W, C, V)) +#define __DO_N(W, C, N, V...) _DO_##N(W, C, V) +#define _DO_N(W, C, N, V...) __DO_N(W, C, N, V) +#define DO(W, C, V...) (_DO_N(W, C, NUM_ARGS(V), V)) + +#define FIRST(a,...) a +#define SECOND(a,b,...) b +#define THIRD(a,b,c,...) c + +#define IS_PROBE(V...) SECOND(V, 0) // Get the second item passed, or 0 +#define PROBE() ~, 1 // Second item will be 1 if this is passed +#define _NOT_0 PROBE() +#define NOT(x) IS_PROBE(_CAT(_NOT_, x)) // NOT('0') gets '1'. Anything else gets '0'. +#define _BOOL(x) NOT(NOT(x)) // _BOOL('0') gets '0'. Anything else gets '1'. + +#define IF_ELSE(TF) _IF_ELSE(_BOOL(TF)) +#define _IF_ELSE(TF) _CAT(_IF_, TF) + +#define _IF_1(V...) V _IF_1_ELSE +#define _IF_0(...) _IF_0_ELSE + +#define _IF_1_ELSE(...) +#define _IF_0_ELSE(V...) V + +// Recognize "true" values: blank, 1, 0x1, true +#define _ISENA_ ~, 1 +#define _ISENA_1 ~, 1 +#define _ISENA_0x1 ~, 1 +#define _ISENA_true ~, 1 +#define _ISENA(V...) IS_PROBE(V) + +// Macros to evaluate simple option switches +#define _ENA_1(O) _ISENA(CAT(_IS, CAT(ENA_, O))) +#define _DIS_1(O) NOT(_ENA_1(O)) +#define ENABLED(V...) DO(ENA, &&, V) + +#ifndef MACRO_DISABLED +#define MACRO_DISABLED(V...) DO(DIS, &&, V) +#endif + +#define ANY(V...) !MACRO_DISABLED(V) +#define ALL ENABLED +#define NONE MACRO_DISABLED +#define COUNT_ENABLED(V...) DO(ENA, +, V) +#define MANY(V...) (COUNT_ENABLED(V) > 1) + +// #define _CAT(a, ...) a ## __VA_ARGS__ +// #define SWITCH_ENABLED_ 1 +// #define ENABLED(b) _CAT(SWITCH_ENABLED_, b) + +// time +#define PENDING(NOW, SOON) ((long)(NOW - (SOON)) < 0) +#define ELAPSED(NOW, SOON) (!PENDING(NOW, SOON)) + +#define MMM_TO_MMS(MM_M) ((MM_M) / 60.0f) +#define MMS_TO_MMM(MM_S) ((MM_S) * 60.0f) +#define HOUR_MS ((millis_t)1000 * (millis_t)(60 * 60)) +#define MIN_MS ((millis_t)1000 * (millis_t)(60)) +#define SECS ((millis_t)1000) + +// bit masks +#undef __BV +#define __BV(b) (1 << (b)) +#define TEST(n, b) !!((n) & __BV(b)) +#define SBI(n, b) (n |= __BV(b)) +#define CBI(n, b) (n &= ~__BV(b)) +#define SET_BIT_TO(N, B, TF) \ + do \ + { \ + if (TF) \ + SBI(N, B); \ + else \ + CBI(N, B); \ + } while (0) + +#define _BV32(b) (1UL << (b)) +#define TEST32(n, b) !!((n) & _BV32(b)) +#define SBI32(n, b) (n |= _BV32(b)) +#define CBI32(n, b) (n &= ~_BV32(b)) +#define SIGN(a) ((a > 0) - (a < 0)) + +// math basics + +#ifndef WITHIN +#define WITHIN(V, L, H) ((V) >= (L) && (V) <= (H)) +#endif + +#define NUMERIC(a) WITHIN(a, '0', '9') +#define DECIMAL(a) (NUMERIC(a) || a == '.') +#define NUMERIC_SIGNED(a) (NUMERIC(a) || (a) == '-' || (a) == '+') +#define DECIMAL_SIGNED(a) (DECIMAL(a) || (a) == '-' || (a) == '+') +#define COUNT(a) (sizeof(a) / sizeof(*a)) + +#ifndef ZERO +#define ZERO(a) memset(a, 0, sizeof(a)) +#endif + +#define COPY(a, b) memcpy(a, b, MIN(sizeof(a), sizeof(b))) + +// #define M_PI 3.14159265358979323846f +#define RADIANS(d) ((d) * M_PI / 180.0f) +#define DEGREES(r) ((r) * 180.0f / M_PI) +#define CEILING(x, y) (((x) + (y) - 1) / (y)) + +#define UNEAR_ZERO(x) ((x) < 0.000001f) +#define NEAR_ZERO(x) WITHIN(x, -0.000001f, 0.000001f) +#define NEAR(x, y) NEAR_ZERO((x) - (y)) + +#define RECIPROCAL(x) (NEAR_ZERO(x) ? 0 : (1 / float(x))) +#define FIXFLOAT(f) ({__typeof__(f) _f = (f); _f + (_f < 0 ? -0.0000005f : 0.0000005f); }) + +// value helper macros +#define ISEOL(C) ((C) == '\n' || (C) == '\r') +#define HEXCHR(a) (NUMERIC(a) ? (a) - '0' : WITHIN(a, 'a', 'f') ? ((a) - 'a' + 10) \ + : WITHIN(a, 'A', 'F') ? ((a) - 'A' + 10) \ + : -1) + +// Macros for initializing arrays +#define ARRAY_6(v1, v2, v3, v4, v5, v6, ...) \ + { \ + v1, v2, v3, v4, v5, v6 \ + } +#define ARRAY_5(v1, v2, v3, v4, v5, ...) \ + { \ + v1, v2, v3, v4, v5 \ + } +#define ARRAY_4(v1, v2, v3, v4, ...) \ + { \ + v1, v2, v3, v4 \ + } +#define ARRAY_3(v1, v2, v3, ...) \ + { \ + v1, v2, v3 \ + } +#define ARRAY_2(v1, v2, ...) \ + { \ + v1, v2 \ + } +#define ARRAY_1(v1, ...) \ + { \ + v1 \ + } + +#define _ARRAY_N(N, ...) ARRAY_##N(__VA_ARGS__) +#define ARRAY_N(N, ...) _ARRAY_N(N, __VA_ARGS__) + +#define SPACE(A) " " << A << " " + +//////////////////////////////////////////////////////////////////////// +// +// Macros from Marlin / compat + +#ifndef MARLIN_HEX_VERSION + +// Clock speed factors +#if !defined(CYCLES_PER_MICROSECOND) && !defined(__STM32F1__) +#define CYCLES_PER_MICROSECOND (F_CPU / 1000000UL) // 16 or 20 on AVR +#endif + +// Nanoseconds per cycle +#define NANOSECONDS_PER_CYCLE (1000000000.0 / F_CPU) + +// Macros to make a string from a macro +#define STRINGIFY_(M) #M +#define STRINGIFY(M) STRINGIFY_(M) + +// Macros to chain up to 40 conditions + +#ifdef __cplusplus +#ifndef _MINMAX_H_ +#define _MINMAX_H_ + +extern "C++" +{ + + // C++11 solution that is standards compliant. Return type is deduced automatically + template + static constexpr N _MIN(const N val) { return val; } + template + static constexpr N _MAX(const N val) { return val; } + template + static constexpr auto _MIN(const L lhs, const R rhs) -> decltype(lhs + rhs) + { + return lhs < rhs ? lhs : rhs; + } + template + static constexpr auto _MAX(const L lhs, const R rhs) -> decltype(lhs + rhs) + { + return lhs > rhs ? lhs : rhs; + } + template + static constexpr const T _MIN(T V, Ts... Vs) { return _MIN(V, _MIN(Vs...)); } + template + static constexpr const T _MAX(T V, Ts... Vs) { return _MAX(V, _MAX(Vs...)); } +} + +#endif + +// Allow manipulating enumeration value like flags without ugly cast everywhere +#define ENUM_FLAGS(T) \ + FORCE_INLINE constexpr T operator&(T x, T y) { return static_cast(static_cast(x) & static_cast(y)); } \ + FORCE_INLINE constexpr T operator|(T x, T y) { return static_cast(static_cast(x) | static_cast(y)); } \ + FORCE_INLINE constexpr T operator^(T x, T y) { return static_cast(static_cast(x) ^ static_cast(y)); } \ + FORCE_INLINE constexpr T operator~(T x) { return static_cast(~static_cast(x)); } \ + FORCE_INLINE T &operator&=(T &x, T y) { return x &= y; } \ + FORCE_INLINE T &operator|=(T &x, T y) { return x |= y; } \ + FORCE_INLINE T &operator^=(T &x, T y) { return x ^= y; } + +// C++11 solution that is standard compliant. is not available on all platform +namespace Private +{ + template + struct enable_if + { + }; + template + struct enable_if + { + typedef _Tp type; + }; + + template + struct is_same + { + enum + { + value = false + }; + }; + template + struct is_same + { + enum + { + value = true + }; + }; + + template + struct first_type_of + { + typedef T type; + }; + template + struct first_type_of + { + typedef T type; + }; +} +// C++11 solution using SFINAE to detect the existence of a member in a class at compile time. +// It creates a HasMember structure containing 'value' set to true if the member exists +#define HAS_MEMBER_IMPL(Member) \ + namespace Private \ + { \ + template \ + struct HasMember_##Member \ + { \ + template \ + static Yes &test(decltype(&C::Member)); \ + template \ + static No &test(...); \ + enum \ + { \ + value = sizeof(test(0)) == sizeof(Yes) \ + }; \ + }; \ + } + +// Call the method if it exists, but do nothing if it does not. The method is detected at compile time. +// If the method exists, this is inlined and does not cost anything. Else, an "empty" wrapper is created, returning a default value +#define CALL_IF_EXISTS_IMPL(Return, Method, ...) \ + HAS_MEMBER_IMPL(Method) \ + namespace Private \ + { \ + template \ + FORCE_INLINE typename enable_if::value, Return>::type Call_##Method(T *t, Args... a) { return static_cast(t->Method(a...)); } \ + _UNUSED static Return Call_##Method(...) { return __VA_ARGS__; } \ + } +#define CALL_IF_EXISTS(Return, That, Method, ...) \ + static_cast(Private::Call_##Method(That, ##__VA_ARGS__)) + +#else + +#endif // __cplusplus + +#endif // MARLIN_HEX_VERSION + +/////////////////////////////////////////////////////// +// +// String Conversion / Casting (WString.h substitute) +// +#define CC_STR(s) (const char *)s +#define C_STR(s) (char *)s + +#endif // MACROS_H diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 00000000..229518e3 --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,15 @@ +#include "utils.h" + +void printHex(uint8_t *data, uint8_t length) +{ + for (int i = 0; i < length; i++) + { + if (data[i] < 0x10) + { + Serial.print("0"); + } + Serial.print(data[i], HEX); + Serial.print(" : "); + } + Serial.println(" "); +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 00000000..9dc1280a --- /dev/null +++ b/src/utils.h @@ -0,0 +1,17 @@ +#ifndef UTILS_H +#define UTILS_H + +#include +#include + +void printHex(uint8_t *data, uint8_t length); +/* +template T normalizeValue(T value, T maximum) { + if (value < 0 || value > maximum) { + return 0; // Or any other appropriate value + } + return value / maximum; +} +*/ + +#endif \ No newline at end of file diff --git a/src/xmath.h b/src/xmath.h new file mode 100644 index 00000000..a532fec3 --- /dev/null +++ b/src/xmath.h @@ -0,0 +1,23 @@ +#ifndef XMATH_H +#define XMATH_H + +#ifdef __cplusplus +extern "C++" +{ +template + T clamp(const T &value, const T &low, const T &high) + { + return value < low ? low : (value > high ? high : value); + } +} + +#endif + +#define RANGE(i, min, max) ((i > min) && (i < max)) + +#define NCLAMP(x, min, max) \ + (((max) == (min)) ? 0.0f : \ + clamp((static_cast((x) - (min))) / ((max) - (min)), 0.0f, 1.0f)) + + +#endif \ No newline at end of file diff --git a/src/xstatistics.cpp b/src/xstatistics.cpp new file mode 100644 index 00000000..4c9faa23 --- /dev/null +++ b/src/xstatistics.cpp @@ -0,0 +1,94 @@ +#include "xstatistics.h" +#include + +Statistic::Statistic() +{ + clear(); +} + +// resets all counters +void Statistic::clear() +{ + _cnt = 0; + _sum = 0; + _min = 0; + _max = 0; +#ifdef STAT_USE_STDEV + _ssqdif = 0.0; // not _ssq but sum of square differences + // which is SUM(from i = 1 to N) of + // (f(i)-_ave_N)**2 +#endif +} + +// adds a new value to the data-set +void Statistic::add(const float value) +{ + if (_cnt == 0) + { + _min = value; + _max = value; + } + else + { + if (value < _min) + _min = value; + else if (value > _max) + _max = value; + } + _sum += value; + _cnt++; + +#ifdef STAT_USE_STDEV + if (_cnt > 1) + { + float _store = (_sum / _cnt - value); + _ssqdif = _ssqdif + _cnt * _store * _store / (_cnt - 1); + // ~10% faster but limits the amount of samples to 65K as _cnt*_cnt overflows + // float _store = _sum - _cnt * value; + // _ssqdif = _ssqdif + _store * _store / (_cnt*_cnt - _cnt); + } +#endif +} + +// returns the average of the data-set added sofar +float Statistic::average() const +{ + if (_cnt == 0) + return NAN; // original code returned 0 + return _sum / _cnt; +} + +// Population standard deviation = s = sqrt [ S ( Xi - � )2 / N ] +// http://www.suite101.com/content/how-is-standard-deviation-used-a99084 +#ifdef STAT_USE_STDEV + +float Statistic::variance() const +{ + if (_cnt == 0) + return NAN; // otherwise DIV0 error + return _ssqdif / _cnt; +} + +float Statistic::mean() const +{ + if (_cnt == 0) + return NAN; // otherwise DIV0 error + return this->sum() / _cnt; +} + +float Statistic::pop_stdev() const +{ + if (_cnt == 0) + return NAN; // otherwise DIV0 error + return sqrt(_ssqdif / _cnt); +} + +float Statistic::unbiased_stdev() const +{ + if (_cnt < 2) + return NAN; // otherwise DIV0 error + return sqrt(_ssqdif / (_cnt - 1)); +} + +#endif +// END OF FILE \ No newline at end of file diff --git a/src/xstatistics.h b/src/xstatistics.h new file mode 100644 index 00000000..7e4aa021 --- /dev/null +++ b/src/xstatistics.h @@ -0,0 +1,116 @@ +#ifndef XSTATISTICS_H +#define XSTATISTICS_H + +#include +#include + +#define STAT_USE_STDEV + +#ifdef __cplusplus + +// C++11 solution that is standards compliant. Return type is deduced automatically +template +static inline constexpr auto MIN(const L lhs, const R rhs) -> decltype(lhs + rhs) +{ + return lhs < rhs ? lhs : rhs; +} +template +static inline constexpr auto MAX(const L lhs, const R rhs) -> decltype(lhs + rhs) +{ + return lhs > rhs ? lhs : rhs; +} +template +static inline constexpr const T ABS(const T v) +{ + return v >= 0 ? v : -v; +} +#else +// Using GCC extensions, but Travis GCC version does not like it and gives +// "error: statement-expressions are not allowed outside functions nor in template-argument lists" +#define MIN(a, b) \ + ({__typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a < _b ? _a : _b; }) + +#define MAX(a, b) \ + ({__typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a > _b ? _a : _b; }) + +#define ABS(a) \ + ({__typeof__(a) _a = (a); \ + _a >= 0 ? _a : -_a; }) + +#endif + +class Statistic +{ +public: + Statistic(); // "switches on/off" stdev run time + void clear(); // "switches on/off" stdev run time + void add(const float); + + // returns the number of values added + uint32_t count() const { return _cnt; }; // zero if empty + float sum() const { return _sum; }; // zero if empty + float minimum() const { return _min; }; // zero if empty + float maximum() const { return _max; }; // zero if empty + float average() const; // NAN if empty + float mean() const; // zero if empty + +#ifdef STAT_USE_STDEV + float variance() const; // NAN if empty + float pop_stdev() const; // population stdev // NAN if empty + float unbiased_stdev() const; // NAN if empty +#endif + +protected: + uint32_t _cnt; + float _sum; + float _min; + float _max; +#ifdef STAT_USE_STDEV + float _ssqdif; // sum of squares difference +#endif +}; + +/** + * Returns the kth q-quantile. + * @link http://en.wikipedia.org/wiki/Quantile#Quantiles_of_a_population + * ie: median is 1st 2-quantile + * ie: upper quartile is 3rd 4-quantile + * @return {Number} q-quantile of values. + */ +/* +const quantile = (arr: number[], i: number, n: number) => { + if (i === 0) return Math.min.apply(null, arr); + if (i === n) return Math.max.apply(null, arr); + + let sorted = arr.slice(0); + sorted.sort((a, b) => a - b); + let index = sorted.length * i / n; + + if (index % 1 === 0) { + return 0.5 * sorted[index - 1] + 0.5 * sorted[index]; + } + + return sorted[~~index]; +}; + +export const median = (arr: number[]) => quantile(arr, 1, 2); + +export const sum = (arr: number[]) => arr.reduce((a, b) => a + b, 0); + +export const mean = (arr: number[]) => sum(arr) / arr.length; + + +// sqare errors along mean +const sdiff = (arr: number[], mean: number) => arr.map((v) => + Math.pow(v - mean, 2) +); + +export const standardDeviation = (arr: number[]) => + Math.sqrt(mean(sdiff(arr, mean(arr)))); +*/ + +#endif \ No newline at end of file diff --git a/src/xtimer.h b/src/xtimer.h new file mode 100644 index 00000000..2fc2d475 --- /dev/null +++ b/src/xtimer.h @@ -0,0 +1,136 @@ +#ifndef XTIMER_H +#define XTIMER_H + +#if defined(ARDUINO) && ARDUINO >= 100 +#include +#else +#include +#endif + +#include "macros.h" + +#ifndef TIMER_MAX_TASKS +#define TIMER_MAX_TASKS 0x10 +#endif + +template < + size_t max_tasks = TIMER_MAX_TASKS, /* max allocated tasks */ + unsigned long (*time_func)() = millis /* time function for timer */ + > +class Timer +{ +public: + typedef bool (*handler_t)(void *opaque); /* task handler func signature */ + /* Calls handler with opaque as argument in delay units of time */ + bool + in(unsigned long delay, handler_t h, void *opaque = NULL) + { + return add_task(time_func(), delay, h, opaque); + } + + /* Calls handler with opaque as argument at time */ + bool + at(unsigned long time, handler_t h, void *opaque = NULL) + { + const unsigned long now = time_func(); + return add_task(now, time - now, h, opaque); + } + + /* Calls handler with opaque as argument every interval units of time */ + bool + every(unsigned long interval, handler_t h, void *opaque = NULL) + { + return add_task(time_func(), interval, h, opaque, interval); + } + + + /* Ticks the timer forward - call this function in loop() */ + void + tick() + { + tick(time_func()); + } + + /* Ticks the timer forward - call this function in loop() */ + inline void + tick(unsigned long t) + { + for (size_t i = 0; i < max_tasks; ++i) + { + struct task *const task = &tasks[i]; + const unsigned long duration = t - task->start; + + if (task->handler && duration >= task->expires) + { + task->repeat = task->handler(task->opaque) && task->repeat; + + if (task->repeat) + task->start = t; + else + remove(task); + }else{ + } + } + } + +private: + struct task + { + handler_t handler; /* task handler callback func */ + void *opaque; /* argument given to the callback handler */ + unsigned long start, + expires, /* when the task expires */ + repeat; /* repeat task */ + } tasks[max_tasks]; + + inline void + remove(struct task *task) + { + task->handler = NULL; + task->opaque = NULL; + task->start = 0; + task->expires = 0; + task->repeat = 0; + } + + inline struct task * + next_task_slot() + { + for (size_t i = 0; i < max_tasks; ++i) + { + struct task *const slot = &tasks[i]; + if (slot->handler == NULL) + return slot; + } + + return NULL; + } + + inline struct task * + add_task(unsigned long start, unsigned long expires, + handler_t h, void *opaque, bool repeat = 0) + { + struct task *const slot = next_task_slot(); + + if (!slot){ + return NULL; + } + + slot->handler = h; + slot->opaque = opaque; + slot->start = start; + slot->expires = expires; + slot->repeat = repeat; + + return slot; + } +}; + +/* create a timer with the default settings */ +inline Timer<> +timer_create_default() +{ + return Timer<>(); +} + +#endif diff --git a/src/xtypes.h b/src/xtypes.h new file mode 100644 index 00000000..e4746146 --- /dev/null +++ b/src/xtypes.h @@ -0,0 +1,16 @@ +#ifndef TYPES_H +#define TYPES_H + +#include + +typedef const char cchar; +typedef unsigned char uchar; + +typedef unsigned long millis_t; +typedef unsigned short ushort; +typedef unsigned long ulong; +typedef long int lint; +typedef long long int llint; + + +#endif \ No newline at end of file