This commit is contained in:
lovebird 2025-05-23 08:56:55 +02:00
parent e97a664658
commit f4476b67b1
32 changed files with 2780 additions and 41 deletions

2
.gitignore vendored
View File

@ -2,3 +2,5 @@
/coverage
*.log
.DS_Store
CppPotpourri
./tmp

View File

@ -1,3 +1,40 @@
# osr-package-template
Package basics
# 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)

11
library.properties Normal file
View File

@ -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

View File

@ -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
}
}

0
src/.gitignore vendored
View File

18
src/Addon.cpp Normal file
View File

@ -0,0 +1,18 @@
#include "Addon.h"
#include <Streaming.h>
#include <Vector.h>
#include <Arduino.h>
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;
}

58
src/Addon.h Normal file
View File

@ -0,0 +1,58 @@
#ifndef ADDON_H
#define ADDON_H
#include <WString.h>
#include <Vector.h>
#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<Addon *> Addons;
Addon *byId(Addons addons, uchar id);
typedef short (Addon::*AddonFnPtr)(short);
#endif

149
src/App.cpp Normal file
View File

@ -0,0 +1,149 @@
#include <Vector.h>
#include <Arduino.h>
#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;
}

131
src/App.h Normal file
View File

@ -0,0 +1,131 @@
#ifndef APP_H
#define APP_H
#include <Vector.h>
#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<Component *> 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

151
src/Bridge.cpp Normal file
View File

@ -0,0 +1,151 @@
#include "macros.h"
#include <ArduinoLog.h>
#include "Bridge.h"
#include <Vector.h>
#include <Streaming.h>
#include "constants.h"
#include <StringUtils.h>
#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<SComponentInfo *> 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<short>(CC_STR(strings[1]));
short arg1 = convertTo<short>(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;
}

58
src/Bridge.h Normal file
View File

@ -0,0 +1,58 @@
#ifndef BRIDGE_H
#define BRIDGE_H
#include "Component.h"
#include <WString.h>
#include <xtypes.h>
#include <enums.h>
#include <macros.h>
#include <Vector.h>
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<Component*> getAllComponents();
static constexpr char *METHOD_DELIMITER = C_STR(":");
};
#endif

92
src/CommandMessage.h Normal file
View File

@ -0,0 +1,92 @@
#ifndef COMMAND_MESSAGE_H
#define COMMAND_MESSAGE_H
#include <Vector.h>
#include <ArduinoLog.h>
#include <StringUtils.h>
#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<int>(strings[1]);
flags = (E_MessageFlags)convertTo<long int>(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

2
src/Component.cpp Normal file
View File

@ -0,0 +1,2 @@
#include "./Component.h"
#include "./Bridge.h"

457
src/Component.h Normal file
View File

@ -0,0 +1,457 @@
#ifndef COMPONENT_H
#define COMPONENT_H
#include <WString.h>
#include <ArduinoLog.h>
#include <Vector.h>
#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

154
src/Logger.h Normal file
View File

@ -0,0 +1,154 @@
#ifndef CIRCULAR_LOG_PRINTER_H
#define CIRCULAR_LOG_PRINTER_H
#include <Arduino.h>
#include <Print.h>
#include <esp_log.h>
// -----------------------------------------------------------------------------
// 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% reinterpretcastfree 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 ESPIDF 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<char>(c));
#if LOG_BUFFER_THREAD_SAFE
portEXIT_CRITICAL(&_mux);
#endif
return res;
}
// ---------------------------------------------------------------------
// Faster multibyte 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<char>(data[i]) == '\n') {
commitLine();
++i; // skip newline
continue;
}
appendChar(static_cast<char>(data[i++]));
}
#if LOG_BUFFER_THREAD_SAFE
portEXIT_CRITICAL(&_mux);
#endif
return size;
}
private:
// ---------------------------------------------------------------------
// ESPIDF 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<uint8_t>(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

20
src/Polymech-Base.h Normal file
View File

@ -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"

100
src/SRegister.h Normal file
View File

@ -0,0 +1,100 @@
#ifndef SREGISTER_H
#define SREGISTER_H
#include <stdint.h>
template <typename T, uint8_t MAX_LENGTH>
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

119
src/SerialMessage.cpp Normal file
View File

@ -0,0 +1,119 @@
#include <Vector.h>
#include <ArduinoLog.h>
#include "macros.h"
#include <constants.h>
#include <xtypes.h>
#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;
}

92
src/SerialMessage.h Normal file
View File

@ -0,0 +1,92 @@
#ifndef SERIAL_MESSAGE_H
#define SERIAL_MESSAGE_H
#include <Vector.h>
#include <ArduinoLog.h>
#include <Arduino.h> // 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<CommandMessage *> 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

76
src/StringUtils.cpp Normal file
View File

@ -0,0 +1,76 @@
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "StringUtils.h"
#include "xtypes.h"
// Specialization for int
template<> int convertTo<int>(cchar* str) {
return atoi(str);
}
template<> short convertTo<short>(cchar* str) {
return atoi(str);
}
// Specialization for long int
template<> long int convertTo<long int>(cchar* str) {
return strtol(str, nullptr, 10);
}
// Specialization for long long int
/*
template<> long long int convertTo<long long int>(cchar* str) {
return strtoll(str, nullptr, 10);
}
*/
// Specialization for float
template<> float convertTo<float>(cchar* str) {
return (float)atof(str);
}
// Specialization for bool
template<> bool convertTo<bool>(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;
}

54
src/StringUtils.h Normal file
View File

@ -0,0 +1,54 @@
#ifndef STRINGUTILS_H
#define STRINGUTILS_H
#include <stdio.h>
#include <string.h>
#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<typename T> T convertTo(cchar* str);
// Specialization for int
template<> int convertTo<int>(cchar* str);
// Specialization for long int
template<> long int convertTo<long int>(cchar* str);
// Specialization for long long int
//template<> long long int convertTo<long long int>(cchar* str);
// Specialization for float
template<> float convertTo<float>(cchar* str);
// Specialization for bool
template<> bool convertTo<bool>(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

7
src/constants.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef CONSTANTS_H
#define CONSTANTS_H
#define MAX_COMPONENTS 30
#define DEFAULT_DEBUG_INTERVAL 1000
#endif

160
src/enums.h Normal file
View File

@ -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
{
/**<Internal: no flags */
E_MF_NONE = 0x00000000,
/**<Internal: flag designating an unprocessed message */
E_MF_NEW = 0x00000001,
/**<Internal: Processing message flag */
E_MF_PROCESSING = 0x00000002,
/**<Internal: Processed message flag */
E_MF_PROCESSED = 0x00000004,
/**<Internal: Debug message during processing */
E_MF_DEBUG = 0x00000008,
/**<Sender: Instruct to send a receipt - Default:On */
E_MF_RECEIPT = 0x0000010,
/**<Sender: Instruct to return component state */
E_MF_STATE = 0x00000020
} E_MessageFlags;
// Restore conflicting enum
typedef enum
{
MB_FC_NONE = 0, //!< null operator
MB_FC_READ_COILS = 1, //!< FCT=1 -> 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

6
src/error_codes.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef ERROR_CODES_H
#define ERROR_CODES_H
#endif

390
src/macros.h Normal file
View File

@ -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 <class N>
static constexpr N _MIN(const N val) { return val; }
template <class N>
static constexpr N _MAX(const N val) { return val; }
template <class L, class R>
static constexpr auto _MIN(const L lhs, const R rhs) -> decltype(lhs + rhs)
{
return lhs < rhs ? lhs : rhs;
}
template <class L, class R>
static constexpr auto _MAX(const L lhs, const R rhs) -> decltype(lhs + rhs)
{
return lhs > rhs ? lhs : rhs;
}
template <class T, class... Ts>
static constexpr const T _MIN(T V, Ts... Vs) { return _MIN(V, _MIN(Vs...)); }
template <class T, class... Ts>
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<T>(static_cast<int>(x) & static_cast<int>(y)); } \
FORCE_INLINE constexpr T operator|(T x, T y) { return static_cast<T>(static_cast<int>(x) | static_cast<int>(y)); } \
FORCE_INLINE constexpr T operator^(T x, T y) { return static_cast<T>(static_cast<int>(x) ^ static_cast<int>(y)); } \
FORCE_INLINE constexpr T operator~(T x) { return static_cast<T>(~static_cast<int>(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. <type_traits> is not available on all platform
namespace Private
{
template <bool, typename _Tp = void>
struct enable_if
{
};
template <typename _Tp>
struct enable_if<true, _Tp>
{
typedef _Tp type;
};
template <typename T, typename U>
struct is_same
{
enum
{
value = false
};
};
template <typename T>
struct is_same<T, T>
{
enum
{
value = true
};
};
template <typename T, typename... Args>
struct first_type_of
{
typedef T type;
};
template <typename T>
struct first_type_of<T>
{
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<Type> structure containing 'value' set to true if the member exists
#define HAS_MEMBER_IMPL(Member) \
namespace Private \
{ \
template <typename Type, typename Yes = char, typename No = long> \
struct HasMember_##Member \
{ \
template <typename C> \
static Yes &test(decltype(&C::Member)); \
template <typename C> \
static No &test(...); \
enum \
{ \
value = sizeof(test<Type>(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 <typename T, typename... Args> \
FORCE_INLINE typename enable_if<HasMember_##Method<T>::value, Return>::type Call_##Method(T *t, Args... a) { return static_cast<Return>(t->Method(a...)); } \
_UNUSED static Return Call_##Method(...) { return __VA_ARGS__; } \
}
#define CALL_IF_EXISTS(Return, That, Method, ...) \
static_cast<Return>(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

15
src/utils.cpp Normal file
View File

@ -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(" ");
}

17
src/utils.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef UTILS_H
#define UTILS_H
#include <Arduino.h>
#include <stdint.h>
void printHex(uint8_t *data, uint8_t length);
/*
template <typename T> T normalizeValue(T value, T maximum) {
if (value < 0 || value > maximum) {
return 0; // Or any other appropriate value
}
return value / maximum;
}
*/
#endif

23
src/xmath.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef XMATH_H
#define XMATH_H
#ifdef __cplusplus
extern "C++"
{
template <typename T>
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<float>((static_cast<float>((x) - (min))) / ((max) - (min)), 0.0f, 1.0f))
#endif

94
src/xstatistics.cpp Normal file
View File

@ -0,0 +1,94 @@
#include "xstatistics.h"
#include <stdint.h>
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 - <20> )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

116
src/xstatistics.h Normal file
View File

@ -0,0 +1,116 @@
#ifndef XSTATISTICS_H
#define XSTATISTICS_H
#include <Arduino.h>
#include <math.h>
#define STAT_USE_STDEV
#ifdef __cplusplus
// C++11 solution that is standards compliant. Return type is deduced automatically
template <class L, class R>
static inline constexpr auto MIN(const L lhs, const R rhs) -> decltype(lhs + rhs)
{
return lhs < rhs ? lhs : rhs;
}
template <class L, class R>
static inline constexpr auto MAX(const L lhs, const R rhs) -> decltype(lhs + rhs)
{
return lhs > rhs ? lhs : rhs;
}
template <class T>
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

136
src/xtimer.h Normal file
View File

@ -0,0 +1,136 @@
#ifndef XTIMER_H
#define XTIMER_H
#if defined(ARDUINO) && ARDUINO >= 100
#include <Arduino.h>
#else
#include <WProgram.h>
#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

16
src/xtypes.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef TYPES_H
#define TYPES_H
#include <stdint.h>
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