feat(gateway_network): integrate GatewayBridgeService and add bridge handling

- Updated CMakeLists.txt to require gateway_bridge component.
- Modified GatewayNetworkService to include a pointer to GatewayBridgeService.
- Added new HTTP handlers for bridge GET and POST requests.
- Implemented query utility functions for handling request parameters.
- Enhanced response handling for bridge actions with JSON responses.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Tony
2026-05-01 03:54:02 +08:00
parent 2c1aa28d4f
commit d16c289626
19 changed files with 3186 additions and 10 deletions
+128
View File
@@ -0,0 +1,128 @@
if(NOT CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED)
idf_component_register(
INCLUDE_DIRS "include"
REQUIRES dali_cpp
)
return()
endif()
set(BACNET_STACK_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../../bacnet-stack")
set(BACNET_SRC_ROOT "${BACNET_STACK_ROOT}/src")
set(BACNET_ESP32_PORT "${BACNET_STACK_ROOT}/ports/esp32/src")
idf_build_get_property(IDF_PATH IDF_PATH)
set(BACNET_CORE_SRCS
"${BACNET_SRC_ROOT}/bacnet/abort.c"
"${BACNET_SRC_ROOT}/bacnet/bacaddr.c"
"${BACNET_SRC_ROOT}/bacnet/bacapp.c"
"${BACNET_SRC_ROOT}/bacnet/bacdcode.c"
"${BACNET_SRC_ROOT}/bacnet/bacdest.c"
"${BACNET_SRC_ROOT}/bacnet/bacdevobjpropref.c"
"${BACNET_SRC_ROOT}/bacnet/bacerror.c"
"${BACNET_SRC_ROOT}/bacnet/bacint.c"
"${BACNET_SRC_ROOT}/bacnet/bacprop.c"
"${BACNET_SRC_ROOT}/bacnet/bacpropstates.c"
"${BACNET_SRC_ROOT}/bacnet/bacreal.c"
"${BACNET_SRC_ROOT}/bacnet/bacstr.c"
"${BACNET_SRC_ROOT}/bacnet/bactext.c"
"${BACNET_SRC_ROOT}/bacnet/bactimevalue.c"
"${BACNET_SRC_ROOT}/bacnet/calendar_entry.c"
"${BACNET_SRC_ROOT}/bacnet/cov.c"
"${BACNET_SRC_ROOT}/bacnet/datetime.c"
"${BACNET_SRC_ROOT}/bacnet/dcc.c"
"${BACNET_SRC_ROOT}/bacnet/iam.c"
"${BACNET_SRC_ROOT}/bacnet/ihave.c"
"${BACNET_SRC_ROOT}/bacnet/list_element.c"
"${BACNET_SRC_ROOT}/bacnet/memcopy.c"
"${BACNET_SRC_ROOT}/bacnet/npdu.c"
"${BACNET_SRC_ROOT}/bacnet/proplist.c"
"${BACNET_SRC_ROOT}/bacnet/reject.c"
"${BACNET_SRC_ROOT}/bacnet/rp.c"
"${BACNET_SRC_ROOT}/bacnet/rpm.c"
"${BACNET_SRC_ROOT}/bacnet/timestamp.c"
"${BACNET_SRC_ROOT}/bacnet/whohas.c"
"${BACNET_SRC_ROOT}/bacnet/whois.c"
"${BACNET_SRC_ROOT}/bacnet/wp.c"
)
set(BACNET_BASIC_SRCS
"${BACNET_SRC_ROOT}/bacnet/basic/binding/address.c"
"${BACNET_SRC_ROOT}/bacnet/basic/npdu/h_npdu.c"
"${BACNET_SRC_ROOT}/bacnet/basic/npdu/s_router.c"
"${BACNET_SRC_ROOT}/bacnet/basic/object/ao.c"
"${BACNET_SRC_ROOT}/bacnet/basic/object/av.c"
"${BACNET_SRC_ROOT}/bacnet/basic/object/bo.c"
"${BACNET_SRC_ROOT}/bacnet/basic/object/bv.c"
"${BACNET_SRC_ROOT}/bacnet/basic/object/device.c"
"${BACNET_SRC_ROOT}/bacnet/basic/object/msv.c"
"${BACNET_SRC_ROOT}/bacnet/basic/service/h_apdu.c"
"${BACNET_SRC_ROOT}/bacnet/basic/service/h_cov.c"
"${BACNET_SRC_ROOT}/bacnet/basic/service/h_dcc.c"
"${BACNET_SRC_ROOT}/bacnet/basic/service/h_noserv.c"
"${BACNET_SRC_ROOT}/bacnet/basic/service/h_rp.c"
"${BACNET_SRC_ROOT}/bacnet/basic/service/h_rpm.c"
"${BACNET_SRC_ROOT}/bacnet/basic/service/h_whohas.c"
"${BACNET_SRC_ROOT}/bacnet/basic/service/h_whois.c"
"${BACNET_SRC_ROOT}/bacnet/basic/service/h_wp.c"
"${BACNET_SRC_ROOT}/bacnet/basic/service/s_abort.c"
"${BACNET_SRC_ROOT}/bacnet/basic/service/s_error.c"
"${BACNET_SRC_ROOT}/bacnet/basic/service/s_iam.c"
"${BACNET_SRC_ROOT}/bacnet/basic/service/s_ihave.c"
"${BACNET_SRC_ROOT}/bacnet/basic/sys/bigend.c"
"${BACNET_SRC_ROOT}/bacnet/basic/sys/days.c"
"${BACNET_SRC_ROOT}/bacnet/basic/sys/datetime_mstimer.c"
"${BACNET_SRC_ROOT}/bacnet/basic/sys/debug.c"
"${BACNET_SRC_ROOT}/bacnet/basic/sys/dst.c"
"${BACNET_SRC_ROOT}/bacnet/basic/sys/keylist.c"
"${BACNET_SRC_ROOT}/bacnet/basic/sys/linear.c"
"${BACNET_SRC_ROOT}/bacnet/basic/sys/mstimer.c"
"${BACNET_SRC_ROOT}/bacnet/basic/tsm/tsm.c"
)
set(BACNET_PORT_SRCS
"${BACNET_ESP32_PORT}/bip.c"
"${BACNET_ESP32_PORT}/mstimer_init.c"
"${BACNET_ESP32_PORT}/bip_init.c"
"${BACNET_ESP32_PORT}/bvlc.c"
)
idf_component_register(
SRCS
"src/gateway_bacnet.cpp"
"src/gateway_bacnet_stack_port.c"
"src/bip_socket_lwip.cpp"
${BACNET_CORE_SRCS}
${BACNET_BASIC_SRCS}
${BACNET_PORT_SRCS}
INCLUDE_DIRS
"include"
"${BACNET_ESP32_PORT}"
"${BACNET_SRC_ROOT}"
PRIV_INCLUDE_DIRS
"${IDF_PATH}/components/esp_netif/include"
REQUIRES dali_cpp esp_netif freertos
PRIV_REQUIRES log lwip
)
target_compile_definitions(${COMPONENT_LIB} PRIVATE
BACDL_BIP=1
BACAPP_MINIMAL=1
BACNET_GATEWAY_EXTERNAL_OBJECT_TABLE=1
BACNET_PROPERTY_LISTS=1
BACNET_PROTOCOL_REVISION=16
BACNET_STACK_DEPRECATED_DISABLE=1
BBMD_ENABLED=0
BBMD_CLIENT_ENABLED=0
MAX_ADDRESS_CACHE=8
MAX_APDU=480
MAX_TSM_TRANSACTIONS=8
PRINT_ENABLED=0
)
target_compile_options(${COMPONENT_LIB} PRIVATE
$<$<COMPILE_LANGUAGE:C>:-Wno-missing-field-initializers>
$<$<COMPILE_LANGUAGE:C>:-Wno-old-style-declaration>
$<$<COMPILE_LANGUAGE:C>:-Wno-unused-function>
)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
@@ -0,0 +1,83 @@
#pragma once
#include "bridge_model.hpp"
#include "model_value.hpp"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <cstdint>
#include <functional>
#include <string>
#include <vector>
namespace gateway {
struct GatewayBacnetServerConfig {
uint32_t device_instance{4194303};
std::string device_name{"DALI Gateway"};
std::string local_address;
uint16_t udp_port{47808};
uint32_t task_stack_size{8192};
UBaseType_t task_priority{5};
};
struct GatewayBacnetObjectBinding {
uint8_t gateway_id{0};
std::string model_id;
std::string name;
BridgeObjectType object_type{BridgeObjectType::unknown};
uint32_t object_instance{0};
std::string property{"presentValue"};
};
struct GatewayBacnetServerStatus {
bool started{false};
uint32_t device_instance{0};
uint16_t udp_port{0};
size_t channel_count{0};
size_t object_count{0};
};
using GatewayBacnetWriteCallback =
std::function<bool(BridgeObjectType object_type, uint32_t object_instance,
const std::string& property, const DaliValue& value)>;
class GatewayBacnetServer {
public:
static GatewayBacnetServer& instance();
esp_err_t registerChannel(uint8_t gateway_id, const GatewayBacnetServerConfig& config,
std::vector<GatewayBacnetObjectBinding> bindings,
GatewayBacnetWriteCallback write_callback);
GatewayBacnetServerStatus status() const;
bool configCompatible(const GatewayBacnetServerConfig& config) const;
bool handleWrite(BridgeObjectType object_type, uint32_t object_instance,
const DaliValue& value);
private:
GatewayBacnetServer();
~GatewayBacnetServer();
GatewayBacnetServer(const GatewayBacnetServer&) = delete;
GatewayBacnetServer& operator=(const GatewayBacnetServer&) = delete;
struct ChannelRegistration;
struct RuntimeBinding;
esp_err_t startStackLocked(const GatewayBacnetServerConfig& config);
esp_err_t rebuildObjectsLocked();
static void TaskEntry(void* arg);
void taskLoop();
GatewayBacnetServerConfig active_config_;
std::vector<ChannelRegistration> channels_;
std::vector<RuntimeBinding> runtime_bindings_;
mutable SemaphoreHandle_t lock_{nullptr};
TaskHandle_t task_handle_{nullptr};
bool started_{false};
};
} // namespace gateway
@@ -0,0 +1,58 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum gateway_bacnet_object_kind {
GW_BACNET_OBJECT_UNKNOWN = 0,
GW_BACNET_OBJECT_ANALOG_VALUE,
GW_BACNET_OBJECT_ANALOG_OUTPUT,
GW_BACNET_OBJECT_BINARY_VALUE,
GW_BACNET_OBJECT_BINARY_OUTPUT,
GW_BACNET_OBJECT_MULTI_STATE_VALUE,
} gateway_bacnet_object_kind_t;
typedef enum gateway_bacnet_write_value_kind {
GW_BACNET_WRITE_VALUE_REAL = 1,
GW_BACNET_WRITE_VALUE_BOOLEAN = 2,
GW_BACNET_WRITE_VALUE_UNSIGNED = 3,
} gateway_bacnet_write_value_kind_t;
typedef struct gateway_bacnet_write_value {
gateway_bacnet_write_value_kind_t kind;
double real_value;
bool boolean_value;
uint32_t unsigned_value;
} gateway_bacnet_write_value_t;
typedef void (*gateway_bacnet_stack_write_callback_t)(
gateway_bacnet_object_kind_t object_kind,
uint32_t object_instance,
const gateway_bacnet_write_value_t* value,
void* context);
bool gateway_bacnet_stack_start(
uint32_t device_instance,
const char* device_name,
uint16_t udp_port,
gateway_bacnet_stack_write_callback_t write_callback,
void* callback_context);
void gateway_bacnet_stack_cleanup(void);
bool gateway_bacnet_stack_upsert_object(
gateway_bacnet_object_kind_t object_kind,
uint32_t object_instance,
const char* object_name,
const char* description);
void gateway_bacnet_stack_send_i_am(void);
void gateway_bacnet_stack_poll(uint16_t elapsed_ms);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,115 @@
#include "bip.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "lwip/inet.h"
#include "lwip/sockets.h"
#include <cerrno>
#include <cstdint>
#include <cstring>
namespace {
constexpr const char* kTag = "gateway_bacnet_socket";
int s_socket = -1;
bool NetifInfo(const char* ifkey, uint8_t* local_addr, uint8_t* netmask) {
esp_netif_t* netif = esp_netif_get_handle_from_ifkey(ifkey);
if (netif == nullptr) {
return false;
}
esp_netif_ip_info_t ip_info = {};
if (esp_netif_get_ip_info(netif, &ip_info) != ESP_OK || ip_info.ip.addr == 0) {
return false;
}
std::memcpy(local_addr, &ip_info.ip.addr, 4);
std::memcpy(netmask, &ip_info.netmask.addr, 4);
return true;
}
} // namespace
extern "C" bool bip_socket_init(uint16_t port) {
bip_socket_cleanup();
s_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (s_socket < 0) {
ESP_LOGE(kTag, "failed to create BACnet/IP UDP socket errno=%d", errno);
return false;
}
int enabled = 1;
setsockopt(s_socket, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled));
setsockopt(s_socket, SOL_SOCKET, SO_BROADCAST, &enabled, sizeof(enabled));
sockaddr_in local = {};
local.sin_family = AF_INET;
local.sin_addr.s_addr = htonl(INADDR_ANY);
local.sin_port = htons(port);
if (bind(s_socket, reinterpret_cast<sockaddr*>(&local), sizeof(local)) != 0) {
ESP_LOGE(kTag, "failed to bind BACnet/IP UDP port %u errno=%d", port, errno);
bip_socket_cleanup();
return false;
}
timeval timeout = {};
timeout.tv_sec = 0;
timeout.tv_usec = 1000;
setsockopt(s_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
ESP_LOGI(kTag, "BACnet/IP UDP socket bound on port %u", port);
return true;
}
extern "C" int bip_socket_send(const uint8_t* dest_addr, uint16_t dest_port,
const uint8_t* mtu, uint16_t mtu_len) {
if (s_socket < 0 || dest_addr == nullptr || mtu == nullptr || mtu_len == 0) {
return -1;
}
sockaddr_in dest = {};
dest.sin_family = AF_INET;
dest.sin_port = htons(dest_port);
std::memcpy(&dest.sin_addr.s_addr, dest_addr, 4);
const int sent = sendto(s_socket, mtu, mtu_len, 0, reinterpret_cast<sockaddr*>(&dest),
sizeof(dest));
return sent == mtu_len ? sent : -1;
}
extern "C" int bip_socket_receive(uint8_t* buf, uint16_t buf_len, uint8_t* src_addr,
uint16_t* src_port) {
if (s_socket < 0 || buf == nullptr || src_addr == nullptr || src_port == nullptr ||
buf_len == 0) {
return 0;
}
sockaddr_in source = {};
socklen_t source_len = sizeof(source);
const int received = recvfrom(s_socket, buf, buf_len, 0, reinterpret_cast<sockaddr*>(&source),
&source_len);
if (received <= 0) {
return 0;
}
std::memcpy(src_addr, &source.sin_addr.s_addr, 4);
*src_port = ntohs(source.sin_port);
return received;
}
extern "C" void bip_socket_cleanup(void) {
if (s_socket >= 0) {
close(s_socket);
s_socket = -1;
}
}
extern "C" bool bip_get_local_network_info(uint8_t* local_addr, uint8_t* netmask) {
if (local_addr == nullptr || netmask == nullptr) {
return false;
}
return NetifInfo("WIFI_STA_DEF", local_addr, netmask) ||
NetifInfo("ETH_DEF", local_addr, netmask) ||
NetifInfo("WIFI_AP_DEF", local_addr, netmask);
}
@@ -0,0 +1,330 @@
#include "gateway_bacnet.hpp"
#include "gateway_bacnet_stack_port.h"
#include "esp_log.h"
#include "freertos/semphr.h"
#include <algorithm>
#include <map>
#include <optional>
#include <set>
#include <string_view>
#include <utility>
namespace gateway {
namespace {
constexpr const char* kTag = "gateway_bacnet";
constexpr TickType_t kPollDelayTicks = pdMS_TO_TICKS(10);
class LockGuard {
public:
explicit LockGuard(SemaphoreHandle_t lock) : lock_(lock) {
if (lock_ != nullptr) {
xSemaphoreTakeRecursive(lock_, portMAX_DELAY);
}
}
~LockGuard() {
if (lock_ != nullptr) {
xSemaphoreGiveRecursive(lock_);
}
}
private:
SemaphoreHandle_t lock_;
};
constexpr uint32_t kMaxBacnetInstance = 4194303;
GatewayBacnetServer* g_server = nullptr;
gateway_bacnet_object_kind_t ToBacnetKind(BridgeObjectType type) {
switch (type) {
case BridgeObjectType::analogValue:
return GW_BACNET_OBJECT_ANALOG_VALUE;
case BridgeObjectType::analogOutput:
return GW_BACNET_OBJECT_ANALOG_OUTPUT;
case BridgeObjectType::binaryValue:
return GW_BACNET_OBJECT_BINARY_VALUE;
case BridgeObjectType::binaryOutput:
return GW_BACNET_OBJECT_BINARY_OUTPUT;
case BridgeObjectType::multiStateValue:
return GW_BACNET_OBJECT_MULTI_STATE_VALUE;
default:
return GW_BACNET_OBJECT_UNKNOWN;
}
}
BridgeObjectType FromBacnetKind(gateway_bacnet_object_kind_t kind) {
switch (kind) {
case GW_BACNET_OBJECT_ANALOG_VALUE:
return BridgeObjectType::analogValue;
case GW_BACNET_OBJECT_ANALOG_OUTPUT:
return BridgeObjectType::analogOutput;
case GW_BACNET_OBJECT_BINARY_VALUE:
return BridgeObjectType::binaryValue;
case GW_BACNET_OBJECT_BINARY_OUTPUT:
return BridgeObjectType::binaryOutput;
case GW_BACNET_OBJECT_MULTI_STATE_VALUE:
return BridgeObjectType::multiStateValue;
default:
return BridgeObjectType::unknown;
}
}
bool IsSupportedObjectType(BridgeObjectType type) {
return ToBacnetKind(type) != GW_BACNET_OBJECT_UNKNOWN;
}
std::string ObjectName(const GatewayBacnetObjectBinding& binding) {
if (!binding.name.empty()) {
return binding.name;
}
if (!binding.model_id.empty()) {
return "DALI " + binding.model_id;
}
return "DALI BACnet " + std::to_string(binding.object_instance);
}
DaliValue StackWriteValueToDali(const gateway_bacnet_write_value_t& value) {
switch (value.kind) {
case GW_BACNET_WRITE_VALUE_REAL:
return DaliValue(value.real_value);
case GW_BACNET_WRITE_VALUE_BOOLEAN:
return DaliValue(value.boolean_value);
case GW_BACNET_WRITE_VALUE_UNSIGNED:
return DaliValue(static_cast<int>(value.unsigned_value));
default:
return DaliValue();
}
}
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) {
return;
}
g_server->handleWrite(FromBacnetKind(object_kind), object_instance, StackWriteValueToDali(*value));
}
} // namespace
struct GatewayBacnetServer::ChannelRegistration {
uint8_t gateway_id{0};
GatewayBacnetServerConfig config;
std::vector<GatewayBacnetObjectBinding> bindings;
GatewayBacnetWriteCallback write_callback;
};
struct GatewayBacnetServer::RuntimeBinding {
uint8_t gateway_id{0};
BridgeObjectType object_type{BridgeObjectType::unknown};
uint32_t object_instance{0};
std::string model_id;
std::string property{"presentValue"};
GatewayBacnetWriteCallback write_callback;
};
GatewayBacnetServer& GatewayBacnetServer::instance() {
static GatewayBacnetServer server;
return server;
}
GatewayBacnetServer::GatewayBacnetServer() : lock_(xSemaphoreCreateRecursiveMutex()) {
g_server = this;
}
GatewayBacnetServer::~GatewayBacnetServer() {
if (lock_ != nullptr) {
vSemaphoreDelete(lock_);
lock_ = nullptr;
}
}
bool GatewayBacnetServer::configCompatible(const GatewayBacnetServerConfig& config) const {
LockGuard guard(lock_);
return !started_ || (active_config_.udp_port == config.udp_port &&
active_config_.device_instance == config.device_instance);
}
GatewayBacnetServerStatus GatewayBacnetServer::status() const {
LockGuard guard(lock_);
return GatewayBacnetServerStatus{started_,
active_config_.device_instance,
active_config_.udp_port,
channels_.size(),
runtime_bindings_.size()};
}
esp_err_t GatewayBacnetServer::registerChannel(
uint8_t gateway_id, const GatewayBacnetServerConfig& config,
std::vector<GatewayBacnetObjectBinding> bindings,
GatewayBacnetWriteCallback write_callback) {
if (write_callback == nullptr) {
return ESP_ERR_INVALID_ARG;
}
bindings.erase(std::remove_if(bindings.begin(), bindings.end(), [](const auto& binding) {
return !IsSupportedObjectType(binding.object_type) ||
binding.object_instance > kMaxBacnetInstance;
}),
bindings.end());
if (bindings.empty()) {
return ESP_ERR_NOT_FOUND;
}
LockGuard guard(lock_);
if (started_ && !configCompatible(config)) {
return ESP_ERR_INVALID_STATE;
}
auto channel = std::find_if(channels_.begin(), channels_.end(), [gateway_id](const auto& item) {
return item.gateway_id == gateway_id;
});
ChannelRegistration registration{gateway_id, config, std::move(bindings),
std::move(write_callback)};
if (channel == channels_.end()) {
channels_.push_back(std::move(registration));
} else {
*channel = std::move(registration);
}
esp_err_t err = startStackLocked(config);
if (err != ESP_OK) {
return err;
}
return rebuildObjectsLocked();
}
esp_err_t GatewayBacnetServer::startStackLocked(const GatewayBacnetServerConfig& config) {
if (started_) {
return ESP_OK;
}
active_config_ = config;
if (active_config_.device_name.empty()) {
active_config_.device_name = "DALI Gateway";
}
if (active_config_.udp_port == 0) {
active_config_.udp_port = 47808;
}
if (active_config_.task_stack_size < 6144) {
active_config_.task_stack_size = 6144;
}
if (!gateway_bacnet_stack_start(active_config_.device_instance, active_config_.device_name.c_str(),
active_config_.udp_port, HandleStackWrite, this)) {
ESP_LOGE(kTag, "failed to initialize BACnet/IP port %u", active_config_.udp_port);
return ESP_FAIL;
}
const BaseType_t created = xTaskCreate(&GatewayBacnetServer::TaskEntry, "gw_bacnet_ip",
active_config_.task_stack_size, this,
active_config_.task_priority, &task_handle_);
if (created != pdPASS) {
task_handle_ = nullptr;
gateway_bacnet_stack_cleanup();
return ESP_ERR_NO_MEM;
}
started_ = true;
gateway_bacnet_stack_send_i_am();
ESP_LOGI(kTag, "BACnet/IP server started device=%lu port=%u",
static_cast<unsigned long>(active_config_.device_instance), active_config_.udp_port);
return ESP_OK;
}
esp_err_t GatewayBacnetServer::rebuildObjectsLocked() {
runtime_bindings_.clear();
std::set<std::pair<BridgeObjectType, uint32_t>> used_objects;
for (const auto& channel : channels_) {
for (const auto& binding : channel.bindings) {
const auto key = std::make_pair(binding.object_type, binding.object_instance);
if (used_objects.find(key) != used_objects.end()) {
ESP_LOGE(kTag, "duplicate BACnet object type=%d instance=%lu",
static_cast<int>(binding.object_type),
static_cast<unsigned long>(binding.object_instance));
return ESP_ERR_INVALID_STATE;
}
used_objects.insert(key);
const std::string name = ObjectName(binding);
if (!gateway_bacnet_stack_upsert_object(ToBacnetKind(binding.object_type),
binding.object_instance, name.c_str(),
binding.model_id.c_str())) {
return ESP_FAIL;
}
runtime_bindings_.push_back(RuntimeBinding{channel.gateway_id,
binding.object_type,
binding.object_instance,
binding.model_id,
binding.property.empty() ? "presentValue"
: binding.property,
channel.write_callback});
}
}
ESP_LOGI(kTag, "BACnet/IP object table updated objects=%u",
static_cast<unsigned>(runtime_bindings_.size()));
return ESP_OK;
}
bool GatewayBacnetServer::handleWrite(BridgeObjectType object_type, uint32_t object_instance,
const DaliValue& value) {
GatewayBacnetWriteCallback callback;
std::string property;
std::string model_id;
uint8_t gateway_id = 0;
{
LockGuard guard(lock_);
const auto binding = std::find_if(runtime_bindings_.begin(), runtime_bindings_.end(),
[object_type, object_instance](const auto& item) {
return item.object_type == object_type &&
item.object_instance == object_instance;
});
if (binding == runtime_bindings_.end()) {
ESP_LOGW(kTag, "write for unmapped BACnet object type=%d instance=%lu",
static_cast<int>(object_type), static_cast<unsigned long>(object_instance));
return false;
}
callback = binding->write_callback;
property = binding->property;
model_id = binding->model_id;
gateway_id = binding->gateway_id;
}
const bool ok = callback != nullptr && callback(object_type, object_instance, property, value);
if (!ok) {
ESP_LOGW(kTag, "gateway=%u BACnet write failed model=%s object=%lu",
gateway_id, model_id.c_str(), static_cast<unsigned long>(object_instance));
}
return ok;
}
void GatewayBacnetServer::TaskEntry(void* arg) {
static_cast<GatewayBacnetServer*>(arg)->taskLoop();
}
void GatewayBacnetServer::taskLoop() {
TickType_t last_timer = xTaskGetTickCount();
while (true) {
const TickType_t now = xTaskGetTickCount();
const TickType_t elapsed = now - last_timer;
uint16_t elapsed_ms = 0;
if (elapsed >= pdMS_TO_TICKS(1000)) {
elapsed_ms = static_cast<uint16_t>(elapsed * portTICK_PERIOD_MS);
last_timer = now;
}
{
LockGuard guard(lock_);
gateway_bacnet_stack_poll(elapsed_ms);
}
vTaskDelay(kPollDelayTicks);
}
}
} // namespace gateway
@@ -0,0 +1,288 @@
#include "gateway_bacnet_stack_port.h"
#include <stddef.h>
#include <string.h>
#include "bacnet/apdu.h"
#include "bacnet/basic/binding/address.h"
#include "bacnet/basic/object/ao.h"
#include "bacnet/basic/object/av.h"
#include "bacnet/basic/object/bo.h"
#include "bacnet/basic/object/bv.h"
#include "bacnet/basic/object/device.h"
#include "bacnet/basic/object/msv.h"
#include "bacnet/basic/service/h_apdu.h"
#include "bacnet/basic/services.h"
#include "bacnet/basic/tsm/tsm.h"
#include "bacnet/bacdef.h"
#include "bacnet/npdu.h"
#include "bip.h"
static gateway_bacnet_stack_write_callback_t Write_Callback;
static void* Write_Callback_Context;
static uint8_t Rx_Buffer[BIP_MPDU_MAX];
static const char Multistate_Value_States[] =
"State 1\0"
"State 2\0"
"State 3\0"
"State 4\0"
"State 5\0"
"State 6\0"
"State 7\0"
"State 8\0"
"State 9\0"
"State 10\0"
"State 11\0"
"State 12\0"
"State 13\0"
"State 14\0"
"State 15\0"
"State 16\0";
static void notify_write_real(
gateway_bacnet_object_kind_t object_kind, uint32_t object_instance, double value)
{
if (Write_Callback) {
gateway_bacnet_write_value_t write_value = {
.kind = GW_BACNET_WRITE_VALUE_REAL,
.real_value = value,
.boolean_value = false,
.unsigned_value = 0,
};
Write_Callback(object_kind, object_instance, &write_value, Write_Callback_Context);
}
}
static void notify_write_boolean(
gateway_bacnet_object_kind_t object_kind, uint32_t object_instance, bool value)
{
if (Write_Callback) {
gateway_bacnet_write_value_t write_value = {
.kind = GW_BACNET_WRITE_VALUE_BOOLEAN,
.real_value = 0.0,
.boolean_value = value,
.unsigned_value = 0,
};
Write_Callback(object_kind, object_instance, &write_value, Write_Callback_Context);
}
}
static void notify_write_unsigned(
gateway_bacnet_object_kind_t object_kind, uint32_t object_instance, uint32_t value)
{
if (Write_Callback) {
gateway_bacnet_write_value_t write_value = {
.kind = GW_BACNET_WRITE_VALUE_UNSIGNED,
.real_value = 0.0,
.boolean_value = false,
.unsigned_value = value,
};
Write_Callback(object_kind, object_instance, &write_value, Write_Callback_Context);
}
}
static void analog_value_write(uint32_t object_instance, float old_value, float value)
{
(void)old_value;
notify_write_real(GW_BACNET_OBJECT_ANALOG_VALUE, object_instance, value);
}
static void analog_output_write(uint32_t object_instance, float old_value, float value)
{
(void)old_value;
notify_write_real(GW_BACNET_OBJECT_ANALOG_OUTPUT, object_instance, value);
}
static void binary_value_write(
uint32_t object_instance, BACNET_BINARY_PV old_value, BACNET_BINARY_PV value)
{
(void)old_value;
notify_write_boolean(GW_BACNET_OBJECT_BINARY_VALUE, object_instance, value == BINARY_ACTIVE);
}
static void binary_output_write(
uint32_t object_instance, BACNET_BINARY_PV old_value, BACNET_BINARY_PV value)
{
(void)old_value;
notify_write_boolean(GW_BACNET_OBJECT_BINARY_OUTPUT, object_instance, value == BINARY_ACTIVE);
}
static void multistate_value_write(uint32_t object_instance, uint32_t old_value, uint32_t value)
{
(void)old_value;
notify_write_unsigned(GW_BACNET_OBJECT_MULTI_STATE_VALUE, object_instance, value);
}
static object_functions_t Object_Table[] = {
{ OBJECT_DEVICE, NULL, Device_Count, Device_Index_To_Instance,
Device_Valid_Object_Instance_Number, Device_Object_Name, Device_Read_Property_Local,
Device_Write_Property_Local, Device_Property_Lists, DeviceGetRRInfo, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, Device_Writable_Property_List },
{ OBJECT_ANALOG_VALUE, Analog_Value_Init, Analog_Value_Count,
Analog_Value_Index_To_Instance, Analog_Value_Valid_Instance, Analog_Value_Object_Name,
Analog_Value_Read_Property, Analog_Value_Write_Property, Analog_Value_Property_Lists,
NULL, NULL, Analog_Value_Encode_Value_List, Analog_Value_Change_Of_Value,
Analog_Value_Change_Of_Value_Clear, NULL, NULL, NULL, Analog_Value_Create,
Analog_Value_Delete, NULL, Analog_Value_Writable_Property_List },
{ OBJECT_ANALOG_OUTPUT, Analog_Output_Init, Analog_Output_Count,
Analog_Output_Index_To_Instance, Analog_Output_Valid_Instance, Analog_Output_Object_Name,
Analog_Output_Read_Property, Analog_Output_Write_Property, Analog_Output_Property_Lists,
NULL, NULL, Analog_Output_Encode_Value_List, Analog_Output_Change_Of_Value,
Analog_Output_Change_Of_Value_Clear, NULL, NULL, NULL, Analog_Output_Create,
Analog_Output_Delete, NULL, Analog_Output_Writable_Property_List },
{ OBJECT_BINARY_VALUE, Binary_Value_Init, Binary_Value_Count,
Binary_Value_Index_To_Instance, Binary_Value_Valid_Instance, Binary_Value_Object_Name,
Binary_Value_Read_Property, Binary_Value_Write_Property, Binary_Value_Property_Lists,
NULL, NULL, Binary_Value_Encode_Value_List, Binary_Value_Change_Of_Value,
Binary_Value_Change_Of_Value_Clear, NULL, NULL, NULL, Binary_Value_Create,
Binary_Value_Delete, NULL, Binary_Value_Writable_Property_List },
{ OBJECT_BINARY_OUTPUT, Binary_Output_Init, Binary_Output_Count,
Binary_Output_Index_To_Instance, Binary_Output_Valid_Instance, Binary_Output_Object_Name,
Binary_Output_Read_Property, Binary_Output_Write_Property, Binary_Output_Property_Lists,
NULL, NULL, Binary_Output_Encode_Value_List, Binary_Output_Change_Of_Value,
Binary_Output_Change_Of_Value_Clear, NULL, NULL, NULL, Binary_Output_Create,
Binary_Output_Delete, NULL, Binary_Output_Writable_Property_List },
{ OBJECT_MULTI_STATE_VALUE, Multistate_Value_Init, Multistate_Value_Count,
Multistate_Value_Index_To_Instance, Multistate_Value_Valid_Instance,
Multistate_Value_Object_Name, Multistate_Value_Read_Property,
Multistate_Value_Write_Property, Multistate_Value_Property_Lists, NULL, NULL,
Multistate_Value_Encode_Value_List, Multistate_Value_Change_Of_Value,
Multistate_Value_Change_Of_Value_Clear, NULL, NULL, NULL, Multistate_Value_Create,
Multistate_Value_Delete, NULL, Multistate_Value_Writable_Property_List },
{ MAX_BACNET_OBJECT_TYPE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
};
bool gateway_bacnet_stack_start(
uint32_t device_instance,
const char* device_name,
uint16_t udp_port,
gateway_bacnet_stack_write_callback_t write_callback,
void* callback_context)
{
if (!device_name || device_name[0] == '\0') {
device_name = "DALI Gateway";
}
if (udp_port == 0) {
udp_port = 47808;
}
Write_Callback = write_callback;
Write_Callback_Context = callback_context;
address_init();
Device_Set_Object_Instance_Number(device_instance);
Device_Init(Object_Table);
Device_Object_Name_ANSI_Init(device_name);
Device_Set_Vendor_Name("TonyCloud", strlen("TonyCloud"));
Device_Set_Vendor_Identifier(260);
Device_Set_Model_Name("DALI Gateway", strlen("DALI Gateway"));
Device_Set_Description("DALI BACnet/IP bridge", strlen("DALI BACnet/IP bridge"));
Analog_Value_Write_Present_Value_Callback_Set(analog_value_write);
Analog_Output_Write_Present_Value_Callback_Set(analog_output_write);
Binary_Value_Write_Present_Value_Callback_Set(binary_value_write);
Binary_Output_Write_Present_Value_Callback_Set(binary_output_write);
Multistate_Value_Write_Present_Value_Callback_Set(multistate_value_write);
apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service);
apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is);
apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, handler_who_has);
apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROPERTY, handler_read_property);
apdu_set_confirmed_handler(
SERVICE_CONFIRMED_READ_PROP_MULTIPLE, handler_read_property_multiple);
apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROPERTY, handler_write_property);
apdu_set_confirmed_handler(
SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, handler_device_communication_control);
return bip_init(udp_port);
}
void gateway_bacnet_stack_cleanup(void)
{
bip_cleanup();
Write_Callback = NULL;
Write_Callback_Context = NULL;
}
bool gateway_bacnet_stack_upsert_object(
gateway_bacnet_object_kind_t object_kind,
uint32_t object_instance,
const char* object_name,
const char* description)
{
if (!object_name || object_name[0] == '\0') {
object_name = "DALI BACnet Object";
}
if (!description) {
description = "";
}
switch (object_kind) {
case GW_BACNET_OBJECT_ANALOG_VALUE:
if (!Analog_Value_Valid_Instance(object_instance)) {
Analog_Value_Create(object_instance);
}
Analog_Value_Name_Set(object_instance, object_name);
Analog_Value_Description_Set(object_instance, description);
Analog_Value_Units_Set(object_instance, UNITS_PERCENT);
Analog_Value_Present_Value_Set(object_instance, 0.0f, BACNET_NO_PRIORITY);
return true;
case GW_BACNET_OBJECT_ANALOG_OUTPUT:
if (!Analog_Output_Valid_Instance(object_instance)) {
Analog_Output_Create(object_instance);
}
Analog_Output_Name_Set(object_instance, object_name);
Analog_Output_Description_Set(object_instance, description);
Analog_Output_Units_Set(object_instance, UNITS_PERCENT);
Analog_Output_Present_Value_Set(object_instance, 0.0f, BACNET_MAX_PRIORITY);
return true;
case GW_BACNET_OBJECT_BINARY_VALUE:
if (!Binary_Value_Valid_Instance(object_instance)) {
Binary_Value_Create(object_instance);
}
Binary_Value_Name_Set(object_instance, object_name);
Binary_Value_Description_Set(object_instance, description);
Binary_Value_Write_Enable(object_instance);
Binary_Value_Present_Value_Set(object_instance, BINARY_INACTIVE);
return true;
case GW_BACNET_OBJECT_BINARY_OUTPUT:
if (!Binary_Output_Valid_Instance(object_instance)) {
Binary_Output_Create(object_instance);
}
Binary_Output_Name_Set(object_instance, object_name);
Binary_Output_Description_Set(object_instance, description);
Binary_Output_Present_Value_Set(object_instance, BINARY_INACTIVE, BACNET_MAX_PRIORITY);
return true;
case GW_BACNET_OBJECT_MULTI_STATE_VALUE:
if (!Multistate_Value_Valid_Instance(object_instance)) {
Multistate_Value_Create(object_instance);
}
Multistate_Value_Name_Set(object_instance, object_name);
Multistate_Value_Description_Set(object_instance, description);
Multistate_Value_State_Text_List_Set(object_instance, Multistate_Value_States);
Multistate_Value_Write_Enable(object_instance);
Multistate_Value_Present_Value_Set(object_instance, 1);
return true;
default:
return false;
}
}
void gateway_bacnet_stack_send_i_am(void)
{
Send_I_Am(&Handler_Transmit_Buffer[0]);
}
void gateway_bacnet_stack_poll(uint16_t elapsed_ms)
{
BACNET_ADDRESS src = { 0 };
uint16_t pdu_len = bip_receive(&src, Rx_Buffer, sizeof(Rx_Buffer), 0);
if (pdu_len > 0) {
npdu_handler(&src, Rx_Buffer, pdu_len);
}
if (elapsed_ms > 0) {
tsm_timer_milliseconds(elapsed_ms);
Device_Timer(elapsed_ms);
}
}