plot silbing
This commit is contained in:
parent
da734afe6d
commit
147a55ff10
516
src/Component.h
516
src/Component.h
@ -1 +1,515 @@
|
||||
LyoKICogQ29tcG9uZW50LmgKICogUmV2aXNpb246IGluaXRpYWwgZG9jdW1lbnRhdGlvbgogKi8KCiNpZm5kZWYgQ09NUE9ORU5UX0gKI2RlZmluZSBDT01QT05FTlRfSAoKI2luY2x1ZGUgPFdTdHJpbmcuaD4KI2luY2x1ZGUgPEFyZHVpbm9Mb2cuaD4KI2luY2x1ZGUgPFZlY3Rvci5oPgojaW5jbHVkZSAiLi9lbnVtcy5oIgojaW5jbHVkZSAiY29uc3RhbnRzLmgiCiNpbmNsdWRlICJlcnJvcl9jb2Rlcy5oIgojaW5jbHVkZSAibWFjcm9zLmgiCiNpbmNsdWRlICJ4dHlwZXMuaCIKCgpjbGFzcyBCcmlkZ2U7CmNsYXNzIE1vZGJ1c1RDUDsKY2xhc3MgTW9kYnVzQmxvY2tWaWV3OwpjbGFzcyBNQl9SZWdpc3RlcnM7CmNsYXNzIFJTNDg1Owo7KioKICogQGJyaWVmIFRoZSBDb21wb25lbnQgY2xhc3MgcmVwcmVzZW50cyBhIGdlbmVyaWMgY29tcG9uZW50LgogKi8KY2xhc3MgQ29tcG9uZW50CnsKcHVibGljOgogICAgLyoqCiAgICAgKiBAYnJpZWYgVGhlIGRlZmF1bHQgcnVuIGZsYWdzIGZvciBhIGNvbXBvbmVudC4KICAgICAqLwogICAgc3RhdGljIGNvbnN0IGludCBDT01QT05FTlRfREVGQVVMVCA9IDEgPDwgT0JKRUNUX1JVTl9GTEFHU19EX09GX0xPT1AgfCAxIDw8IE9CSkVDVF9SVU5fRkxBR1M6OkVfT0ZfU0VUVVA7CgogICAgLyoqCiAgICAgKiBAYnJpZWYgVGhlIGRlZmF1bHQgSUQgZm9yIGEgY29tcG9uZW50LgogICAgICovCiAgICBzdGF0aWMgY29uc3QgdXNob3J0IENPTVBPTkVOVF9OT19JRCA9IDA7CgogICAgLyoqCiAgICAgKiBAYnJpZWYgVGhlIHR5cGUgb2YgdGhlIGNvbXBvbmVudC4KICAgICAqLwogICAgdXNob3J0IHR5cGUgPSBDT01QT05FTlRfVFlQRTo6Q09NUE9ORU5UX1RZUEVfVU5LT1dOOwoKICAgIC8qKgogICAgICogQGJyaWVmIERlZmF1bHQgY29uc3RydWN0b3IgZm9yIHRoZSBDb21wb25lbnQgY2xhc3MuCiAgICAgKi8KICAgIENvbXBvbmVudCgpIDogbmFtZSgiTk9fTkFNRSIpLCBpZCgwKSwKICAgICAgICAgICAgICAgICAgZmxhZ3MoT0JKRUNUX1JVTl9GTEFHU1dGX05PTkUpLAogICAgICAgICAgICAgICAgICBuRmxhZ3MoT0JKRUNUX05FVF9DQVBTOjpFX05DQVBTX05PTkUpLCAKICAgICAgICAgICAgICAgICAgb3duZXIobnVsbHB0ciksQAAAAnMJbGF2ZUlkKDApIHt9CgogICAgLyoqCiAgICAgKiBAYnJpZWYgQ29uc3RydWN0b3IgZm9yIHRoZSBDb21wb25lbnQgY2xhc3Mgd2l0aCBhIHNwZWNpZmllZCBuYW1lLgogICAgICogQHBhcmFtIF9uYW1lIFRoZSBuYW1lIG9mIHRoZSBjb21wb25lbnQuCiAgICAgKi8KICAgIENvbXBvbmVudChTdHJpbmcgX25hbWUpIDogbmFtZShfbmFtZSksIGlkKENPTVBPTkVOVF9OT19JRCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmbGFncyhPQkpFQ1RfUlVOX0ZMQUdTOjpFX09GX05PTkUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbkZsYWdzKE9CSkVDVF9ORV11zcRQ1Mpo5FX05DQVBTX05PTkUpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG93bmVyKG51bGxwdHIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2xhdmVJZCgwKSB7fQoKICAgIC8qKgogICAgICogQGJyaWVmIENvbnN0cnVjdG9yIGZvciB0aGUgQ29tcG9uZW50IGNsYXNzIHdpdGggYSBzcGVjaWZpZWQgbmFtZSBhbmQgSUQuCiAgICAgKiBAcGFyYW0gX25hbWUgVGhlIG5hbWUgb2YgdGhlIGNvbXBvbmVudC4KICAgICAqIEBwYXJhbSBfaWQgVGhlIElEIG9mIHRoZSBjb21wb25lbnQuCiAgICAgKi8KICAgIENvbXBvbmVudChTdHJpbmcgX25hbWUsIHVzaG9ydCBfaWQpIDogbmFtZShfbmFtZSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWQoX2lkKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmbGFncyhPQkpFQ1RfUlVOX0ZMQUdTOjpFX09GX05PTkUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5GbGFncyNVSf2RFQ1RfTkVUX0NBUFPLkVfTkNBUFNfTk9ORSksIAokICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3duZXIobnVsbHB0ciksCoUAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzbGF2ZUlkKDApIHt9CgogICAgLyoqCiAgICAgKiBAYnJpZWYgQ29uc3RydWN0b3IgZm9yIHRoZSBDb21wb25lbnQgY2xhc3Mgd2l0aCBhIHNwZWNpZmllZCBuYW1lLCBJRCwgYW5kIGZsYWdzLgogICAgICogQHBhcmFtIF9uYW1lIFRoZSBuYW1lIG9mIHRoZSBjb21wb25lbnQuCiAgICAgKiBAcGFyYW0gX2lkIFRoZSBJRCBvZiB0aGUgY29tcG9uZW50LgogICAgICogQHBhcmFtIF9mbGFncyBUaGUgcnVuIGZsYWdzIGZvciB0aGUgY29tcG9uZW50LgogICAgICovCiAgICBDb21wb25lbnQoU3RyaW5nIF9uYW1lLCBzaG9ydCBfaWQsIGludCBfZmxhZ3MpIDogbmFtZShfbmFtZSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWQoX2lkKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmbGFncyhfZmxhZ3MpLAtyCAgICAgICAgICAgICAgICAgICAgICAgICAgICngwOTAweCAgICAgICAgICAga3ygZSAgICAgICNGbGFncyhPQkpFQ1RfTkVUX0NBUFPl5VX05DQVBTX05PTkUpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvd25lcilybnVsbHB0ciksOvgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzbGF2ZUlkKDApCiAgICB7CiAgICB9OvgOvgKICAgIC8qKgogICAgICogQGJyaWVmIJnPDb25zdHJ1Y3RvciBmb3IgdGhlIENvbXBvbmVudCBjbGFzcyB3aXRoIGEgc3BlY2lmaWVkIG5hbWUsIElELCBmbGFncywgybW5kIG9ybmXyi7gkLvgogICAgICogQHBhcmFtIF9uYW1lIFRoZSBuYW1lIG5mIHRoZSBjb21wb25lbnTCKoMgICAgICogQHBhcmFtIF9pZCBUaGUgSUQgb2YgdGhlieNvbXBvbmVudC4KKgAAIAgUCAgICAgKiBAcGFyYW0gX2ZsYWdzIFRoZSBydW4gZmxhZ3MgZm9yIHRoZSBjb21wb25lbnQuCiAgICAgKiBAcGFyYW0gX293bmVyIFRoZSBvd25lciBvZiB0aGUgY29tcG9uZW50LgogICAgICovCiAgICBDb21wb25lbnQoU3RyaW5nIF9uYW1lLCB1c2hvcnQgX2lkLCB1aW50MTZfdCEf ZmxhZ3nSQ*b21wb25lbnQgKl9vd25lcikgOiAKICAgIG5GbGFncyhPQkpFQ1OLTa5FVF9DQVBTX19FX05DQVBTf5NTX05PTkUpLAogICAgbmFtZShfbmFtZSksCiAgICBpZCa3pBid4fkAwogICAgZmxhZ3MoX2ZsYWdz2FBvQkVVEAjb3duZXIoX293bmVyAEm4cHRyeCwKICAgIF9g7+abGx2ZUWlkKLApoqGZ7fQuKQ9hWOogICAgmgdCdKJ4ygb2YKCiYCSK4ggogggYiBVUSKBgC2ABgOOIAEq3VyCAgITUEABQJAgACAEgAEJp
|
||||
#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;
|
||||
class RS485;
|
||||
/**
|
||||
* @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 The type of the component.
|
||||
*/
|
||||
ushort type = COMPONENT_TYPE::COMPONENT_TYPE_UNKOWN;
|
||||
|
||||
/**
|
||||
* @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),
|
||||
slaveId(0) {}
|
||||
|
||||
/**
|
||||
* @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),
|
||||
slaveId(0) {}
|
||||
|
||||
/**
|
||||
* @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),
|
||||
slaveId(0) {}
|
||||
|
||||
/**
|
||||
* @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),
|
||||
slaveId(0)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) :
|
||||
nFlags(OBJECT_NET_CAPS::E_NCAPS_NONE),
|
||||
name(_name),
|
||||
id(_id),
|
||||
flags(_flags),
|
||||
owner(_owner),
|
||||
slaveId(0)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 being called after all components have been setup.
|
||||
* @return The error code indicating the success or failure of the operation.
|
||||
*/
|
||||
virtual short onRun() { 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() {
|
||||
_loop_start_time_us = micros();
|
||||
// Derived classes will call this base implementation or implement their own logic
|
||||
// Execution of derived class loop happens here
|
||||
// Then, the duration is calculated in the derived class if it overrides this, or after App::loop() in PHApp
|
||||
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;
|
||||
|
||||
/**
|
||||
* @brief Start time of the last loop execution in microseconds.
|
||||
*/
|
||||
uint64_t _loop_start_time_us;
|
||||
|
||||
/**
|
||||
* @brief Duration of the last loop execution in microseconds.
|
||||
*/
|
||||
uint64_t _loop_duration_us;
|
||||
|
||||
//////////////////////////////////////////
|
||||
//
|
||||
// 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 Gets the base Modbus TCP address allocated for this RTU device instance.
|
||||
* @return The base TCP address for this device instance.
|
||||
*/
|
||||
virtual uint16_t mb_tcp_base_address() const { return 0; }
|
||||
|
||||
/**
|
||||
* @brief The Modbus slave ID for this component (satisfies the Modbus interfaces)
|
||||
*/
|
||||
ushort slaveId;
|
||||
|
||||
/**
|
||||
* @brief The RS485 interface for this component.
|
||||
* @todo: move to feature
|
||||
*/
|
||||
RS485* rs485;
|
||||
|
||||
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 Gets the duration of the last loop execution in microseconds.
|
||||
* @return The loop duration in microseconds.
|
||||
*/
|
||||
uint64_t getLoopDurationUs() const { return _loop_duration_us; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @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
|
||||
|
||||
@ -124,7 +124,7 @@ public:
|
||||
update_msg.functionCode = m_notificationFunctionCode;
|
||||
update_msg.componentId = m_sourceComponentId;
|
||||
|
||||
Log.infoln("ValueWrapper (CompID: %u, Mode: %d): Threshold met for reg_offset %u. Old: %d, New: %d. Notifying owner.",
|
||||
Log.traceln("ValueWrapper (CompID: %u, Mode: %d): Threshold met for reg_offset %u. Old: %d, New: %d. Notifying owner.",
|
||||
m_sourceComponentId, static_cast<int>(m_thresholdMode), m_modbusRegisterOffset,
|
||||
static_cast<int>(oldValue),
|
||||
static_cast<int>(newValue));
|
||||
@ -135,7 +135,7 @@ public:
|
||||
m_onNotifiedPostCallback(newValue, oldValue);
|
||||
}
|
||||
} else {
|
||||
Log.warningln("ValueWrapper (CompID: %u): Threshold met for reg_offset %u, but owner or base_addr_lambda is null. Cannot notify.",
|
||||
Log.traceln("ValueWrapper (CompID: %u): Threshold met for reg_offset %u, but owner or base_addr_lambda is null. Cannot notify.",
|
||||
m_sourceComponentId, m_modbusRegisterOffset);
|
||||
if (m_onNotifiedPostCallback) {
|
||||
m_onNotifiedPostCallback(newValue, oldValue);
|
||||
|
||||
@ -16,10 +16,14 @@ class Relay : public Component
|
||||
{
|
||||
private: // Keep address private, provide via getModbusInfo
|
||||
const short modbusAddress; // Store Modbus address internally
|
||||
MB_Registers m_modbus_block;
|
||||
MB_Registers m_modbus_block[2];
|
||||
// m_modbus_view needs to be mutable to be returned as ModbusBlockView* from a const method.
|
||||
mutable ModbusBlockView m_modbus_view;
|
||||
|
||||
// for blinking
|
||||
unsigned long onOffFrequency; // in seconds
|
||||
unsigned long lastToggleTime;
|
||||
|
||||
public:
|
||||
Relay(
|
||||
Component *owner,
|
||||
@ -29,14 +33,16 @@ public:
|
||||
: Component("Relay", _id, Component::COMPONENT_DEFAULT, owner),
|
||||
pin(_pin),
|
||||
modbusAddress(_modbusAddress),
|
||||
value(false)
|
||||
value(false),
|
||||
onOffFrequency(0),
|
||||
lastToggleTime(0)
|
||||
{
|
||||
setNetCapability(OBJECT_NET_CAPS::E_NCAPS_MODBUS);
|
||||
|
||||
// Initialize instance-specific Modbus block.
|
||||
// The modbusAddress is the actual start address for the Relay's single coil.
|
||||
// So, the offset passed to the macro is 0.
|
||||
m_modbus_block = INIT_MODBUS_BLOCK_TCP(
|
||||
m_modbus_block[0] = INIT_MODBUS_BLOCK_TCP(
|
||||
this->modbusAddress, // Base address for this component's block
|
||||
0, // Offset for this specific register
|
||||
E_FN_CODE::FN_WRITE_COIL, // Function code
|
||||
@ -45,15 +51,24 @@ public:
|
||||
nullptr // Group (nullptr if not applicable)
|
||||
);
|
||||
|
||||
m_modbus_block[1] = INIT_MODBUS_BLOCK_TCP(
|
||||
this->modbusAddress, // Base address for this component's block
|
||||
1, // Offset for the frequency register
|
||||
E_FN_CODE::FN_WRITE_HOLD_REGISTER, // Function code
|
||||
MB_ACCESS_READ_WRITE, // Access type
|
||||
"Relay On/Off Freq (secs)", // Name
|
||||
nullptr // Group (nullptr if not applicable)
|
||||
);
|
||||
|
||||
// Initialize the view to point to this instance-specific block
|
||||
m_modbus_view.data = &m_modbus_block; // Point to the single block
|
||||
m_modbus_view.count = 1;
|
||||
m_modbus_view.data = m_modbus_block; // Point to the array of blocks
|
||||
m_modbus_view.count = 2;
|
||||
}
|
||||
|
||||
short info(short flags = 0, short val = 0) override
|
||||
{
|
||||
Log.verboseln("Relay::info - ID: %d, Pin: %d, Modbus Addr: %d, Value: %d, NetCaps: %d",
|
||||
id, pin, modbusAddress, value, nFlags);
|
||||
Log.verboseln("Relay::info - ID: %d, Pin: %d, Modbus Addr: %d, Value: %d, Freq: %lu, NetCaps: %d",
|
||||
id, pin, modbusAddress, value, onOffFrequency, nFlags);
|
||||
return E_OK;
|
||||
}
|
||||
|
||||
@ -84,6 +99,7 @@ public:
|
||||
|
||||
short setValueCmd(short arg1, short arg2)
|
||||
{
|
||||
onOffFrequency = 0; // manual override
|
||||
return setValue(arg1 > 0);
|
||||
}
|
||||
|
||||
@ -112,11 +128,15 @@ public:
|
||||
{
|
||||
if (address == modbusAddress) // Use internal member
|
||||
{
|
||||
bool newValue = (networkValue > 0);
|
||||
if (value != newValue)
|
||||
onOffFrequency = 0; // Manual control stops blinking
|
||||
return setValue(networkValue > 0);
|
||||
}
|
||||
else if (address == modbusAddress + 1)
|
||||
{
|
||||
onOffFrequency = networkValue;
|
||||
if (onOffFrequency > 0)
|
||||
{
|
||||
value = newValue;
|
||||
digitalWrite(pin, value); // Re-enabled GPIO write
|
||||
lastToggleTime = millis();
|
||||
}
|
||||
return E_OK;
|
||||
}
|
||||
@ -134,13 +154,17 @@ public:
|
||||
{
|
||||
return value ? 1 : 0;
|
||||
}
|
||||
else if (address == modbusAddress + 1)
|
||||
{
|
||||
return onOffFrequency;
|
||||
}
|
||||
return 0; // Default for mismatched addresses
|
||||
}
|
||||
|
||||
short mb_tcp_read(MB_Registers *reg) override
|
||||
{
|
||||
// Log.traceln(F("Relay::mb_tcp_read (Reg Context) - TCP Addr: %d, Type: %d"), reg->startAddress, reg->type);
|
||||
return value ? 1 : 0;
|
||||
return mb_tcp_read(reg->startAddress);
|
||||
}
|
||||
|
||||
void mb_tcp_register(ModbusTCP *manager) const override
|
||||
@ -171,6 +195,15 @@ public:
|
||||
short loop() override
|
||||
{
|
||||
Component::loop();
|
||||
if (onOffFrequency > 0)
|
||||
{
|
||||
unsigned long currentMillis = millis();
|
||||
if (currentMillis - lastToggleTime >= onOffFrequency * 1000)
|
||||
{
|
||||
lastToggleTime = currentMillis;
|
||||
setValue(!value);
|
||||
}
|
||||
}
|
||||
return E_OK;
|
||||
}
|
||||
|
||||
|
||||
@ -1203,9 +1203,9 @@ void sendJsonResponse(AsyncWebSocketClient *client, const JsonDocument &doc, con
|
||||
Log.warningln("WS #%u: JSON response too large (%u bytes), sending error instead.", client->id(), responseStr.length());
|
||||
JsonDocument errorDoc;
|
||||
errorDoc["type"] = "error";
|
||||
JsonObject dataObj = errorDoc.createNestedObject("data");
|
||||
dataObj["message"] = "Response too large to send";
|
||||
dataObj["original_type"] = type;
|
||||
JsonObject errorDataObj = errorDoc["data"].to<JsonObject>();
|
||||
errorDataObj["message"] = "Response too large to send";
|
||||
errorDataObj["original_type"] = type;
|
||||
String errorStr;
|
||||
serializeJson(errorDoc, errorStr);
|
||||
client->text(errorStr);
|
||||
@ -1470,7 +1470,7 @@ void RESTServer::handleWebSocketMessage(AsyncWebSocketClient *client, void *arg,
|
||||
JsonDocument errorDoc;
|
||||
errorDoc["type"] = "error";
|
||||
// Maintain the {type: "error", data: {message:"..."}} structure for errors for consistency with sendJsonResponse
|
||||
JsonObject errorDataObj = errorDoc.createNestedObject("data");
|
||||
JsonObject errorDataObj = errorDoc["data"].to<JsonObject>();
|
||||
errorDataObj["message"] = "Response too large to send";
|
||||
errorDataObj["original_type"] = "registers";
|
||||
String errorStr;
|
||||
@ -1663,7 +1663,6 @@ void RESTServer::handleWebSocketMessage(AsyncWebSocketClient *client, void *arg,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RESTServer::broadcast(BroadcastMessageType type, const JsonDocument &data)
|
||||
{
|
||||
JsonDocument doc;
|
||||
@ -1682,6 +1681,12 @@ void RESTServer::broadcast(BroadcastMessageType type, const JsonDocument &data)
|
||||
case BROADCAST_SYSTEM_STATUS:
|
||||
typeStr = "system_status";
|
||||
break;
|
||||
case BROADCAST_USER_DEFINED:
|
||||
typeStr = "user_defined";
|
||||
break;
|
||||
case BROADCAST_USER_MESSAGE:
|
||||
typeStr = "user_message";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@ -26,7 +26,9 @@ typedef enum : uint8_t {
|
||||
BROADCAST_COIL_UPDATE,
|
||||
BROADCAST_REGISTER_UPDATE,
|
||||
BROADCAST_LOG_ENTRY,
|
||||
BROADCAST_SYSTEM_STATUS // Example
|
||||
BROADCAST_SYSTEM_STATUS,
|
||||
BROADCAST_USER_DEFINED,
|
||||
BROADCAST_USER_MESSAGE
|
||||
} BroadcastMessageType;
|
||||
|
||||
class ModbusTCP;
|
||||
|
||||
@ -651,10 +651,9 @@ MB_Error ModbusRTU::executeOperation(ModbusOperation &op)
|
||||
}
|
||||
if (err != Modbus::SUCCESS)
|
||||
{
|
||||
Log.error("Error adding request: %d for token %u", (int)err, op.token);
|
||||
// Log.error("Error adding request: %d for token %u", (int)err, op.token);
|
||||
// Clear IN_PROGRESS flag using CBI and OP_IN_PROGRESS_BIT
|
||||
CBI(op.flags, OP_IN_PROGRESS_BIT);
|
||||
|
||||
// Only retry on specific communication errors, not queue full
|
||||
if (err == Modbus::TIMEOUT || err == Modbus::CRC_ERROR)
|
||||
{
|
||||
@ -699,7 +698,9 @@ MB_Error ModbusRTU::executeOperation(ModbusOperation &op)
|
||||
op.slaveId, deviceErrorState.currentBackoffInterval[slaveIdx]);
|
||||
}
|
||||
|
||||
lastOperationTime = now + deviceErrorState.currentBackoffInterval[slaveIdx];
|
||||
// Add random jitter to the backoff time
|
||||
long jitter = (random(-10, 11) / 100.0) * deviceErrorState.currentBackoffInterval[slaveIdx]; // +/- 10%
|
||||
lastOperationTime = now + deviceErrorState.currentBackoffInterval[slaveIdx] + jitter;
|
||||
return MB_Error::OpClientQueueFull;
|
||||
}
|
||||
else
|
||||
@ -724,6 +725,7 @@ MB_Error ModbusRTU::executeOperation(ModbusOperation &op)
|
||||
// Need to return appropriate error code from the if/else if branches
|
||||
if (err == Modbus::TIMEOUT || err == Modbus::CRC_ERROR)
|
||||
{
|
||||
Log.traceln("Communication error for token %u, will retry later (retry %d) (error %d)", op.token, op.retries, (int)err);
|
||||
return (op.retries < MAX_RETRIES) ? MB_Error::OpRetrying : MB_Error::OpMaxRetriesExceeded;
|
||||
}
|
||||
else if (err == static_cast<Error>(MB_Error::OpClientQueueFull))
|
||||
@ -1151,36 +1153,6 @@ void ModbusRTU::setOnErrorCallback(OnErrorCallback callback)
|
||||
|
||||
ModbusOperation *ModbusRTU::findOperationByToken(uint32_t token)
|
||||
{
|
||||
unsigned long now = millis();
|
||||
if (token > 0 && now - token > OPERATION_TIMEOUT)
|
||||
{
|
||||
// Log.verboseln("Token %u is older than timeout (%lu ms old)", token, now - token);
|
||||
}
|
||||
|
||||
// Detailed debug trace to help understand what's in the queue
|
||||
if (Log.getLevel() <= LOG_LEVEL_TRACE)
|
||||
{
|
||||
Log.traceln("Searching for token %u in queue of %u operations", token, operationCount);
|
||||
int found = 0;
|
||||
for (int i = 0; i < MAX_PENDING_OPERATIONS; i++)
|
||||
{
|
||||
if (TEST(operationQueue[i].flags, OP_USED_BIT))
|
||||
{
|
||||
Log.traceln(" Queue slot %d: token=%u, address=%u, slaveId=%u, type=%d, age=%lu ms",
|
||||
i, operationQueue[i].token, operationQueue[i].address,
|
||||
operationQueue[i].slaveId, operationQueue[i].type,
|
||||
now - operationQueue[i].timestamp);
|
||||
found++;
|
||||
if (found >= 5)
|
||||
{ // Limit to first 5 operations to avoid flooding logs
|
||||
Log.traceln(" ... and %u more operations", operationCount - 5);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search in pending operations - Use TEST
|
||||
for (int i = 0; i < MAX_PENDING_OPERATIONS; i++)
|
||||
{
|
||||
if (TEST(operationQueue[i].flags, OP_USED_BIT) && operationQueue[i].token == token)
|
||||
@ -1188,8 +1160,6 @@ ModbusOperation *ModbusRTU::findOperationByToken(uint32_t token)
|
||||
return &operationQueue[i];
|
||||
}
|
||||
}
|
||||
|
||||
Log.traceln("Token %u not found in operation queue", token);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -1204,7 +1174,7 @@ void ModbusRTU::onErrorReceived(Error error, uint32_t token)
|
||||
if (operation == nullptr)
|
||||
{
|
||||
MB_Error mbError = static_cast<MB_Error>(error);
|
||||
Log.errorln("Callback Error: Operation not found for token %u (error %u: %s)", token, (int)error, modbusErrorToString(mbError));
|
||||
// Log.errorln("Callback Error: Operation not found for token %u (error %u: %s)", token, (int)error, modbusErrorToString(mbError));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1233,9 +1203,8 @@ void ModbusRTU::onErrorReceived(Error error, uint32_t token)
|
||||
|
||||
// Handle device-specific backoff for timeouts
|
||||
if (error == Modbus::TIMEOUT) {
|
||||
uint8_t slaveIdx = operation->slaveId - 1;
|
||||
uint8_t slaveIdx = operation->slaveId - 1;
|
||||
uint32_t now = millis();
|
||||
|
||||
if (now - deviceErrorState.lastBackoffIncreaseTime[slaveIdx] > deviceErrorState.currentBackoffInterval[slaveIdx]) {
|
||||
deviceErrorState.lastBackoffIncreaseTime[slaveIdx] = now;
|
||||
|
||||
@ -1245,11 +1214,9 @@ void ModbusRTU::onErrorReceived(Error error, uint32_t token)
|
||||
deviceErrorState.currentBackoffInterval[slaveIdx] = MB_OFFLINE_BACKOFF_MAX;
|
||||
}
|
||||
}
|
||||
Log.traceln("Device %u timeout. Setting backoff to %lu ms",
|
||||
operation->slaveId, deviceErrorState.currentBackoffInterval[slaveIdx]);
|
||||
}
|
||||
// This will pause the main ModbusRTU scheduler for the current backoff duration of this slave
|
||||
lastOperationTime = now + deviceErrorState.currentBackoffInterval[slaveIdx];
|
||||
long jitter = (random(-10, 11) / 100.0) * deviceErrorState.currentBackoffInterval[slaveIdx]; // +/- 10%
|
||||
lastOperationTime = now + deviceErrorState.currentBackoffInterval[slaveIdx] + jitter;
|
||||
}
|
||||
|
||||
MB_Error mbError = static_cast<MB_Error>(error);
|
||||
@ -1626,6 +1593,5 @@ void ModbusRTU::resetDeviceOfflineState(uint8_t slaveId) {
|
||||
uint8_t idx = slaveId - 1;
|
||||
deviceErrorState.lastBackoffIncreaseTime[idx] = 0;
|
||||
deviceErrorState.currentBackoffInterval[idx] = MB_OFFLINE_BACKOFF_INITIAL;
|
||||
Log.noticeln("Reset backoff for device %u to %lu ms", slaveId, deviceErrorState.currentBackoffInterval[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@ void PlotBase::start() {
|
||||
_running = true;
|
||||
_paused = false;
|
||||
_explicitlyStopped = false; // Ensure explicitlyStopped is false when starting
|
||||
onStart();
|
||||
} else {
|
||||
_running = false;
|
||||
_paused = false;
|
||||
@ -47,6 +48,7 @@ void PlotBase::stop() {
|
||||
_explicitlyStopped = true; // Mark as explicitly stopped
|
||||
_elapsedMsAtPause = 0;
|
||||
_startTimeMs = 0; // Reset startTime to ensure IDLE/STOPPED state if duration not met
|
||||
onStop();
|
||||
}
|
||||
|
||||
void PlotBase::pause() {
|
||||
@ -61,6 +63,7 @@ void PlotBase::pause() {
|
||||
_elapsedMsAtPause = min(currentElapsed, _durationMs);
|
||||
_paused = true;
|
||||
_explicitlyStopped = false; // Pausing implies it's not in a fully stopped state
|
||||
onPause();
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,6 +77,7 @@ void PlotBase::resume() {
|
||||
}
|
||||
_paused = false;
|
||||
_explicitlyStopped = false; // Resuming implies it's not stopped
|
||||
onResume();
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,4 +202,59 @@ PlotStatus PlotBase::getCurrentStatus() const {
|
||||
}
|
||||
|
||||
return PlotStatus::IDLE; // Default if no other state matches
|
||||
}
|
||||
|
||||
bool PlotBase::addPlot(PlotBase* plot) {
|
||||
if (!plot) return false;
|
||||
for (int i = 0; i < MAX_PLOTS; ++i) {
|
||||
if (!_plots[i]) {
|
||||
_plots[i] = plot;
|
||||
plot->setParent(this);
|
||||
plot->setDuration(_durationMs);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false; // No available slot
|
||||
}
|
||||
|
||||
PlotBase* PlotBase::getPlot(uint8_t index) const {
|
||||
if (index < MAX_PLOTS) {
|
||||
return _plots[index];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void PlotBase::onStart() {
|
||||
for (int i = 0; i < MAX_PLOTS; ++i) {
|
||||
if (_plots[i]) {
|
||||
_plots[i]->start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlotBase::onStop() {
|
||||
for (int i = 0; i < MAX_PLOTS; ++i) {
|
||||
if (_plots[i]) {
|
||||
_plots[i]->stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlotBase::onPause() {
|
||||
for (int i = 0; i < MAX_PLOTS; ++i) {
|
||||
if (_plots[i]) {
|
||||
_plots[i]->pause();
|
||||
}
|
||||
}
|
||||
if(getParent()) {
|
||||
getParent()->pause();
|
||||
}
|
||||
}
|
||||
|
||||
void PlotBase::onResume() {
|
||||
for (int i = 0; i < MAX_PLOTS; ++i) {
|
||||
if (_plots[i]) {
|
||||
_plots[i]->resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,8 @@
|
||||
#include "config.h"
|
||||
|
||||
#define PROFILE_SCALE 10000
|
||||
#define PROFILE_TIME_SCALE 1000
|
||||
#define MAX_PLOTS 1
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Status Enum
|
||||
@ -34,7 +36,13 @@ class PlotBase : public Component { // Ensure inheritance is active
|
||||
public:
|
||||
PlotBase(Component* owner, ushort componentId) :
|
||||
Component("PlotBase", componentId, Component::COMPONENT_DEFAULT, owner),
|
||||
_durationMs(0), _startTimeMs(0), _elapsedMsAtPause(0), _running(false), _paused(false), _explicitlyStopped(false), _userData(nullptr) {}
|
||||
_durationMs(0), _startTimeMs(0), _elapsedMsAtPause(0), _running(false), _paused(false), _explicitlyStopped(false), _userData(nullptr),
|
||||
_parent(nullptr) {
|
||||
for (int i = 0; i < MAX_PLOTS; ++i) {
|
||||
_plots[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~PlotBase() = default;
|
||||
/**
|
||||
* @brief Loads configuration from a JSON object.
|
||||
@ -82,6 +90,10 @@ public:
|
||||
*/
|
||||
virtual void seek(uint32_t targetMs);
|
||||
|
||||
// --- Sub-Plot Management ---
|
||||
bool addPlot(PlotBase* plot);
|
||||
PlotBase* getPlot(uint8_t index) const;
|
||||
|
||||
/**
|
||||
* @brief Checks if the plot is currently running (actively progressing).
|
||||
* @return true if running, false otherwise.
|
||||
@ -100,6 +112,12 @@ public:
|
||||
*/
|
||||
uint32_t getDuration() const { return _durationMs; }
|
||||
|
||||
/**
|
||||
* @brief Sets the total duration of the plot.
|
||||
* @param durationMs The new duration in milliseconds.
|
||||
*/
|
||||
virtual void setDuration(uint32_t durationMs) { _durationMs = durationMs; }
|
||||
|
||||
/**
|
||||
* @brief Gets the remaining time in the plot based on the current elapsed time.
|
||||
* @return Remaining time in milliseconds. Returns 0 if the plot is not running or has finished.
|
||||
@ -160,10 +178,16 @@ public:
|
||||
*/
|
||||
uint32_t getElapsedMs() const;
|
||||
|
||||
PlotBase* getParent() const { return _parent; }
|
||||
void setParent(PlotBase* parent) { _parent = parent; }
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
|
||||
|
||||
virtual void onStart();
|
||||
virtual void onStop();
|
||||
virtual void onPause();
|
||||
virtual void onResume();
|
||||
|
||||
uint32_t _durationMs;
|
||||
uint32_t _startTimeMs;
|
||||
uint32_t _elapsedMsAtPause; // Stores elapsed time when pause() is called
|
||||
@ -171,6 +195,9 @@ protected:
|
||||
bool _paused; // True if pause() called while running
|
||||
bool _explicitlyStopped; // True if stop() was called and not superseded by start()
|
||||
void* _userData; // Pointer for arbitrary user data
|
||||
|
||||
PlotBase* _plots[MAX_PLOTS];
|
||||
PlotBase* _parent;
|
||||
};
|
||||
|
||||
#endif // PLOT_BASE_H
|
||||
@ -8,6 +8,7 @@
|
||||
#include "Arduino.h" // For pinMode, digitalWrite, analogWrite
|
||||
#include "CommandMessage.h" // For CommandMessage
|
||||
#include "Bridge.h" // For Bridge component
|
||||
#include "PHApp.h" // For PHApp component
|
||||
|
||||
SignalPlot::SignalPlot(Component* owner, ushort slot, ushort componentId)
|
||||
: PlotBase(owner, componentId),
|
||||
@ -54,19 +55,12 @@ short SignalPlot::loop() {
|
||||
if (!isRunning() || !enabled() || modbusTCP == nullptr || modbusTCP->modbusServer == nullptr) {
|
||||
return E_OK;
|
||||
}
|
||||
|
||||
uint32_t currentElapsedMs = getElapsedMs();
|
||||
// Duration is now in _durationMs from PlotBase, loaded in milliseconds
|
||||
|
||||
uint32_t now = millis();
|
||||
for (uint8_t i = 0; i < _numControlPoints; ++i) {
|
||||
S_SignalControlPoint& cp = _controlPoints[i];
|
||||
// Calculate the absolute trigger time in milliseconds for this control point
|
||||
// cp.time is scaled 0-PROFILE_SCALE (e.g., 0-1000)
|
||||
uint32_t absoluteTriggerMs = static_cast<uint32_t>(((uint64_t)cp.time * (uint64_t)getDuration()) / PROFILE_SCALE);
|
||||
// Check if the control point is due and hasn't been processed yet
|
||||
uint32_t absoluteTriggerMs = static_cast<uint32_t>(((uint64_t)cp.time * (uint64_t)getDuration()) / 1000);
|
||||
if (cp.state == E_SIGNAL_STATE::STATE_NONE && currentElapsedMs >= absoluteTriggerMs) {
|
||||
Log.verboseln("%s: Executing CP id %d (scaled time: %lu, abs time: %lu ms) at plot elapsed: %lu ms",
|
||||
name.c_str(), cp.id, cp.time, absoluteTriggerMs, currentElapsedMs);
|
||||
executeControlPointAction(i);
|
||||
}
|
||||
}
|
||||
@ -76,7 +70,7 @@ short SignalPlot::loop() {
|
||||
bool SignalPlot::load(const JsonObject& config) {
|
||||
|
||||
// Load name if present
|
||||
if (config.containsKey("name") && config["name"].is<String>()) {
|
||||
if (config["name"].is<String>()) {
|
||||
this->name = config["name"].as<String>();
|
||||
Log.verboseln("%s: Loaded name from config: %s", this->name.c_str(), this->name.c_str());
|
||||
} else {
|
||||
@ -85,23 +79,18 @@ bool SignalPlot::load(const JsonObject& config) {
|
||||
}
|
||||
|
||||
// Load duration if present (expected in milliseconds from JSON)
|
||||
if (config.containsKey("duration") && config["duration"].is<uint32_t>()) {
|
||||
if (config["duration"].is<uint32_t>()) {
|
||||
this->_durationMs = config["duration"].as<uint32_t>(); // Assign directly as ms
|
||||
Log.verboseln("%s: Loaded duration from config: %lu ms", this->name.c_str(), this->_durationMs);
|
||||
} else {
|
||||
Log.warningln("%s: 'duration' not found in config or not uint32. Current duration: %lu ms", this->name.c_str(), this->_durationMs);
|
||||
// Keep existing _durationMs (could be 0 from PlotBase constructor, or previously set)
|
||||
}
|
||||
|
||||
|
||||
// Slot is set by constructor and typically not overridden by JSON load().
|
||||
|
||||
_numControlPoints = 0; // Reset count before loading new points
|
||||
for (int i = 0; i < MAX_SIGNAL_POINTS; ++i)
|
||||
{
|
||||
_controlPoints[i] = {}; // Clear existing points
|
||||
}
|
||||
|
||||
if (!config.containsKey("controlPoints") || !config["controlPoints"].is<JsonArray>()) {
|
||||
if (!config["controlPoints"].is<JsonArray>()) {
|
||||
Log.warningln("%s: 'controlPoints' array not found or not an array in config. No points loaded.", name.c_str());
|
||||
_numControlPoints = 0;
|
||||
return false; // No points to load is considered a failure for a plot that needs points.
|
||||
@ -129,7 +118,7 @@ bool SignalPlot::load(const JsonObject& config) {
|
||||
_controlPoints[i].arg_0 = pointObj["arg_0"].as<int16_t>();
|
||||
// Always load arg_1 directly from JSON.
|
||||
// JSON must provide the correct value (e.g., 0 or 1 for MB_WRITE_COIL).
|
||||
if (pointObj.containsKey("arg_1")) {
|
||||
if (pointObj["arg_1"].is<int>()) {
|
||||
_controlPoints[i].arg_1 = pointObj["arg_1"].as<int16_t>();
|
||||
} else {
|
||||
_controlPoints[i].arg_1 = 0; // Default if not present, or log warning
|
||||
@ -137,20 +126,20 @@ bool SignalPlot::load(const JsonObject& config) {
|
||||
name.c_str(), _controlPoints[i].id, static_cast<int>(_controlPoints[i].type));
|
||||
}
|
||||
|
||||
if (pointObj.containsKey("arg_2")) {
|
||||
if (pointObj["arg_2"].is<int>()) {
|
||||
_controlPoints[i].arg_2 = pointObj["arg_2"].as<int16_t>();
|
||||
} else {
|
||||
_controlPoints[i].arg_2 = 0; // Default if not present
|
||||
}
|
||||
|
||||
// user field logic remains as is (currently skipped for assignment)
|
||||
if (pointObj.containsKey("user")) {
|
||||
if (!pointObj["user"].isNull()) {
|
||||
// ... user field handling ...
|
||||
}
|
||||
|
||||
// Load the actual execution state for this point, if provided in JSON.
|
||||
// This is distinct from the coil value that might have been in jsonState before.
|
||||
if (pointObj.containsKey("state")) {
|
||||
if (pointObj["state"].is<int>()) {
|
||||
_controlPoints[i].state = static_cast<E_SIGNAL_STATE>(pointObj["state"].as<int16_t>());
|
||||
} else {
|
||||
_controlPoints[i].state = E_SIGNAL_STATE::STATE_NONE; // Default initial state for execution tracking
|
||||
@ -167,7 +156,8 @@ const S_SignalControlPoint* SignalPlot::findActivePoint(uint32_t elapsedMs) cons
|
||||
// Iterate backwards assuming points are sorted by time ascending
|
||||
// This is more efficient as we likely hit the correct segment sooner.
|
||||
for (int i = _numControlPoints - 1; i >= 0; --i) {
|
||||
if (_controlPoints[i].time <= elapsedMs) {
|
||||
uint32_t pointTimeMs = static_cast<uint32_t>(((uint64_t)_controlPoints[i].time * (uint64_t)getDuration()) / 1000);
|
||||
if (pointTimeMs <= elapsedMs) {
|
||||
// Found the latest point at or before the elapsed time
|
||||
lastApplicablePoint = &_controlPoints[i];
|
||||
break; // Since sorted, this is the correct one
|
||||
@ -218,7 +208,7 @@ bool SignalPlot::getCurrentControlPointInfo(uint8_t& outId, uint32_t& outTimeMs,
|
||||
|
||||
if (activePoint != nullptr) {
|
||||
outId = activePoint->id;
|
||||
outTimeMs = activePoint->time;
|
||||
outTimeMs = static_cast<uint32_t>(((uint64_t)activePoint->time * (uint64_t)getDuration()) / 1000);
|
||||
outValue = static_cast<int16_t>(activePoint->state); // Cast enum state to int16_t
|
||||
outUser = *((int16_t*)activePoint->user);
|
||||
return true;
|
||||
@ -338,7 +328,6 @@ short SignalPlot::mb_tcp_write(MB_Registers *reg, short value) {
|
||||
|
||||
void SignalPlot::start() {
|
||||
PlotBase::start();
|
||||
// Reset execution state for all control points when the plot starts
|
||||
for (uint8_t i = 0; i < _numControlPoints; ++i) {
|
||||
_controlPoints[i].state = E_SIGNAL_STATE::STATE_NONE;
|
||||
}
|
||||
@ -350,9 +339,7 @@ void SignalPlot::executeControlPointAction(uint8_t cpIndex) {
|
||||
Log.errorln("%s: Invalid control point index %d in executeControlPointAction.", name.c_str(), cpIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
S_SignalControlPoint& cp = _controlPoints[cpIndex];
|
||||
|
||||
switch (cp.type) {
|
||||
case E_SIGNAL_TYPE::MB_WRITE_COIL: {
|
||||
if (modbusTCP == nullptr || modbusTCP->modbusServer == nullptr) {
|
||||
@ -419,7 +406,6 @@ void SignalPlot::executeControlPointAction(uint8_t cpIndex) {
|
||||
|
||||
String cmdStr = String((char*)cp.user);
|
||||
Log.infoln("%s: CP id %d: CALL_METHOD executing: %s", name.c_str(), cp.id, cmdStr.c_str());
|
||||
|
||||
Bridge* bridge = static_cast<Bridge*>(owner->byId(COMPONENT_KEY_MB_BRIDGE));
|
||||
if (!bridge) {
|
||||
Log.errorln("%s: CP id %d: Bridge component not found.", name.c_str(), cp.id);
|
||||
@ -444,6 +430,31 @@ void SignalPlot::executeControlPointAction(uint8_t cpIndex) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case E_SIGNAL_TYPE::DISPLAY_MESSAGE: {
|
||||
String message = cp.description;
|
||||
if (message.isEmpty()) {
|
||||
message = cp.name;
|
||||
}
|
||||
PHApp* phApp = static_cast<PHApp*>(owner);
|
||||
if (phApp) {
|
||||
JsonDocument doc;
|
||||
doc["message"] = message;
|
||||
doc["id"] = cp.id;
|
||||
doc["type"] = "display_message";
|
||||
phApp->broadcast(BROADCAST_USER_MESSAGE, doc);
|
||||
}else{
|
||||
Log.errorln("%s: CP id %d: DISPLAY_MESSAGE: PHApp component not found.", name.c_str(), cp.id);
|
||||
}
|
||||
Log.infoln("%s: CP id %d: DISPLAY_MESSAGE: %s", name.c_str(), cp.id, message.c_str());
|
||||
cp.state = E_SIGNAL_STATE::STATE_ON;
|
||||
break;
|
||||
}
|
||||
case E_SIGNAL_TYPE::PAUSE: {
|
||||
Log.infoln("%s: CP id %d: PAUSE", name.c_str(), cp.id);
|
||||
pause();
|
||||
cp.state = E_SIGNAL_STATE::STATE_ON;
|
||||
break;
|
||||
}
|
||||
case E_SIGNAL_TYPE::MB_WRITE_HOLDING_REGISTER:
|
||||
Log.warningln("%s: CP id %d: MB_WRITE_HOLDING_REGISTER not yet fully implemented in loop.", name.c_str(), cp.id);
|
||||
cp.state = E_SIGNAL_STATE::STATE_ERROR;
|
||||
|
||||
@ -40,7 +40,9 @@ enum class E_SIGNAL_TYPE : int16_t
|
||||
CALL_FUNCTION = 4,
|
||||
CALL_REST = 5,
|
||||
GPIO_WRITE = 6,
|
||||
USER_DEFINED = 7
|
||||
DISPLAY_MESSAGE = 7,
|
||||
USER_DEFINED = 8,
|
||||
PAUSE = 9
|
||||
};
|
||||
|
||||
// New enum for GPIO Write Modes
|
||||
@ -77,6 +79,8 @@ struct S_SignalControlPoint
|
||||
* - CALL_FUNCTION: Function ID (ushort) // TODO: not implemented yet
|
||||
* - CALL_REST: Not used directly, path/params likely in name/description or separate storage. // TODO: not implemented yet
|
||||
* - USER_DEFINED: User-specific. // TODO: not implemented yet
|
||||
* - DISPLAY_MESSAGE: Not currently used.
|
||||
* - PAUSE: Not used.
|
||||
*/
|
||||
int16_t arg_0;
|
||||
|
||||
@ -88,6 +92,8 @@ struct S_SignalControlPoint
|
||||
* - CALL_METHOD: Method index/ID. // TODO: not implemented yet
|
||||
* - CALL_FUNCTION: Not used typically, or first param. // TODO: not implemented yet
|
||||
* - USER_DEFINED: User-specific. // TODO: not implemented yet
|
||||
* - DISPLAY_MESSAGE: Not currently used.
|
||||
* - PAUSE: Not used.
|
||||
*/
|
||||
int16_t arg_1;
|
||||
|
||||
@ -97,6 +103,8 @@ struct S_SignalControlPoint
|
||||
* - CALL_METHOD: First method parameter (if any). // TODO: not implemented yet
|
||||
* - CALL_FUNCTION: Second param / etc. // TODO: not implemented yet
|
||||
* - USER_DEFINED: User-specific. // TODO: not implemented yet
|
||||
* - DISPLAY_MESSAGE: Not currently used.
|
||||
* - PAUSE: Not used.
|
||||
* - Others: Not used (typically).
|
||||
*/
|
||||
int16_t arg_2;
|
||||
@ -200,6 +208,7 @@ private:
|
||||
ModbusTCP *modbusTCP;
|
||||
MB_Registers _modbusBlocks[SIGNAL_PLOT_REGISTER_COUNT];
|
||||
ModbusBlockView _modbusBlockView;
|
||||
uint32_t _lastLogMs = 0;
|
||||
};
|
||||
|
||||
#endif // SIGNAL_PLOT_H
|
||||
@ -277,6 +277,11 @@ bool TemperatureProfile::load(const JsonObject &config)
|
||||
{
|
||||
uint32_t duration_s = config["duration"].as<uint32_t>();
|
||||
_durationMs = duration_s;
|
||||
for (int i = 0; i < MAX_PLOTS; ++i) {
|
||||
if (_plots[i]) {
|
||||
_plots[i]->setDuration(_durationMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -354,7 +359,7 @@ bool TemperatureProfile::load(const JsonObject &config)
|
||||
Log.infoln("%s: Loaded %d target registers.", name.c_str(), _targetRegisters.size());
|
||||
|
||||
// Load associated signal plot slot ID if present
|
||||
if (config.containsKey("signalPlot") && config["signalPlot"].is<int>())
|
||||
if (config["signalPlot"].is<int>())
|
||||
{
|
||||
_signalPlotSlotId = config["signalPlot"].as<short>();
|
||||
Log.verboseln("%s: Loaded signalPlot: %d", name.c_str(), _signalPlotSlotId);
|
||||
@ -761,63 +766,23 @@ uint16_t TemperatureProfile::getCount() const
|
||||
return count;
|
||||
}
|
||||
|
||||
void TemperatureProfile::start()
|
||||
{
|
||||
PlotBase::start();
|
||||
if (_signalPlotSlotId >= 0 && owner != nullptr)
|
||||
{
|
||||
PHApp* app = static_cast<PHApp*>(owner);
|
||||
app->startSignalPlot(_signalPlotSlotId);
|
||||
}
|
||||
}
|
||||
|
||||
void TemperatureProfile::stop()
|
||||
{
|
||||
PlotBase::stop();
|
||||
if (_signalPlotSlotId >= 0 && owner != nullptr)
|
||||
{
|
||||
PHApp* app = static_cast<PHApp*>(owner);
|
||||
app->stopSignalPlot(_signalPlotSlotId);
|
||||
}
|
||||
}
|
||||
|
||||
void TemperatureProfile::enable()
|
||||
{
|
||||
PlotBase::enable();
|
||||
if (_signalPlotSlotId >= 0 && owner != nullptr)
|
||||
{
|
||||
PHApp* app = static_cast<PHApp*>(owner);
|
||||
app->enableSignalPlot(_signalPlotSlotId, true);
|
||||
for (int i = 0; i < MAX_PLOTS; ++i) {
|
||||
if (_plots[i]) {
|
||||
_plots[i]->enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TemperatureProfile::disable()
|
||||
{
|
||||
PlotBase::disable();
|
||||
if (_signalPlotSlotId >= 0 && owner != nullptr)
|
||||
{
|
||||
PHApp* app = static_cast<PHApp*>(owner);
|
||||
app->enableSignalPlot(_signalPlotSlotId, false);
|
||||
}
|
||||
}
|
||||
|
||||
void TemperatureProfile::pause()
|
||||
{
|
||||
PlotBase::pause();
|
||||
if (_signalPlotSlotId >= 0 && owner != nullptr)
|
||||
{
|
||||
PHApp* app = static_cast<PHApp*>(owner);
|
||||
app->pauseSignalPlot(_signalPlotSlotId);
|
||||
}
|
||||
}
|
||||
|
||||
void TemperatureProfile::resume()
|
||||
{
|
||||
PlotBase::resume();
|
||||
if (_signalPlotSlotId >= 0 && owner != nullptr)
|
||||
{
|
||||
PHApp* app = static_cast<PHApp*>(owner);
|
||||
app->resumeSignalPlot(_signalPlotSlotId);
|
||||
for (int i = 0; i < MAX_PLOTS; ++i) {
|
||||
if (_plots[i]) {
|
||||
_plots[i]->disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,4 @@
|
||||
/**
|
||||
* TemperatureProfile.h
|
||||
* Revision: initial documentation
|
||||
*/
|
||||
|
||||
#nfndef TEMPERATURE_PROFILE_H
|
||||
#ifndef TEMPERATURE_PROFILE_H
|
||||
#define TEMPERATURE_PROFILE_H
|
||||
|
||||
#include "PlotBase.h"
|
||||
@ -13,7 +8,7 @@
|
||||
#include <ArduinoJson.h>
|
||||
#include <Component.h>
|
||||
#include <modbus/ModbusTypes.h>
|
||||
#include "../ValueWrapper.h" // Include the updated ValueWrapper
|
||||
#include <ValueWrapper.h>
|
||||
#include <vector>
|
||||
|
||||
class ModbusTCP;
|
||||
@ -71,13 +66,8 @@ public:
|
||||
short setup() override;
|
||||
short loop() override;
|
||||
|
||||
// Overriding PlotBase methods to integrate SignalPlot control
|
||||
void start() override;
|
||||
void stop() override;
|
||||
void enable();
|
||||
void disable();
|
||||
void pause() override;
|
||||
void resume() override;
|
||||
|
||||
// --- Profile Specific Methods ---
|
||||
/**
|
||||
@ -222,4 +212,4 @@ private:
|
||||
void _initializeControlPoints();
|
||||
};
|
||||
|
||||
#endif // TEMPERATURE_PROFILE_H
|
||||
#endif // TEMPERATURE_PROFILE_H
|
||||
Loading…
Reference in New Issue
Block a user