plot silbing

This commit is contained in:
lovebird 2025-06-06 14:02:29 +02:00
parent da734afe6d
commit 147a55ff10
12 changed files with 740 additions and 159 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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