Add diagnostic bit support to Gateway Modbus

- Introduced new enum value `kShortDiagnosticBit` to `GatewayModbusGeneratedKind`.
- Enhanced `GatewayModbusPoint` and `GatewayModbusPointBinding` structures to include diagnostic snapshot, boolean key, and device type.
- Added new diagnostic bit specifications and updated the corresponding arrays for generated discrete inputs and holding registers.
- Implemented `addGeneratedDiagnosticPoint` function to handle the creation of diagnostic points.
- Updated `rebuildMap` method to include generated diagnostic points during the map rebuilding process.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Tony
2026-05-04 02:26:09 +08:00
parent 694217eb2c
commit 7424b43bdd
13 changed files with 1133 additions and 40 deletions
@@ -18,6 +18,8 @@ namespace {
constexpr const char* kTag = "gateway_bacnet";
constexpr TickType_t kPollDelayTicks = pdMS_TO_TICKS(10);
constexpr TickType_t kValueRefreshTicks = pdMS_TO_TICKS(2000);
constexpr uint32_t kReliabilityCommunicationFailure = 12;
class LockGuard {
public:
@@ -125,6 +127,59 @@ DaliValue StackWriteValueToDali(const gateway_bacnet_write_value_t& value) {
}
}
bool DaliValueToStackPresentValue(BridgeObjectType object_type, const DaliValue& value,
gateway_bacnet_write_value_t* out) {
if (out == nullptr) {
return false;
}
switch (ToBacnetKind(object_type)) {
case GW_BACNET_OBJECT_ANALOG_INPUT:
case GW_BACNET_OBJECT_ANALOG_VALUE:
case GW_BACNET_OBJECT_ANALOG_OUTPUT: {
const auto parsed = value.asDouble();
if (!parsed.has_value()) {
return false;
}
*out = gateway_bacnet_write_value_t{GW_BACNET_WRITE_VALUE_REAL,
parsed.value(),
false,
0};
return true;
}
case GW_BACNET_OBJECT_BINARY_INPUT:
case GW_BACNET_OBJECT_BINARY_VALUE:
case GW_BACNET_OBJECT_BINARY_OUTPUT: {
const auto parsed = value.asBool();
if (!parsed.has_value()) {
return false;
}
*out = gateway_bacnet_write_value_t{GW_BACNET_WRITE_VALUE_BOOLEAN,
0.0,
parsed.value(),
0};
return true;
}
case GW_BACNET_OBJECT_MULTI_STATE_INPUT:
case GW_BACNET_OBJECT_MULTI_STATE_VALUE:
case GW_BACNET_OBJECT_MULTI_STATE_OUTPUT: {
const auto parsed = value.asInt();
if (!parsed.has_value()) {
return false;
}
const uint32_t unsigned_value = parsed.value() <= 0
? 1
: static_cast<uint32_t>(parsed.value());
*out = gateway_bacnet_write_value_t{GW_BACNET_WRITE_VALUE_UNSIGNED,
0.0,
false,
unsigned_value};
return true;
}
default:
return false;
}
}
void HandleStackWrite(gateway_bacnet_object_kind_t object_kind, uint32_t object_instance,
const gateway_bacnet_write_value_t* value, void*) {
if (g_server == nullptr || value == nullptr) {
@@ -140,6 +195,7 @@ struct GatewayBacnetServer::ChannelRegistration {
GatewayBacnetServerConfig config;
std::vector<GatewayBacnetObjectBinding> bindings;
GatewayBacnetWriteCallback write_callback;
GatewayBacnetReadCallback read_callback;
};
struct GatewayBacnetServer::RuntimeBinding {
@@ -150,7 +206,9 @@ struct GatewayBacnetServer::RuntimeBinding {
std::string property{"presentValue"};
bool out_of_service{false};
uint32_t reliability{0};
bool readable{false};
GatewayBacnetWriteCallback write_callback;
GatewayBacnetReadCallback read_callback;
};
GatewayBacnetServer& GatewayBacnetServer::instance() {
@@ -187,7 +245,8 @@ GatewayBacnetServerStatus GatewayBacnetServer::status() const {
esp_err_t GatewayBacnetServer::registerChannel(
uint8_t gateway_id, const GatewayBacnetServerConfig& config,
std::vector<GatewayBacnetObjectBinding> bindings,
GatewayBacnetWriteCallback write_callback) {
GatewayBacnetWriteCallback write_callback,
GatewayBacnetReadCallback read_callback) {
if (write_callback == nullptr) {
return ESP_ERR_INVALID_ARG;
}
@@ -210,7 +269,7 @@ esp_err_t GatewayBacnetServer::registerChannel(
return ESP_ERR_NOT_FOUND;
}
ChannelRegistration registration{gateway_id, config, std::move(bindings),
std::move(write_callback)};
std::move(write_callback), std::move(read_callback)};
if (channel == channels_.end()) {
channels_.push_back(std::move(registration));
} else {
@@ -297,7 +356,9 @@ esp_err_t GatewayBacnetServer::rebuildObjectsLocked() {
: binding.property,
binding.out_of_service,
binding.reliability,
channel.write_callback});
binding.readable,
channel.write_callback,
channel.read_callback});
}
}
@@ -338,12 +399,47 @@ bool GatewayBacnetServer::handleWrite(BridgeObjectType object_type, uint32_t obj
return ok;
}
void GatewayBacnetServer::refreshPresentValues() {
std::vector<RuntimeBinding> bindings;
{
LockGuard guard(lock_);
bindings = runtime_bindings_;
}
for (const auto& binding : bindings) {
const auto object_kind = ToBacnetKind(binding.object_type);
if (object_kind == GW_BACNET_OBJECT_UNKNOWN) {
continue;
}
if (!binding.readable || binding.read_callback == nullptr) {
LockGuard guard(lock_);
gateway_bacnet_stack_set_object_state(object_kind, binding.object_instance,
binding.out_of_service, binding.reliability);
continue;
}
gateway_bacnet_write_value_t stack_value = {};
const auto value = binding.read_callback(binding.object_type, binding.object_instance,
binding.property);
const bool converted = value.has_value() &&
DaliValueToStackPresentValue(binding.object_type, value.value(),
&stack_value);
LockGuard guard(lock_);
const bool ok = converted && gateway_bacnet_stack_set_present_value(
object_kind, binding.object_instance, &stack_value);
gateway_bacnet_stack_set_object_state(
object_kind, binding.object_instance, binding.out_of_service || !ok,
ok ? binding.reliability : kReliabilityCommunicationFailure);
}
}
void GatewayBacnetServer::TaskEntry(void* arg) {
static_cast<GatewayBacnetServer*>(arg)->taskLoop();
}
void GatewayBacnetServer::taskLoop() {
TickType_t last_timer = xTaskGetTickCount();
TickType_t last_refresh = last_timer;
while (true) {
const TickType_t now = xTaskGetTickCount();
@@ -353,9 +449,17 @@ void GatewayBacnetServer::taskLoop() {
elapsed_ms = static_cast<uint16_t>(elapsed * portTICK_PERIOD_MS);
last_timer = now;
}
bool refresh_due = false;
{
LockGuard guard(lock_);
gateway_bacnet_stack_poll(elapsed_ms);
if ((now - last_refresh) >= kValueRefreshTicks) {
refresh_due = true;
}
}
if (refresh_due) {
refreshPresentValues();
last_refresh = now;
}
vTaskDelay(kPollDelayTicks);
}