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