Add binary WebSocket updates and lag compensation; extend PlotStatus
This commit is contained in:
parent
2f92442b00
commit
357e36b24d
@ -14,7 +14,7 @@
|
||||
#define NETWORKVALUE_PERSISTENCE_ENABLED
|
||||
|
||||
#ifdef NETWORKVALUE_PERSISTENCE_ENABLED
|
||||
#include <ArduinoJson.h>
|
||||
#include <ArduinoJson.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
@ -44,7 +44,7 @@
|
||||
#include "NetworkValuePB.h"
|
||||
#define NETWORKVALUE_PROTOBUF_INHERITANCE , public maybe<NETWORKVALUE_ENABLE_PROTOBUF, NV_Protobuf>
|
||||
#else
|
||||
#define NETWORKVALUE_PROTOBUF_INHERITANCE
|
||||
#define NETWORKVALUE_PROTOBUF_INHERITANCE
|
||||
#endif
|
||||
|
||||
enum class NetworkValue_ThresholdMode : uint8_t
|
||||
@ -87,10 +87,10 @@ public:
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void log(int level, const char *componentName, const char *fmt, Args... args) const { }
|
||||
void log(int level, const char *componentName, const char *fmt, Args... args) const {}
|
||||
void setup_feature() {}
|
||||
void loop_feature() {}
|
||||
void info_feature() const { }
|
||||
void info_feature() const {}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -124,7 +124,6 @@ public:
|
||||
void loop_feature() {}
|
||||
void info_feature() const
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@ -222,7 +221,8 @@ public:
|
||||
|
||||
bool applyUpdate(const T &newValue)
|
||||
{
|
||||
if (!checkChanged(newValue)) {
|
||||
if (!checkChanged(newValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
T oldValue = m_lastValueOnUpdate;
|
||||
@ -236,7 +236,7 @@ public:
|
||||
}
|
||||
|
||||
T getValue() const { return m_value; }
|
||||
T& getValueRef() { return m_value; }
|
||||
T &getValueRef() { return m_value; }
|
||||
|
||||
void setup_feature() {}
|
||||
void loop_feature() {}
|
||||
@ -281,8 +281,10 @@ public:
|
||||
// This specialized version of checkChanged now compares the new value against the last known state.
|
||||
bool checkChanged(const std::array<T, N> &newValue) const
|
||||
{
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
if (newValue[i] != m_lastValueOnUpdate[i]) {
|
||||
for (size_t i = 0; i < N; ++i)
|
||||
{
|
||||
if (newValue[i] != m_lastValueOnUpdate[i])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -291,7 +293,8 @@ public:
|
||||
|
||||
bool applyUpdate(const std::array<T, N> &newValue)
|
||||
{
|
||||
if (!checkChanged(newValue)) {
|
||||
if (!checkChanged(newValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::array<T, N> oldValue = m_lastValueOnUpdate;
|
||||
@ -305,7 +308,7 @@ public:
|
||||
}
|
||||
|
||||
std::array<T, N> getValue() const { return m_value; }
|
||||
std::array<T, N>& getValueRef() { return m_value; }
|
||||
std::array<T, N> &getValueRef() { return m_value; }
|
||||
|
||||
void setup_feature() {}
|
||||
void loop_feature() {}
|
||||
@ -349,8 +352,10 @@ public:
|
||||
|
||||
bool checkChanged(const std::array<bool, N> &newValue) const
|
||||
{
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
if (newValue[i] != m_lastValueOnUpdate[i]) {
|
||||
for (size_t i = 0; i < N; ++i)
|
||||
{
|
||||
if (newValue[i] != m_lastValueOnUpdate[i])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -359,7 +364,8 @@ public:
|
||||
|
||||
bool applyUpdate(const std::array<bool, N> &newValue)
|
||||
{
|
||||
if (!checkChanged(newValue)) {
|
||||
if (!checkChanged(newValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::array<bool, N> oldValue = m_lastValueOnUpdate;
|
||||
@ -373,7 +379,7 @@ public:
|
||||
}
|
||||
|
||||
std::array<bool, N> getValue() const { return m_value; }
|
||||
std::array<bool, N>& getValueRef() { return m_value; }
|
||||
std::array<bool, N> &getValueRef() { return m_value; }
|
||||
|
||||
void setup_feature() {}
|
||||
void loop_feature() {}
|
||||
@ -399,8 +405,7 @@ template <typename T>
|
||||
class NetworkValue : public NetworkValueBase,
|
||||
public maybe<NETWORKVALUE_ENABLE_LOGGING, NV_Logging>,
|
||||
public maybe<NETWORKVALUE_ENABLE_MODBUS, NV_Modbus>,
|
||||
public maybe<NETWORKVALUE_ENABLE_NOTIFY, NV_Notify<T>>
|
||||
NETWORKVALUE_PROTOBUF_INHERITANCE
|
||||
public maybe<NETWORKVALUE_ENABLE_NOTIFY, NV_Notify<T>> NETWORKVALUE_PROTOBUF_INHERITANCE
|
||||
{
|
||||
private:
|
||||
// --- Tag Dispatching for update() method ---
|
||||
@ -429,18 +434,22 @@ private:
|
||||
if (this->owner)
|
||||
{
|
||||
#if (NETWORKVALUE_ENABLE_PROTOBUF == 1)
|
||||
if (hasFeature(E_NetworkValueFeatureFlags::E_NVFF_PROTOBUF)) {
|
||||
if (hasFeature(E_NetworkValueFeatureFlags::E_NVFF_PROTOBUF))
|
||||
{
|
||||
uint8_t buffer[64]; // Static buffer for PB encoding
|
||||
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
|
||||
|
||||
|
||||
MB_Registers regInfo = this->getRegisterInfo();
|
||||
if (this->encode(&stream, regInfo, newValue)) {
|
||||
if (this->encode(&stream, regInfo, newValue))
|
||||
{
|
||||
PB_UpdateData pb_msg;
|
||||
pb_msg.data = buffer;
|
||||
pb_msg.len = stream.bytes_written;
|
||||
pb_msg.componentId = this->id;
|
||||
this->owner->onMessage(this->id, E_CALLS::EC_PROTOBUF_UPDATE, E_MessageFlags::E_MF_NONE, &pb_msg, this->owner);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.warningln(F(" [update_impl] Protobuf encoding failed for '%s'"), this->name.c_str());
|
||||
}
|
||||
}
|
||||
@ -471,18 +480,22 @@ private:
|
||||
if (this->owner)
|
||||
{
|
||||
#if (NETWORKVALUE_ENABLE_PROTOBUF == 1)
|
||||
if (hasFeature(E_NetworkValueFeatureFlags::E_NVFF_PROTOBUF)) {
|
||||
if (hasFeature(E_NetworkValueFeatureFlags::E_NVFF_PROTOBUF))
|
||||
{
|
||||
uint8_t buffer[256]; // Larger buffer for arrays
|
||||
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
|
||||
|
||||
|
||||
MB_Registers regInfo = this->getRegisterInfo();
|
||||
if (this->encode(&stream, regInfo, newValue)) {
|
||||
if (this->encode(&stream, regInfo, newValue))
|
||||
{
|
||||
PB_UpdateData pb_msg;
|
||||
pb_msg.data = buffer;
|
||||
pb_msg.len = stream.bytes_written;
|
||||
pb_msg.componentId = this->id;
|
||||
this->owner->onMessage(this->id, E_CALLS::EC_PROTOBUF_UPDATE, E_MessageFlags::E_MF_NONE, &pb_msg, this->owner);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.warningln(F(" [update_impl] Protobuf encoding failed for array '%s'"), this->name.c_str());
|
||||
}
|
||||
}
|
||||
@ -518,12 +531,10 @@ private:
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
|
||||
NetworkValue(Component *owner, ushort id,
|
||||
const char *name,
|
||||
T initial, T threshold, NetworkValue_ThresholdMode mode, void (*cb)(const T &, const T &) = nullptr,
|
||||
uint8_t featureFlags = static_cast<uint8_t>(E_NetworkValueFeatureFlags::E_NVFF_ALL))
|
||||
NetworkValue(Component *owner, ushort id,
|
||||
const char *name,
|
||||
T initial, T threshold, NetworkValue_ThresholdMode mode, void (*cb)(const T &, const T &) = nullptr,
|
||||
uint8_t featureFlags = static_cast<uint8_t>(E_NetworkValueFeatureFlags::E_NVFF_ALL))
|
||||
: NetworkValueBase(name, id, COMPONENT_DEFAULT, owner, featureFlags)
|
||||
{
|
||||
initNotify(initial, threshold, mode, cb);
|
||||
@ -539,10 +550,10 @@ public:
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
NetworkValue(Component *owner, ushort id,
|
||||
const char *name,
|
||||
uint8_t featureFlags = static_cast<uint8_t>(E_NetworkValueFeatureFlags::E_NVFF_ALL))
|
||||
|
||||
NetworkValue(Component *owner, ushort id,
|
||||
const char *name,
|
||||
uint8_t featureFlags = static_cast<uint8_t>(E_NetworkValueFeatureFlags::E_NVFF_ALL))
|
||||
: NetworkValueBase(name, id, COMPONENT_DEFAULT, owner, featureFlags)
|
||||
{
|
||||
#if (NETWORKVALUE_ENABLE_LOGGING == 1)
|
||||
@ -610,7 +621,8 @@ public:
|
||||
return E_OK;
|
||||
}
|
||||
|
||||
short info() override {
|
||||
short info() override
|
||||
{
|
||||
Component::info();
|
||||
#if (NETWORKVALUE_ENABLE_LOGGING == 1)
|
||||
if (hasFeature(E_NetworkValueFeatureFlags::E_NVFF_LOGGING))
|
||||
@ -629,10 +641,11 @@ public:
|
||||
template <typename U = T>
|
||||
bool update(const U &newValue, E_PRIORITY priority = E_PRIORITY::E_PRIORITY_LOWEST)
|
||||
{
|
||||
if (!this->applyUpdate(newValue)) {
|
||||
if (!this->applyUpdate(newValue))
|
||||
{
|
||||
return false; // If no change, do nothing further.
|
||||
}
|
||||
|
||||
|
||||
#if (NETWORKVALUE_ENABLE_LOGGING == 1)
|
||||
if (hasFeature(E_NetworkValueFeatureFlags::E_NVFF_LOGGING))
|
||||
{
|
||||
@ -641,10 +654,11 @@ public:
|
||||
#endif
|
||||
|
||||
// Dispatch notifications
|
||||
if (this->owner) {
|
||||
if (this->owner)
|
||||
{
|
||||
update_impl(newValue, typename get_update_tag<U>::type(), priority);
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -685,18 +699,20 @@ public:
|
||||
if (NETWORKVALUE_ENABLE_NOTIFY && hasFeature(E_NetworkValueFeatureFlags::E_NVFF_NOTIFY))
|
||||
{
|
||||
NV_Notify<T>::init_feature(initial, threshold, mode, cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool applyUpdate(const T& newValue) {
|
||||
if (NETWORKVALUE_ENABLE_NOTIFY && hasFeature(E_NetworkValueFeatureFlags::E_NVFF_NOTIFY)) {
|
||||
bool applyUpdate(const T &newValue)
|
||||
{
|
||||
if (NETWORKVALUE_ENABLE_NOTIFY && hasFeature(E_NetworkValueFeatureFlags::E_NVFF_NOTIFY))
|
||||
{
|
||||
return NV_Notify<T>::applyUpdate(newValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
T getValue() const { return NV_Notify<T>::getValue(); }
|
||||
T& getValueRef() { return NV_Notify<T>::getValueRef(); }
|
||||
T &getValueRef() { return NV_Notify<T>::getValueRef(); }
|
||||
|
||||
using NV_Notify<T>::getValue;
|
||||
using NV_Notify<T>::getValueRef;
|
||||
@ -750,14 +766,14 @@ public:
|
||||
void fromJSON(const JsonObjT &obj)
|
||||
{
|
||||
#if NETWORKVALUE_ENABLE_LOGGING
|
||||
if (hasFeature(E_NetworkValueFeatureFlags::E_NVFF_LOGGING) && obj.containsKey("logging_enabled"))
|
||||
if (hasFeature(E_NetworkValueFeatureFlags::E_NVFF_LOGGING) && !obj["logging_enabled"].isNull())
|
||||
{
|
||||
this->enableLogging(obj["logging_enabled"].template as<bool>());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NETWORKVALUE_ENABLE_MODBUS
|
||||
if (hasFeature(E_NetworkValueFeatureFlags::E_NVFF_MODBUS) && obj.containsKey("modbus"))
|
||||
if (hasFeature(E_NetworkValueFeatureFlags::E_NVFF_MODBUS) && !obj["modbus"].isNull())
|
||||
{
|
||||
auto mod = obj["modbus"].template as<ArduinoJson::JsonObjectConst>();
|
||||
ushort startAddress = mod["startAddress"].template as<ushort>();
|
||||
@ -771,7 +787,7 @@ public:
|
||||
#endif
|
||||
|
||||
#if NETWORKVALUE_ENABLE_NOTIFY
|
||||
if (hasFeature(E_NetworkValueFeatureFlags::E_NVFF_NOTIFY) && obj.containsKey("notify"))
|
||||
if (hasFeature(E_NetworkValueFeatureFlags::E_NVFF_NOTIFY) && !obj["notify"].isNull())
|
||||
{
|
||||
auto notify = obj["notify"].template as<ArduinoJson::JsonObjectConst>();
|
||||
T threshold = notify["threshold"].template as<T>();
|
||||
|
||||
@ -54,10 +54,9 @@ enum class E_DELTA_MS300_FAULT_STATUS_FLAGS : uint16_t
|
||||
// MS300 Frequency Command Register (2001H)
|
||||
#define DELTA_REG_SET_FREQ static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_FREQUENCY_COMMAND)
|
||||
|
||||
|
||||
// Enum for TCP Offsets is now in DELTA_VFD.h
|
||||
|
||||
DELTA_VFD::DELTA_VFD(Component* owner, uint8_t slaveId, millis_t readInterval)
|
||||
DELTA_VFD::DELTA_VFD(Component *owner, uint8_t slaveId, millis_t readInterval)
|
||||
: VFD_Base(owner, slaveId, COMPONENT_KEY_DELTA_VFD, readInterval)
|
||||
{
|
||||
componentId = id;
|
||||
@ -87,7 +86,7 @@ DELTA_VFD::DELTA_VFD(Component* owner, uint8_t slaveId, millis_t readInterval)
|
||||
short DELTA_VFD::setup()
|
||||
{
|
||||
L_INFO(F("DELTA_VFD[%d]: Setting up..."), slaveId);
|
||||
|
||||
|
||||
// --- Configure Mandatory Read Blocks ---
|
||||
// Block 1: Status and monitoring registers (0x2100-0x2106) - Status Codes, Fault Status, Freq Cmd, Output Freq, Current, DC Bus, Output Voltage
|
||||
ModbusReadBlock *statusMonitorBlock = addMandatoryReadBlock(
|
||||
@ -117,12 +116,12 @@ short DELTA_VFD::setup()
|
||||
}
|
||||
#endif
|
||||
// Add output registers for VFD control
|
||||
this->addOutputRegister(DELTA_REG_SET_FREQ, E_FN_CODE::FN_WRITE_HOLD_REGISTER, 0, PRIORITY_HIGH);
|
||||
this->addOutputRegister(static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_CONTROL_COMMAND),
|
||||
this->addOutputRegister(DELTA_REG_SET_FREQ, E_FN_CODE::FN_WRITE_HOLD_REGISTER, 0, PRIORITY_HIGH);
|
||||
this->addOutputRegister(static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_CONTROL_COMMAND),
|
||||
E_FN_CODE::FN_WRITE_HOLD_REGISTER,
|
||||
0, // Default to no command (stopped)
|
||||
PRIORITY_HIGH);
|
||||
|
||||
|
||||
return E_OK;
|
||||
}
|
||||
|
||||
@ -181,7 +180,6 @@ bool DELTA_VFD::onRegisterUpdate(uint16_t address, uint16_t newValue)
|
||||
bool updated = false;
|
||||
// Update local state based on the register address using MS300 registers
|
||||
|
||||
|
||||
switch (address)
|
||||
{
|
||||
case static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_MON_OUTPUT_FREQ):
|
||||
@ -214,10 +212,11 @@ bool DELTA_VFD::onRegisterUpdate(uint16_t address, uint16_t newValue)
|
||||
updated = true;
|
||||
break;
|
||||
case static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_STATUS_CODES): // 0x2100 - Status Codes
|
||||
_faultCode = newValue & 0xFF; // Low byte = error code
|
||||
_faultCode = newValue & 0xFF; // Low byte = error code
|
||||
_faultValid = true;
|
||||
// Check for specific fault flags if high byte has fault status flags
|
||||
if (newValue & 0xFF00) {
|
||||
if (newValue & 0xFF00)
|
||||
{
|
||||
_faultStatusFlags = (newValue >> 8) & 0xFF; // High byte contains fault status flags
|
||||
}
|
||||
_updateVfdState();
|
||||
@ -225,7 +224,7 @@ bool DELTA_VFD::onRegisterUpdate(uint16_t address, uint16_t newValue)
|
||||
break;
|
||||
default:
|
||||
// Check if it's within the monitoring range
|
||||
if (address >= static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_MON_FREQ_CMD) &&
|
||||
if (address >= static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_MON_FREQ_CMD) &&
|
||||
address <= static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_MON_OUTPUT_TORQUE))
|
||||
{
|
||||
// Log.traceln(F("DELTA_VFD[%d]: MS300 monitoring register update (Addr: 0x%04X) -> %d"), slaveId, address, newValue);
|
||||
@ -234,9 +233,10 @@ bool DELTA_VFD::onRegisterUpdate(uint16_t address, uint16_t newValue)
|
||||
}
|
||||
|
||||
// Call base class method
|
||||
if (RTU_Base::onRegisterUpdate(address, newValue)) updated = true;
|
||||
if (RTU_Base::onRegisterUpdate(address, newValue))
|
||||
updated = true;
|
||||
|
||||
//L_INFO(F("DELTA_VFD[%d]: Register Update (Addr: 0x%04X) -> %d"), slaveId, address, newValue);
|
||||
// L_INFO(F("DELTA_VFD[%d]: Register Update (Addr: 0x%04X) -> %d"), slaveId, address, newValue);
|
||||
|
||||
return updated;
|
||||
}
|
||||
@ -299,7 +299,7 @@ bool DELTA_VFD::hasFaultType(E_DELTA_MS300_FAULT_STATUS_FLAGS faultType) const
|
||||
{
|
||||
if (!_faultValid)
|
||||
return false;
|
||||
|
||||
|
||||
return (_faultStatusFlags & static_cast<uint8_t>(faultType)) != 0;
|
||||
}
|
||||
|
||||
@ -324,8 +324,7 @@ bool DELTA_VFD::run()
|
||||
static_cast<uint8_t>(E_DELTA_MS300_CMD_ACCEL_DECEL::ACCEL_DECEL_1),
|
||||
static_cast<uint8_t>(E_DELTA_MS300_CMD_SPEED_SELECT::MASTER_SPEED),
|
||||
static_cast<uint8_t>(E_DELTA_MS300_CMD_ENABLE::DISABLE),
|
||||
static_cast<uint8_t>(E_DELTA_MS300_CMD_OPERATION_SOURCE::PR_00_21_SETTING)
|
||||
);
|
||||
static_cast<uint8_t>(E_DELTA_MS300_CMD_OPERATION_SOURCE::PR_00_21_SETTING));
|
||||
uint16_t reg = static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_CONTROL_COMMAND);
|
||||
this->setOutputRegisterValue(reg, cmd);
|
||||
return true;
|
||||
@ -339,8 +338,7 @@ bool DELTA_VFD::reverse()
|
||||
static_cast<uint8_t>(E_DELTA_MS300_CMD_ACCEL_DECEL::ACCEL_DECEL_1),
|
||||
static_cast<uint8_t>(E_DELTA_MS300_CMD_SPEED_SELECT::MASTER_SPEED),
|
||||
static_cast<uint8_t>(E_DELTA_MS300_CMD_ENABLE::DISABLE),
|
||||
static_cast<uint8_t>(E_DELTA_MS300_CMD_OPERATION_SOURCE::PR_00_21_SETTING)
|
||||
);
|
||||
static_cast<uint8_t>(E_DELTA_MS300_CMD_OPERATION_SOURCE::PR_00_21_SETTING));
|
||||
uint16_t reg = static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_CONTROL_COMMAND);
|
||||
this->setOutputRegisterValue(reg, cmd);
|
||||
return true;
|
||||
@ -354,8 +352,7 @@ short DELTA_VFD::stop()
|
||||
static_cast<uint8_t>(E_DELTA_MS300_CMD_ACCEL_DECEL::ACCEL_DECEL_1),
|
||||
static_cast<uint8_t>(E_DELTA_MS300_CMD_SPEED_SELECT::MASTER_SPEED),
|
||||
static_cast<uint8_t>(E_DELTA_MS300_CMD_ENABLE::DISABLE),
|
||||
static_cast<uint8_t>(E_DELTA_MS300_CMD_OPERATION_SOURCE::PR_00_21_SETTING)
|
||||
);
|
||||
static_cast<uint8_t>(E_DELTA_MS300_CMD_OPERATION_SOURCE::PR_00_21_SETTING));
|
||||
uint16_t reg = static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_CONTROL_COMMAND);
|
||||
this->setOutputRegisterValue(reg, cmd);
|
||||
return true;
|
||||
@ -505,7 +502,7 @@ short DELTA_VFD::mb_tcp_read(MB_Registers *reg)
|
||||
success = _setFrequencyValid; // Corrected: was _setFrequencyValid / 100
|
||||
value = success ? (_setFrequency / 100) : 0xFFFF; // Display in Hz
|
||||
break;
|
||||
case E_DELTA_TCP_OFFSET::CMD_DIRECTION: // Read associated with command write returns current running state?
|
||||
case E_DELTA_TCP_OFFSET::CMD_DIRECTION: // Read associated with command write returns current running state?
|
||||
value = _statusValid ? _statusRegister : 0xFFFF; // Return raw status
|
||||
success = _statusValid;
|
||||
break;
|
||||
@ -533,15 +530,15 @@ short DELTA_VFD::mb_tcp_read(MB_Registers *reg)
|
||||
short DELTA_VFD::setupVFD()
|
||||
{
|
||||
RS485 *rs485 = (RS485 *)owner;
|
||||
|
||||
|
||||
// Configure MS300 parameters for communication control
|
||||
|
||||
|
||||
// /rs485->modbus.writeRegister(this->slaveId, static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_MASTER_FREQ_CMD_SOURCE), 9, true); // Master Freq Cmd Source (00-20)
|
||||
rs485->modbus.writeRegister(this->slaveId, static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_OPERATION_CMD_SOURCE), 2, true); // Operation Cmd Source (00-21)
|
||||
rs485->modbus.writeRegister(this->slaveId, static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_OPERATION_CMD_SOURCE), 2, true); // Operation Cmd Source (00-21)
|
||||
rs485->modbus.writeRegister(this->slaveId, static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_MAX_OP_FREQ_MOTOR_1), DELTA_MAX_OP_FREQ_MOTOR_1, true); // Max Op Freq Motor 1 (01-00) = 75.00 Hz
|
||||
rs485->modbus.writeRegister(this->slaveId, static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_ACCEL_TIME_1_MOTOR_1), 150, true); // Accel Time 1 (01-12) = 1.50 sec
|
||||
rs485->modbus.writeRegister(this->slaveId, static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_DECEL_TIME_1_MOTOR_1), 150, true); // Decel Time 1 (01-13) = 1.50 sec
|
||||
rs485->modbus.writeRegister(this->slaveId, static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_OUTPUT_VOLTAGE_MOTOR_1), 2200, true); // Motor Voltage (01-02) = 220.0V
|
||||
rs485->modbus.writeRegister(this->slaveId, static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_ACCEL_TIME_1_MOTOR_1), 150, true); // Accel Time 1 (01-12) = 1.50 sec
|
||||
rs485->modbus.writeRegister(this->slaveId, static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_DECEL_TIME_1_MOTOR_1), 150, true); // Decel Time 1 (01-13) = 1.50 sec
|
||||
rs485->modbus.writeRegister(this->slaveId, static_cast<uint16_t>(E_DELTA_MS300_REGISTERS::E_DELTA_MS300_OUTPUT_VOLTAGE_MOTOR_1), 2200, true); // Motor Voltage (01-02) = 220.0V
|
||||
|
||||
return E_OK;
|
||||
}
|
||||
@ -583,7 +580,7 @@ short DELTA_VFD::mb_tcp_write(MB_Registers *reg, short value)
|
||||
case 1: // Run Forward
|
||||
commandSuccess = run();
|
||||
break;
|
||||
case 2: // Run Reverse
|
||||
case 2: // Run Reverse
|
||||
commandSuccess = reverse();
|
||||
break;
|
||||
case 0: // Stop
|
||||
@ -604,7 +601,7 @@ short DELTA_VFD::mb_tcp_write(MB_Registers *reg, short value)
|
||||
// Handle custom commands
|
||||
E_DELTA_CMD command = static_cast<E_DELTA_CMD>(value);
|
||||
_lastCommand = command;
|
||||
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case E_DELTA_CMD::E_DTC_INFO:
|
||||
@ -811,7 +808,7 @@ bool DELTA_VFD::getOutputTorquePercent(uint16_t &value) const
|
||||
}
|
||||
#endif
|
||||
|
||||
void DELTA_VFD::onError(ushort errorCode, const char* errorMessage)
|
||||
void DELTA_VFD::onError(ushort errorCode, const char *errorMessage)
|
||||
{
|
||||
// L_ERROR(F("DELTA_VFD[%d]: Modbus Error %d - %s"), slaveId, errorCode, errorMessage ? errorMessage : "Unknown");
|
||||
RTU_Base::onError(errorCode, errorMessage);
|
||||
|
||||
@ -52,9 +52,11 @@ OmronE5::OmronE5(Component *owner, uint8_t slaveId, millis_t readInterval)
|
||||
|
||||
m_pv.initModbus(tcpBaseAddr + (ushort)E_OmronTcpOffset::PV, 1, this->id, this->slaveId, FN_READ_HOLD_REGISTER, "PV", name.c_str());
|
||||
m_sp.initModbus(tcpBaseAddr + (ushort)E_OmronTcpOffset::SP, 1, this->id, this->slaveId, FN_READ_HOLD_REGISTER, "SP", name.c_str());
|
||||
|
||||
m_statusLow.initModbus(tcpBaseAddr + (ushort)E_OmronTcpOffset::STATUS_LOW, 1, this->id, this->slaveId, FN_READ_HOLD_REGISTER, "Status Low", name.c_str());
|
||||
m_statusHigh.initModbus(tcpBaseAddr + (ushort)E_OmronTcpOffset::STATUS_HIGH, 1, this->id, this->slaveId, FN_READ_HOLD_REGISTER, "Status High", name.c_str());
|
||||
m_enabled.initModbus(tcpBaseAddr + (ushort)E_OmronTcpOffset::ENABLED, 1, this->id, this->slaveId, FN_WRITE_COIL, "Enabled", name.c_str());
|
||||
|
||||
m_commsWritingEnabled.initModbus(tcpBaseAddr + (ushort)E_OmronTcpOffset::CMD_COMMS_WRITE, 1, this->id, this->slaveId, FN_WRITE_COIL, "Comms Write", name.c_str());
|
||||
m_runState.initModbus(tcpBaseAddr + (ushort)E_OmronTcpOffset::CMD_STOP, 1, this->id, this->slaveId, FN_WRITE_COIL, "Run/Stop Coil", name.c_str());
|
||||
|
||||
|
||||
@ -5,16 +5,26 @@
|
||||
#include "./OperatorSwitch.h"
|
||||
|
||||
// Helper function to convert State enum to string for logging
|
||||
const char* stateToString(OperatorSwitch::State state) {
|
||||
switch (state) {
|
||||
case OperatorSwitch::State::IDLE: return "IDLE";
|
||||
case OperatorSwitch::State::STOP_PRESSED: return "STOP_PRESSED";
|
||||
case OperatorSwitch::State::CYCLE_PRESSED: return "CYCLE_PRESSED";
|
||||
case OperatorSwitch::State::BOTH_PRESSED: return "BOTH_PRESSED";
|
||||
case OperatorSwitch::State::STOP_HELD: return "STOP_HELD";
|
||||
case OperatorSwitch::State::CYCLE_HELD: return "CYCLE_HELD";
|
||||
case OperatorSwitch::State::BOTH_HELD: return "BOTH_HELD";
|
||||
default: return "UNKNOWN";
|
||||
const char *stateToString(OperatorSwitch::State state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case OperatorSwitch::State::IDLE:
|
||||
return "IDLE";
|
||||
case OperatorSwitch::State::STOP_PRESSED:
|
||||
return "STOP_PRESSED";
|
||||
case OperatorSwitch::State::CYCLE_PRESSED:
|
||||
return "CYCLE_PRESSED";
|
||||
case OperatorSwitch::State::BOTH_PRESSED:
|
||||
return "BOTH_PRESSED";
|
||||
case OperatorSwitch::State::STOP_HELD:
|
||||
return "STOP_HELD";
|
||||
case OperatorSwitch::State::CYCLE_HELD:
|
||||
return "CYCLE_HELD";
|
||||
case OperatorSwitch::State::BOTH_HELD:
|
||||
return "BOTH_HELD";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,9 +126,11 @@ short OperatorSwitch::loop()
|
||||
if (rawInputState != State::IDLE)
|
||||
{
|
||||
unsigned long mostRecentPressTime = 0;
|
||||
if (stopPressed) mostRecentPressTime = max(mostRecentPressTime, buttons[BUTTON_STOP].pressTime);
|
||||
if (cyclePressed) mostRecentPressTime = max(mostRecentPressTime, buttons[BUTTON_CYCLE].pressTime);
|
||||
|
||||
if (stopPressed)
|
||||
mostRecentPressTime = max(mostRecentPressTime, buttons[BUTTON_STOP].pressTime);
|
||||
if (cyclePressed)
|
||||
mostRecentPressTime = max(mostRecentPressTime, buttons[BUTTON_CYCLE].pressTime);
|
||||
|
||||
if ((now - mostRecentPressTime >= PRESS_TIME_MS))
|
||||
{
|
||||
newState = rawInputState;
|
||||
@ -143,35 +155,42 @@ short OperatorSwitch::loop()
|
||||
}
|
||||
else if (!holdEventTriggered && (now - pressStartTime >= HOLD_TIME_MS))
|
||||
{
|
||||
if (currentState == State::STOP_PRESSED) newState = State::STOP_HELD;
|
||||
else if (currentState == State::CYCLE_PRESSED) newState = State::CYCLE_HELD;
|
||||
else if (currentState == State::BOTH_PRESSED) newState = State::BOTH_HELD;
|
||||
|
||||
if (newState != currentState) {
|
||||
if (currentState == State::STOP_PRESSED)
|
||||
newState = State::STOP_HELD;
|
||||
else if (currentState == State::CYCLE_PRESSED)
|
||||
newState = State::CYCLE_HELD;
|
||||
else if (currentState == State::BOTH_PRESSED)
|
||||
newState = State::BOTH_HELD;
|
||||
|
||||
if (newState != currentState)
|
||||
{
|
||||
holdEventTriggered = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case State::STOP_HELD:
|
||||
if (!stopPressed) newState = State::IDLE;
|
||||
if (!stopPressed)
|
||||
newState = State::IDLE;
|
||||
break;
|
||||
case State::CYCLE_HELD:
|
||||
if (!cyclePressed) newState = State::IDLE;
|
||||
if (!cyclePressed)
|
||||
newState = State::IDLE;
|
||||
break;
|
||||
case State::BOTH_HELD:
|
||||
if (!stopPressed || !cyclePressed) newState = State::IDLE;
|
||||
if (!stopPressed || !cyclePressed)
|
||||
newState = State::IDLE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (newState != currentState) {
|
||||
if (newState != currentState)
|
||||
{
|
||||
m_state.update(newState);
|
||||
notifyStateChange();
|
||||
}
|
||||
|
||||
|
||||
return E_OK;
|
||||
}
|
||||
|
||||
@ -184,7 +203,6 @@ short OperatorSwitch::info(short val0, short val1)
|
||||
|
||||
void OperatorSwitch::notifyStateChange()
|
||||
{
|
||||
Log.verboseln("OperatorSwitch::notifyStateChange, state: %s", stateToString(m_state.getValue()));
|
||||
Component::notifyStateChange();
|
||||
}
|
||||
|
||||
@ -196,12 +214,13 @@ short OperatorSwitch::mb_tcp_write(MB_Registers *reg, short networkValue)
|
||||
short OperatorSwitch::mb_tcp_read(MB_Registers *reg)
|
||||
{
|
||||
short result = NetworkComponent::mb_tcp_read(reg);
|
||||
if (result != E_NOT_IMPLEMENTED) {
|
||||
if (result != E_NOT_IMPLEMENTED)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
uint16_t address = reg->startAddress;
|
||||
|
||||
|
||||
if (address == (_baseAddress + (ushort)E_MB_Offset::STATE))
|
||||
{
|
||||
return (ushort)m_state.getValue();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -14,6 +14,7 @@
|
||||
|
||||
class Loadcell;
|
||||
class Solenoid;
|
||||
class PushButton;
|
||||
|
||||
// Default pressure thresholds for states
|
||||
#define PRESS_CYLINDER_DEFAULT_HOMING_THRESHOLD 5
|
||||
@ -21,22 +22,22 @@ class Solenoid;
|
||||
#define PRESS_CYLINDER_DEFAULT_MAXLOAD_THRESHOLD 3200
|
||||
#define PRESS_CYLINDER_DEFAULT_HIGHLOAD_THRESHOLD 3000
|
||||
#define PRESS_CYLINDER_DEFAULT_OVERLOAD 120
|
||||
#define PRESS_CYLINDER_DEFAULT_SP_DEADBAND_PERCENT 7
|
||||
#define PRESS_CYLINDER_DEFAULT_SP_DEADBAND_RAW 100
|
||||
#define PRESS_CYLINDER_DEFAULT_SP_DEADBAND_PERCENT 5
|
||||
#define PRESS_CYLINDER_DEFAULT_SP_DEADBAND_RAW 50
|
||||
#define PRESS_CYLINDER_DEFAULT_MIN_LOAD 10
|
||||
#define PRESS_CYLINDER_MAX_LOAD_CLAMP 3500
|
||||
|
||||
#define PRESS_MAX_PRESS_TIME 35000
|
||||
#define PRESS_AUTO_TIMEOUT 35000
|
||||
#define PRESS_CYLINDER_AUTO_MODE_HOLD_DURATION_MS 1500
|
||||
#define BALANCE_INTERVAL 3000
|
||||
#define BALANCE_MIN_PV_DIFF 50
|
||||
#define BALANCE_INTERVAL 500
|
||||
#define BALANCE_MIN_PV_DIFF 200
|
||||
#define BALANCE_MAX_PV_DIFF 350
|
||||
#define BALANCE_MIN_STEP_DIFF 300
|
||||
#define BALANCE_MIN_STEP_DIFF 200
|
||||
|
||||
#define PRESSCYLINDER_INTERVAL 20
|
||||
#define PRESS_CYLINDER_MAX_STALL_ACTIVATIONS 4
|
||||
#define PRESS_CYLINDER_MB_COUNT 10
|
||||
#define PRESS_CYLINDER_MB_COUNT 12
|
||||
#define PRESS_CYLINDER_MAX_PAIRS 2
|
||||
#define PRESS_CYLINDER_MAX_LOADCELLS PRESS_CYLINDER_MAX_PAIRS
|
||||
#define PRESS_CYLINDER_MAX_SOLENOIDS PRESS_CYLINDER_MAX_PAIRS
|
||||
@ -124,7 +125,7 @@ public:
|
||||
E_PC_OP_CHECK_MULTI_TIMEOUT = 1 << 5,
|
||||
E_PC_OP_ENABLE_DOUBLE_CLICK = 1 << 6,
|
||||
// E_PC_OP_ALL = E_PC_OP_CHECK_MAX_TIME | E_PC_OP_CHECK_STALLED | E_PC_OP_CHECK_BALANCE | E_PC_OP_CHECK_LOADCELL,
|
||||
E_PC_OP_ALL = E_PC_OP_CHECK_MAX_TIME | E_PC_OP_ENABLE_DOUBLE_CLICK
|
||||
E_PC_OP_ALL = E_PC_OP_CHECK_MAX_TIME | E_PC_OP_ENABLE_DOUBLE_CLICK | E_PC_OP_CHECK_LOADCELL
|
||||
};
|
||||
enum E_PC_OutputMode
|
||||
{
|
||||
@ -150,6 +151,7 @@ private:
|
||||
Loadcell *_loadcells[PRESS_CYLINDER_MAX_PAIRS];
|
||||
Solenoid *_solenoids[PRESS_CYLINDER_MAX_PAIRS];
|
||||
Joystick *_joystick;
|
||||
PushButton *_pushButton;
|
||||
unsigned long _last_balance_time;
|
||||
unsigned long _auto_interlocked_start_time;
|
||||
uint32_t _pvs_raw_previous[PRESS_CYLINDER_MAX_PAIRS];
|
||||
@ -169,10 +171,11 @@ public:
|
||||
PressCylinderValue m_error_code;
|
||||
PressCylinderValue m_cflags;
|
||||
PressCylinderValue m_outputMode;
|
||||
PressCylinderValue m_cmd;
|
||||
NetworkValue<bool> m_interlocked;
|
||||
PressCylinderSettings _settings;
|
||||
|
||||
PressCylinder(Component *owner, short id, short mbAddress, Joystick *joystick);
|
||||
PressCylinder(Component *owner, short id, short mbAddress, Joystick *joystick, PushButton *pushButton);
|
||||
|
||||
short addLoadcell(Loadcell *loadcell);
|
||||
short addSolenoid(Solenoid *solenoid);
|
||||
@ -186,6 +189,7 @@ public:
|
||||
static constexpr unsigned long JOYSTICK_DOUBLE_CLICK_MS = 1200;
|
||||
static constexpr E_Mode DEFAULT_AUTO_MODE_NORMAL = MODE_AUTO;
|
||||
static constexpr E_Mode DEFAULT_AUTO_MODE_INTERLOCKED = MODE_AUTO_MULTI_BALANCED;
|
||||
static constexpr uint8_t MANUAL_VIRTUAL_TARGET_PERCENT = 70;
|
||||
|
||||
short mb_tcp_write(MB_Registers *reg, short networkValue) override;
|
||||
short mb_tcp_read(MB_Registers *reg) override;
|
||||
@ -203,8 +207,10 @@ public:
|
||||
private:
|
||||
void _applyDefaultSettings();
|
||||
bool pvsOk();
|
||||
short loopSingle();
|
||||
short loopMulti();
|
||||
const char *getErrorString(int error_code);
|
||||
void onError(int error_code);
|
||||
short loopSingle(E_Mode mode, uint32_t targetVal);
|
||||
short loopMulti(E_Mode mode, uint32_t targetVal);
|
||||
};
|
||||
|
||||
#endif // ENABLE_PRESS_CYLINDER
|
||||
|
||||
@ -1558,20 +1558,7 @@ void RESTServer::handleWebSocketMessage(AsyncWebSocketClient *client, void *arg,
|
||||
|
||||
if (len > 0 && Log.getLevel() >= LOG_LEVEL_VERBOSE)
|
||||
{
|
||||
String data_str;
|
||||
// Limit hex dump to a reasonable size to avoid flooding logs
|
||||
size_t dump_len = len > 128 ? 128 : len;
|
||||
for (size_t i = 0; i < dump_len; i++)
|
||||
{
|
||||
if (data[i] < 16)
|
||||
data_str += "0";
|
||||
data_str += String(data[i], HEX);
|
||||
data_str += " ";
|
||||
}
|
||||
if (len > 128)
|
||||
{
|
||||
data_str += "...";
|
||||
}
|
||||
// Log.verbose("WS Message len: %d", len); - unsafe string build removed
|
||||
}
|
||||
|
||||
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT)
|
||||
@ -1945,8 +1932,8 @@ void RESTServer::handleWebSocketMessage(AsyncWebSocketClient *client, void *arg,
|
||||
#ifdef ENABLE_WEBSOCKET_PB
|
||||
else if (command == "get_registers_pb")
|
||||
{
|
||||
int currentPage = requestDoc.containsKey("page") ? requestDoc["page"].as<int>() : 0;
|
||||
int pageSize = requestDoc.containsKey("pageSize") ? requestDoc["pageSize"].as<int>() : DEFAULT_WEBSOCKET_PAGE_SIZE;
|
||||
int currentPage = !requestDoc["page"].isNull() ? requestDoc["page"].as<int>() : 0;
|
||||
int pageSize = !requestDoc["pageSize"].isNull() ? requestDoc["pageSize"].as<int>() : DEFAULT_WEBSOCKET_PAGE_SIZE;
|
||||
if (pageSize <= 0)
|
||||
pageSize = DEFAULT_WEBSOCKET_PAGE_SIZE;
|
||||
if (pageSize > MAX_PAGE_SIZE)
|
||||
@ -2240,37 +2227,37 @@ void RESTServer::broadcast(BroadcastMessageType type, const JsonDocument &data)
|
||||
case BROADCAST_USER_MESSAGE:
|
||||
typeStr = "user_message";
|
||||
break;
|
||||
case BROADCAST_ERROR_MESSAGE:
|
||||
typeStr = "error_message";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
doc["type"] = typeStr;
|
||||
doc["data"] = data;
|
||||
|
||||
if (type == BROADCAST_USER_MESSAGE)
|
||||
{
|
||||
doc["timestamp"] = millis();
|
||||
}
|
||||
|
||||
String output;
|
||||
serializeJson(doc, output);
|
||||
|
||||
if (type == BROADCAST_USER_MESSAGE)
|
||||
if (type == BROADCAST_USER_MESSAGE || type == BROADCAST_ERROR_MESSAGE)
|
||||
{
|
||||
doc["timestamp"] = millis();
|
||||
userMessageHistory.addMessage(output.c_str());
|
||||
}
|
||||
|
||||
queueWsMessage(0, output);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_WEBSOCKET
|
||||
void RESTServer::broadcastBinary(const uint8_t *data, size_t len)
|
||||
{
|
||||
if (!ws.availableForWriteAll())
|
||||
// Check rate limit if needed, or simply broadcast
|
||||
if (ws.count() > 0 && ws.availableForWriteAll())
|
||||
{
|
||||
return;
|
||||
ws.binaryAll((uint8_t *)data, len);
|
||||
}
|
||||
ws.binaryAll(data, len);
|
||||
}
|
||||
#endif
|
||||
|
||||
short RESTServer::onMessage(int id, E_CALLS verb, E_MessageFlags flags, void *user, Component *src)
|
||||
{
|
||||
@ -2284,6 +2271,75 @@ short RESTServer::onMessage(int id, E_CALLS verb, E_MessageFlags flags, void *us
|
||||
if (verb == E_CALLS::EC_USER && user != nullptr)
|
||||
{
|
||||
MB_UpdateData *info = static_cast<MB_UpdateData *>(user);
|
||||
|
||||
// --- Binary Protocol Implementation ---
|
||||
#ifdef USE_BINARY_UPDATES
|
||||
if (info->functionCode == E_FN_CODE::FN_READ_HOLD_REGISTER ||
|
||||
info->functionCode == E_FN_CODE::FN_READ_INPUT_REGISTER ||
|
||||
info->functionCode == E_FN_CODE::FN_WRITE_HOLD_REGISTER ||
|
||||
info->functionCode == E_FN_CODE::FN_WRITE_MULT_REGISTERS)
|
||||
{
|
||||
BinaryRegisterUpdate update;
|
||||
// update.type is initialized to 0x02
|
||||
update.slaveId = info->slaveId;
|
||||
update.fc = (uint8_t)info->functionCode;
|
||||
update.componentId = src ? src->id : 0;
|
||||
update.address = info->address;
|
||||
update.count = info->count;
|
||||
update.reserved = 0;
|
||||
|
||||
// Handle value extraction
|
||||
if (info->functionCode == E_FN_CODE::FN_WRITE_MULT_REGISTERS && info->userData != nullptr)
|
||||
{
|
||||
// For multi-write, we might need a different packet or just send the first one?
|
||||
// The current spec assumes single register update mostly.
|
||||
// Let's take the first value for now as the 'value' field.
|
||||
uint16_t *data = static_cast<uint16_t *>(info->userData);
|
||||
update.value = data[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
update.value = (uint16_t)info->value;
|
||||
}
|
||||
|
||||
this->broadcastBinary((uint8_t *)&update, sizeof(update));
|
||||
lastBroadcastTime = millis();
|
||||
return E_OK;
|
||||
}
|
||||
else if (info->functionCode == E_FN_CODE::FN_READ_COIL ||
|
||||
info->functionCode == E_FN_CODE::FN_READ_DISCR_INPUT ||
|
||||
info->functionCode == E_FN_CODE::FN_WRITE_COIL ||
|
||||
info->functionCode == E_FN_CODE::FN_WRITE_MULT_COILS)
|
||||
{
|
||||
BinaryCoilUpdate update;
|
||||
// update.type is initialized to 0x01
|
||||
update.slaveId = info->slaveId;
|
||||
update.fc = (uint8_t)info->functionCode;
|
||||
update.componentId = src ? src->id : 0;
|
||||
update.address = info->address;
|
||||
update.count = info->count;
|
||||
|
||||
if (info->functionCode == E_FN_CODE::FN_WRITE_MULT_COILS && info->userData != nullptr)
|
||||
{
|
||||
bool *data = static_cast<bool *>(info->userData);
|
||||
update.value = data[0] ? 1 : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
update.value = info->value ? 1 : 0;
|
||||
}
|
||||
|
||||
this->broadcastBinary((uint8_t *)&update, sizeof(update));
|
||||
lastBroadcastTime = millis();
|
||||
return E_OK;
|
||||
}
|
||||
#endif
|
||||
// --- End Binary Protocol ---
|
||||
|
||||
// --- JSON Fallback (Original Logic) ---
|
||||
// If USE_BINARY_UPDATES is undefined, OR if the message type wasn't handled above (though all types are covered)
|
||||
// we fall through to here. Note: If USE_BINARY_UPDATES is defined, we returned E_OK above.
|
||||
|
||||
JsonDocument doc;
|
||||
doc["slaveId"] = info->slaveId;
|
||||
doc["address"] = info->address;
|
||||
@ -2358,6 +2414,7 @@ uint8_t RESTServer::getConnectedClientsCount() const
|
||||
return ws.count();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_WEBSOCKET
|
||||
void RESTServer::queueWsMessage(uint32_t clientId, const String &message)
|
||||
{
|
||||
@ -2376,8 +2433,10 @@ void RESTServer::queueWsMessage(uint32_t clientId, const String &message)
|
||||
|
||||
void RESTServer::processWsQueue()
|
||||
{
|
||||
if (millis() - lastWsSendTime < WS_MIN_SEND_INTERVAL_MS)
|
||||
if (millis() - lastWsSendTime < 100)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WsMessage msg;
|
||||
if (wsTxQueue.peek(msg))
|
||||
|
||||
@ -33,7 +33,8 @@ typedef enum : uint8_t
|
||||
BROADCAST_LOG_ENTRY,
|
||||
BROADCAST_SYSTEM_STATUS,
|
||||
BROADCAST_USER_DEFINED,
|
||||
BROADCAST_USER_MESSAGE
|
||||
BROADCAST_USER_MESSAGE,
|
||||
BROADCAST_ERROR_MESSAGE
|
||||
} BroadcastMessageType;
|
||||
|
||||
struct WsMessage
|
||||
@ -42,6 +43,33 @@ struct WsMessage
|
||||
String message;
|
||||
};
|
||||
|
||||
// --- Binary Protocol Structs ---
|
||||
#pragma pack(push, 1)
|
||||
struct BinaryCoilUpdate
|
||||
{
|
||||
uint8_t type = 0x01; // BROADCAST_COIL_UPDATE
|
||||
uint8_t slaveId;
|
||||
uint8_t fc;
|
||||
uint8_t value; // 0 or 1
|
||||
uint16_t componentId;
|
||||
uint16_t address;
|
||||
uint16_t count;
|
||||
};
|
||||
|
||||
struct BinaryRegisterUpdate
|
||||
{
|
||||
uint8_t type = 0x02; // BROADCAST_REGISTER_UPDATE
|
||||
uint8_t slaveId;
|
||||
uint8_t fc;
|
||||
uint8_t reserved; // Padding
|
||||
uint16_t componentId;
|
||||
uint16_t address;
|
||||
uint16_t count;
|
||||
uint16_t value;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
// ----------------------------
|
||||
|
||||
class WsTxQueue
|
||||
{
|
||||
public:
|
||||
|
||||
@ -1355,7 +1355,7 @@ void ModbusRTU::onErrorReceived(Error error, uint32_t token)
|
||||
const char *errorString = modbusErrorToString(mbError);
|
||||
if (mbError != MB_Error::Timeout && MB_PRINT_ERRORS)
|
||||
{
|
||||
Log.errorln("Modbus Error: Token=%u, Error=%d (%s) Slave %d, address %d", token, (int)error, errorString, slaveIdxOperation, address);
|
||||
// Log.errorln("Modbus Error: Token=%u, Error=%d (%s) Slave %d, address %d", token, (int)error, errorString, slaveIdxOperation, address);
|
||||
}
|
||||
onErrorCallback(*operation, (int)error, errorString);
|
||||
CBI(operation->flags, OP_USED_BIT);
|
||||
|
||||
@ -126,6 +126,31 @@ void PlotBase::seek(uint32_t targetMs)
|
||||
_startTimeMs = ULONG_MAX - (targetMs - now - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively seek children
|
||||
for (int i = 0; i < MAX_PLOTS; ++i)
|
||||
{
|
||||
if (_plots[i] != nullptr)
|
||||
{
|
||||
_plots[i]->seek(targetMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlotBase::slipTime(uint32_t deltaMs)
|
||||
{
|
||||
if (_running && !_paused)
|
||||
{
|
||||
_startTimeMs += deltaMs;
|
||||
}
|
||||
// Recursively slip children
|
||||
for (int i = 0; i < MAX_PLOTS; ++i)
|
||||
{
|
||||
if (_plots[i] != nullptr)
|
||||
{
|
||||
_plots[i]->slipTime(deltaMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t PlotBase::getElapsedMs() const
|
||||
@ -216,6 +241,10 @@ PlotStatus PlotBase::getCurrentStatus() const
|
||||
{
|
||||
return PlotStatus::PAUSED;
|
||||
}
|
||||
if (_waiting && _running)
|
||||
{
|
||||
return PlotStatus::WAITING;
|
||||
}
|
||||
if (_running)
|
||||
{
|
||||
return PlotStatus::RUNNING;
|
||||
@ -308,6 +337,7 @@ short PlotBase::startPlots()
|
||||
_plots[i]->start();
|
||||
}
|
||||
}
|
||||
return E_OK;
|
||||
}
|
||||
void PlotBase::onStop()
|
||||
{
|
||||
@ -755,7 +785,7 @@ short PlotBase::setup()
|
||||
|
||||
const char *group = getModbusGroupName();
|
||||
|
||||
m_status.initModbus(_baseAddress + (ushort)PlotBaseRegisterOffset::STATUS, 1, this->id, this->slaveId, E_FN_CODE::FN_READ_HOLD_REGISTER, "Status(0:IDLE, 1:INITIALIZING, 2:RUNNING, 3:PAUSED, 4:STOPPED, 5:FINISHED)", group);
|
||||
m_status.initModbus(_baseAddress + (ushort)PlotBaseRegisterOffset::STATUS, 1, this->id, this->slaveId, E_FN_CODE::FN_READ_HOLD_REGISTER, "Status(0:IDLE, 1:INITIALIZING, 2:RUNNING, 3:PAUSED, 4:STOPPED, 5:FINISHED, 6:WAITING)", group);
|
||||
m_status.initNotify(PlotStatus::IDLE, PlotStatus::IDLE, NetworkValue_ThresholdMode::DIFFERENCE);
|
||||
registerBlock(m_status.getRegisterInfo());
|
||||
|
||||
|
||||
@ -28,7 +28,8 @@ enum class PlotStatus : uint8_t
|
||||
RUNNING, // Actively running
|
||||
PAUSED, // Started, but currently paused
|
||||
STOPPED, // Explicitly stopped by user/logic
|
||||
FINISHED // Reached or exceeded duration
|
||||
FINISHED, // Reached or exceeded duration
|
||||
WAITING, // Waiting for active delays to finish
|
||||
};
|
||||
|
||||
enum class PlotType : uint8_t
|
||||
@ -82,7 +83,7 @@ public:
|
||||
PlotBase(Component *owner, ushort componentId, ushort modbusAddress, PlotType plotType = PlotType::Base) : NetworkComponent<PLOT_BASE_BLOCK_COUNT>(modbusAddress, "PlotBase", componentId, Component::COMPONENT_DEFAULT, owner),
|
||||
_durationMs(0), _startTimeMs(0), _elapsedMsAtPause(0), _running(false), _paused(false), _explicitlyStopped(false), _userData(nullptr),
|
||||
_parent(nullptr), _eventsDelegate(nullptr), _plotType(plotType), _numControlPoints(0), max(0),
|
||||
m_status(this, componentId, "Status(0:IDLE, 1:INITIALIZING, 2:RUNNING, 3:PAUSED, 4:STOPPED, 5:FINISHED)", static_cast<uint8_t>(E_NetworkValueFeatureFlags::E_NVFF_ALL)),
|
||||
m_status(this, componentId, "Status(0:IDLE, 1:INITIALIZING, 2:RUNNING, 3:PAUSED, 4:STOPPED, 5:FINISHED, 6:WAITING)", static_cast<uint8_t>(E_NetworkValueFeatureFlags::E_NVFF_ALL)),
|
||||
m_currentValue(this, componentId, "CurrentValue", static_cast<uint8_t>(E_NetworkValueFeatureFlags::E_NVFF_ALL)),
|
||||
m_duration(this, componentId, "Duration", static_cast<uint8_t>(E_NetworkValueFeatureFlags::E_NVFF_ALL)),
|
||||
m_elapsed(this, componentId, "Elapsed", static_cast<uint8_t>(E_NetworkValueFeatureFlags::E_NVFF_ALL)),
|
||||
@ -151,6 +152,13 @@ public:
|
||||
*/
|
||||
virtual void seek(uint32_t targetMs);
|
||||
|
||||
/**
|
||||
* @brief Slips the start time by the specified delta to compensate for lag.
|
||||
* Recursively slips all child plots.
|
||||
* @param deltaMs The amount of time to slip in milliseconds.
|
||||
*/
|
||||
virtual void slipTime(uint32_t deltaMs);
|
||||
|
||||
void setEventsDelegate(IPlotEvents *delegate) { _eventsDelegate = delegate; }
|
||||
|
||||
// --- Sub-Plot Management ---
|
||||
@ -170,6 +178,17 @@ public:
|
||||
*/
|
||||
bool isPaused() const { return _paused; }
|
||||
|
||||
/**
|
||||
* @brief Checks if the plot is currently waiting (lagging).
|
||||
* @return true if waiting, false otherwise.
|
||||
*/
|
||||
bool isWaiting() const { return _waiting; }
|
||||
|
||||
/**
|
||||
* @brief Sets the waiting status of the plot.
|
||||
* @param waiting True if waiting, false otherwise.
|
||||
*/
|
||||
void setWaiting(bool waiting) { _waiting = waiting; }
|
||||
/**
|
||||
* @brief Gets the total duration of the plot.
|
||||
* @return Plot duration in milliseconds.
|
||||
@ -311,6 +330,7 @@ protected:
|
||||
uint32_t _startTimeMs;
|
||||
bool _running; // True if started and not stopped
|
||||
bool _paused; // True if pause() called while running
|
||||
bool _waiting = false; // True if waiting for lag compensation
|
||||
bool _explicitlyStopped; // True if stop() was called and not superseded by start()
|
||||
void *_userData; // Pointer for arbitrary user data
|
||||
|
||||
|
||||
@ -40,18 +40,18 @@ short SignalPlot::setup()
|
||||
short SignalPlot::loop()
|
||||
{
|
||||
PlotBase::loop();
|
||||
|
||||
|
||||
// Update all relevant NetworkValues from the base class
|
||||
PlotStatus currentStatus = getCurrentStatus();
|
||||
m_status.update(currentStatus);
|
||||
m_duration.update(getDuration() / 1000); // Assuming duration is in seconds for modbus
|
||||
m_duration.update(getDuration() / 1000); // Assuming duration is in seconds for modbus
|
||||
m_remaining.update(getRemainingTime() / 1000); // Same for remaining
|
||||
|
||||
|
||||
uint32_t currentElapsedMs = getElapsedMs();
|
||||
m_elapsed.update(currentElapsedMs / 1000); // Same for elapsed
|
||||
|
||||
// Since SignalPlot doesn't have a value curve, we'll just update with 0
|
||||
m_currentValue.update(0);
|
||||
m_currentValue.update(0);
|
||||
|
||||
if (!isRunning() || !enabled())
|
||||
{
|
||||
@ -70,7 +70,8 @@ short SignalPlot::loop()
|
||||
return E_OK;
|
||||
}
|
||||
|
||||
bool SignalPlot::load(const JsonObject& config) {
|
||||
bool SignalPlot::load(const JsonObject &config)
|
||||
{
|
||||
PlotBase::load(config);
|
||||
_numControlPoints = 0; // Reset count before loading new points
|
||||
for (int i = 0; i < MAX_SIGNAL_POINTS; ++i)
|
||||
@ -242,12 +243,12 @@ void SignalPlot::executeControlPointAction(uint8_t cpIndex)
|
||||
return;
|
||||
}
|
||||
S_SignalControlPoint &cp = _controlPoints[cpIndex];
|
||||
|
||||
|
||||
if (cp.state == E_SIGNAL_STATE::STATE_ON)
|
||||
{
|
||||
return; // Already executed, skip
|
||||
return; // Already executed, skip
|
||||
}
|
||||
|
||||
|
||||
switch (cp.type)
|
||||
{
|
||||
case E_SIGNAL_TYPE::MB_WRITE_COIL:
|
||||
@ -276,7 +277,7 @@ void SignalPlot::executeControlPointAction(uint8_t cpIndex)
|
||||
if (gpioMode == E_GpioWriteMode::DIGITAL)
|
||||
{
|
||||
digitalWrite(pin, (value != 0) ? HIGH : LOW);
|
||||
cp.state = E_SIGNAL_STATE::STATE_ON;
|
||||
cp.state = E_SIGNAL_STATE::STATE_ON;
|
||||
}
|
||||
else if (gpioMode == E_GpioWriteMode::ANALOG_PWM)
|
||||
{
|
||||
@ -302,7 +303,7 @@ void SignalPlot::executeControlPointAction(uint8_t cpIndex)
|
||||
}
|
||||
|
||||
String cmdStr = String((char *)cp.user);
|
||||
|
||||
|
||||
Bridge *bridge = static_cast<Bridge *>(owner->byId(COMPONENT_KEY_MB_BRIDGE));
|
||||
if (!bridge)
|
||||
{
|
||||
@ -320,7 +321,7 @@ void SignalPlot::executeControlPointAction(uint8_t cpIndex)
|
||||
}
|
||||
case E_SIGNAL_TYPE::DISPLAY_MESSAGE:
|
||||
{
|
||||
#ifdef ENABLE_WEBSOCKET
|
||||
#ifdef ENABLE_WEBSOCKET
|
||||
String message = cp.description;
|
||||
if (message.isEmpty())
|
||||
{
|
||||
@ -336,7 +337,7 @@ void SignalPlot::executeControlPointAction(uint8_t cpIndex)
|
||||
phApp->broadcast(BROADCAST_USER_MESSAGE, doc);
|
||||
}
|
||||
cp.state = E_SIGNAL_STATE::STATE_ON;
|
||||
#endif
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case E_SIGNAL_TYPE::PAUSE:
|
||||
@ -369,7 +370,8 @@ void SignalPlot::executeControlPointAction(uint8_t cpIndex)
|
||||
case E_SIGNAL_TYPE::STOP_PIDS:
|
||||
{
|
||||
PlotBase *parent = getParent();
|
||||
if(parent){
|
||||
if (parent)
|
||||
{
|
||||
PHApp *phApp = static_cast<PHApp *>(owner);
|
||||
if (phApp)
|
||||
{
|
||||
@ -383,7 +385,8 @@ void SignalPlot::executeControlPointAction(uint8_t cpIndex)
|
||||
case E_SIGNAL_TYPE::START_PIDS:
|
||||
{
|
||||
PlotBase *parent = getParent();
|
||||
if(parent){
|
||||
if (parent)
|
||||
{
|
||||
PHApp *phApp = static_cast<PHApp *>(owner);
|
||||
if (phApp)
|
||||
{
|
||||
@ -393,7 +396,7 @@ void SignalPlot::executeControlPointAction(uint8_t cpIndex)
|
||||
cp.state = E_SIGNAL_STATE::STATE_ON;
|
||||
break;
|
||||
}
|
||||
#ifdef ENABLE_FEEDBACK_BUZZER
|
||||
#ifdef ENABLE_FEEDBACK_BUZZER
|
||||
case E_SIGNAL_TYPE::BUZZER_OFF:
|
||||
case E_SIGNAL_TYPE::BUZZER_SOLID:
|
||||
case E_SIGNAL_TYPE::BUZZER_SLOW_BLINK:
|
||||
@ -431,11 +434,11 @@ void SignalPlot::executeControlPointAction(uint8_t cpIndex)
|
||||
}
|
||||
else
|
||||
{
|
||||
cp.state = E_SIGNAL_STATE::STATE_ERROR;
|
||||
}
|
||||
break;
|
||||
cp.state = E_SIGNAL_STATE::STATE_ERROR;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case E_SIGNAL_TYPE::IFTTT_WEBHOOK:
|
||||
{
|
||||
#ifdef ENABLE_IFTTT
|
||||
|
||||
@ -79,6 +79,7 @@ short TemperatureProfile::loop()
|
||||
|
||||
auto handleRunning = [&]()
|
||||
{
|
||||
// --- Lag Compensation ---
|
||||
if (now - _lastLoopExecutionMs < TEMPERATURE_PROFILE_LOOP_INTERVAL_MS)
|
||||
{
|
||||
return;
|
||||
@ -91,6 +92,9 @@ short TemperatureProfile::loop()
|
||||
}
|
||||
};
|
||||
|
||||
// Check if initializing to reset cache if needed, or do it on start.
|
||||
// Ideally we clear _omron on stop/start to handle config changes, but it's unlikely to change at runtime.
|
||||
|
||||
// --- State Machine ---
|
||||
switch (currentStatus)
|
||||
{
|
||||
|
||||
@ -19,6 +19,7 @@ class ModbusTCP;
|
||||
#define TEMPERATURE_PROFILE_LOOP_INTERVAL_MS 1000
|
||||
#define TEMP_PROFILE_ID_BASE 2000
|
||||
#define TEMP_REGISTER_NAME_PREFIX "TProf"
|
||||
#define TEMP_MAX_LAG 1000 * 60 * 5 // 5 minutes
|
||||
|
||||
const uint16_t TEMP_PROFILE_REGISTER_COUNT = static_cast<uint16_t>(PlotBaseRegisterOffset::_COUNT);
|
||||
|
||||
@ -39,6 +40,10 @@ enum class TemperatureProfileCommand : uint16_t
|
||||
class TemperatureProfile : public PlotBase
|
||||
{
|
||||
public:
|
||||
uint32_t getLagDuration() const { return _lagDurationMs; }
|
||||
void setLagDuration(uint32_t duration) { _lagDurationMs = duration; }
|
||||
uint32_t getLastLogMs() const { return _lastLogMs; }
|
||||
void setLastLogMs(uint32_t timestamp) { _lastLogMs = timestamp; }
|
||||
TemperatureProfile(Component *owner, short slot, ushort componentId, ushort modbusBaseAddress);
|
||||
virtual ~TemperatureProfile() = default;
|
||||
|
||||
@ -134,6 +139,12 @@ private:
|
||||
short _pressureProfileSlotId;
|
||||
|
||||
PlotStatus _previousStatus;
|
||||
|
||||
// Cached pointer to OmronE5 device for lag compensation
|
||||
Component *_omron = nullptr;
|
||||
|
||||
// Track how long we have been slipping due to lag
|
||||
uint32_t _lagDurationMs = 0;
|
||||
};
|
||||
|
||||
#endif // TEMPERATURE_PROFILE_H
|
||||
Loading…
Reference in New Issue
Block a user