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:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user