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:
@@ -1,6 +1,6 @@
|
||||
idf_component_register(
|
||||
SRCS "app_main.cpp"
|
||||
REQUIRES gateway_core gateway_controller gateway_network dali_domain gateway_runtime gateway_ble gateway_usb_setup log
|
||||
REQUIRES gateway_core gateway_controller gateway_network gateway_bridge dali_domain gateway_runtime gateway_ble gateway_usb_setup log
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -322,6 +322,79 @@ config GATEWAY_SMARTCONFIG_TIMEOUT_SEC
|
||||
help
|
||||
Timeout passed to ESP-IDF smartconfig before provisioning restarts internally.
|
||||
|
||||
config GATEWAY_BRIDGE_SUPPORTED
|
||||
bool "dali_cpp bridge runtime is supported"
|
||||
default y
|
||||
help
|
||||
Enables per-channel dali_cpp bridge model provisioning, execution, and protocol adapter state.
|
||||
|
||||
config GATEWAY_MODBUS_BRIDGE_SUPPORTED
|
||||
bool "Modbus TCP bridge is supported"
|
||||
depends on GATEWAY_BRIDGE_SUPPORTED && GATEWAY_WIFI_SUPPORTED
|
||||
default y
|
||||
help
|
||||
Enables the per-channel Modbus TCP adapter backed by DaliModbusBridge. Runtime startup still requires persisted bridge config with Modbus settings.
|
||||
|
||||
config GATEWAY_START_MODBUS_BRIDGE_ENABLED
|
||||
bool "Start Modbus TCP bridge at startup"
|
||||
depends on GATEWAY_MODBUS_BRIDGE_SUPPORTED
|
||||
default n
|
||||
help
|
||||
Starts configured Modbus TCP listeners at boot. Disabled by default so ports are opened only after provisioning or explicit runtime start.
|
||||
|
||||
config GATEWAY_BACNET_BRIDGE_SUPPORTED
|
||||
bool "BACnet/IP bridge is supported"
|
||||
depends on GATEWAY_BRIDGE_SUPPORTED && GATEWAY_WIFI_SUPPORTED
|
||||
default n
|
||||
help
|
||||
Enables BACnet bridge configuration, binding discovery, and the bacnet-stack BACnet/IP server adapter.
|
||||
Disable this option for smaller flash builds that do not need BACnet/IP.
|
||||
|
||||
config GATEWAY_START_BACNET_BRIDGE_ENABLED
|
||||
bool "Start BACnet/IP bridge at startup"
|
||||
depends on GATEWAY_BACNET_BRIDGE_SUPPORTED
|
||||
default n
|
||||
help
|
||||
Starts configured BACnet/IP object bindings at boot. Disabled by default so the UDP BACnet/IP port is opened only after provisioning or explicit runtime start.
|
||||
|
||||
config GATEWAY_CLOUD_BRIDGE_SUPPORTED
|
||||
bool "MQTT cloud bridge is supported"
|
||||
depends on GATEWAY_BRIDGE_SUPPORTED && GATEWAY_WIFI_SUPPORTED
|
||||
default y
|
||||
help
|
||||
Enables per-channel DaliCloudBridge provisioning and MQTT downlink execution.
|
||||
|
||||
config GATEWAY_START_CLOUD_BRIDGE_ENABLED
|
||||
bool "Start MQTT cloud bridge at startup"
|
||||
depends on GATEWAY_CLOUD_BRIDGE_SUPPORTED
|
||||
default n
|
||||
help
|
||||
Starts configured MQTT cloud bridges at boot when broker URI and device id are present.
|
||||
|
||||
config GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE
|
||||
int "Modbus bridge task stack bytes"
|
||||
depends on GATEWAY_MODBUS_BRIDGE_SUPPORTED
|
||||
range 4096 16384
|
||||
default 6144
|
||||
|
||||
config GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY
|
||||
int "Modbus bridge task priority"
|
||||
depends on GATEWAY_MODBUS_BRIDGE_SUPPORTED
|
||||
range 1 10
|
||||
default 4
|
||||
|
||||
config GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE
|
||||
int "BACnet/IP bridge task stack bytes"
|
||||
depends on GATEWAY_BACNET_BRIDGE_SUPPORTED
|
||||
range 6144 24576
|
||||
default 8192
|
||||
|
||||
config GATEWAY_BRIDGE_BACNET_TASK_PRIORITY
|
||||
int "BACnet/IP bridge task priority"
|
||||
depends on GATEWAY_BACNET_BRIDGE_SUPPORTED
|
||||
range 1 10
|
||||
default 5
|
||||
|
||||
choice GATEWAY_USB_STARTUP_MODE
|
||||
prompt "USB Serial/JTAG startup mode"
|
||||
default GATEWAY_USB_STARTUP_DEBUG_JTAG
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "dali_domain.hpp"
|
||||
#include "gateway_ble.hpp"
|
||||
#include "gateway_bridge.hpp"
|
||||
#include "gateway_controller.hpp"
|
||||
#include "gateway_core.hpp"
|
||||
#include "gateway_network.hpp"
|
||||
@@ -57,6 +58,22 @@
|
||||
#define CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC 60
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE
|
||||
#define CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE 6144
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY
|
||||
#define CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY 4
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE
|
||||
#define CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE 8192
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY
|
||||
#define CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY 5
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
constexpr const char* kProjectName = "DALI_485_Gateway";
|
||||
constexpr const char* kProjectVersion = "0.1.0";
|
||||
@@ -116,9 +133,52 @@ constexpr bool kUsbSetupStartupEnabled = true;
|
||||
constexpr bool kUsbSetupStartupEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_BRIDGE_SUPPORTED
|
||||
constexpr bool kBridgeSupported = true;
|
||||
#else
|
||||
constexpr bool kBridgeSupported = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_MODBUS_BRIDGE_SUPPORTED
|
||||
constexpr bool kModbusBridgeSupported = true;
|
||||
#else
|
||||
constexpr bool kModbusBridgeSupported = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_START_MODBUS_BRIDGE_ENABLED
|
||||
constexpr bool kModbusBridgeStartupEnabled = true;
|
||||
#else
|
||||
constexpr bool kModbusBridgeStartupEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED
|
||||
constexpr bool kBacnetBridgeSupported = true;
|
||||
#else
|
||||
constexpr bool kBacnetBridgeSupported = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED
|
||||
constexpr bool kBacnetBridgeStartupEnabled = true;
|
||||
#else
|
||||
constexpr bool kBacnetBridgeStartupEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED
|
||||
constexpr bool kCloudBridgeSupported = true;
|
||||
#else
|
||||
constexpr bool kCloudBridgeSupported = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED
|
||||
constexpr bool kCloudBridgeStartupEnabled = true;
|
||||
#else
|
||||
constexpr bool kCloudBridgeStartupEnabled = false;
|
||||
#endif
|
||||
|
||||
std::unique_ptr<gateway::DaliDomainService> s_dali_domain;
|
||||
std::unique_ptr<gateway::GatewayRuntime> s_runtime;
|
||||
std::unique_ptr<gateway::GatewayController> s_controller;
|
||||
std::unique_ptr<gateway::GatewayBridgeService> s_bridge;
|
||||
std::unique_ptr<gateway::GatewayNetworkService> s_network;
|
||||
std::unique_ptr<gateway::GatewayBleBridge> s_ble_bridge;
|
||||
std::unique_ptr<gateway::GatewayUsbSetupBridge> s_usb_setup_bridge;
|
||||
@@ -361,6 +421,29 @@ extern "C" void app_main(void) {
|
||||
controller_config);
|
||||
ESP_ERROR_CHECK(s_controller->start());
|
||||
|
||||
if (kBridgeSupported) {
|
||||
gateway::GatewayBridgeServiceConfig bridge_config;
|
||||
bridge_config.bridge_enabled = true;
|
||||
bridge_config.modbus_enabled = profile.enable_wifi && kModbusBridgeSupported;
|
||||
bridge_config.modbus_startup_enabled = profile.enable_wifi && kModbusBridgeSupported &&
|
||||
kModbusBridgeStartupEnabled;
|
||||
bridge_config.bacnet_enabled = profile.enable_wifi && kBacnetBridgeSupported;
|
||||
bridge_config.bacnet_startup_enabled = profile.enable_wifi && kBacnetBridgeSupported &&
|
||||
kBacnetBridgeStartupEnabled;
|
||||
bridge_config.cloud_enabled = profile.enable_wifi && kCloudBridgeSupported;
|
||||
bridge_config.cloud_startup_enabled = profile.enable_wifi && kCloudBridgeSupported &&
|
||||
kCloudBridgeStartupEnabled;
|
||||
bridge_config.modbus_task_stack_size =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE);
|
||||
bridge_config.modbus_task_priority =
|
||||
static_cast<UBaseType_t>(CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY);
|
||||
bridge_config.bacnet_task_stack_size =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE);
|
||||
bridge_config.bacnet_task_priority =
|
||||
static_cast<UBaseType_t>(CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY);
|
||||
s_bridge = std::make_unique<gateway::GatewayBridgeService>(*s_dali_domain, bridge_config);
|
||||
}
|
||||
|
||||
if (profile.enable_wifi || profile.enable_eth) {
|
||||
gateway::GatewayNetworkServiceConfig network_config;
|
||||
network_config.wifi_enabled = profile.enable_wifi && kWifiStartupEnabled;
|
||||
@@ -397,10 +480,15 @@ extern "C" void app_main(void) {
|
||||
network_config.boot_button_active_low = false;
|
||||
#endif
|
||||
s_network = std::make_unique<gateway::GatewayNetworkService>(*s_controller, *s_runtime,
|
||||
*s_dali_domain, network_config);
|
||||
*s_dali_domain, network_config,
|
||||
s_bridge.get());
|
||||
ESP_ERROR_CHECK(s_network->start());
|
||||
}
|
||||
|
||||
if (s_bridge != nullptr) {
|
||||
ESP_ERROR_CHECK(s_bridge->start());
|
||||
}
|
||||
|
||||
if (profile.enable_ble) {
|
||||
s_ble_bridge = std::make_unique<gateway::GatewayBleBridge>(*s_controller, *s_runtime,
|
||||
*s_dali_domain);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
nvs, data, nvs, 0x9000, 0x6000,
|
||||
otadata, data, ota, 0xf000, 0x2000,
|
||||
phy_init, data, phy, 0x11000, 0x1000,
|
||||
factory, app, factory, 0x20000, 0x180000,
|
||||
ota_0, app, ota_0, 0x1a0000, 0x180000,
|
||||
ota_1, app, ota_1, 0x320000, 0x180000,
|
||||
storage, data, spiffs, 0x4a0000, 0xb60000,
|
||||
factory, app, factory, 0x20000, 0x400000,
|
||||
ota_0, app, ota_0, 0x420000, 0x400000,
|
||||
ota_1, app, ota_1, 0x820000, 0x400000,
|
||||
storage, data, spiffs, 0xc20000, 0x3e0000,
|
||||
|
@@ -632,6 +632,17 @@ CONFIG_GATEWAY_SMARTCONFIG_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_ESPNOW_SETUP_ENABLED is not set
|
||||
# CONFIG_GATEWAY_START_SMARTCONFIG_ENABLED is not set
|
||||
CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC=60
|
||||
CONFIG_GATEWAY_BRIDGE_SUPPORTED=y
|
||||
CONFIG_GATEWAY_MODBUS_BRIDGE_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_MODBUS_BRIDGE_ENABLED is not set
|
||||
CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set
|
||||
CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set
|
||||
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144
|
||||
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY=4
|
||||
CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE=8192
|
||||
CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY=5
|
||||
CONFIG_GATEWAY_USB_STARTUP_DEBUG_JTAG=y
|
||||
# CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL is not set
|
||||
# end of Gateway Startup Services
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -75,6 +76,23 @@ struct DaliRawFrame {
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
enum class DaliDt8SceneColorMode {
|
||||
kDisabled,
|
||||
kColorTemperature,
|
||||
kRgb,
|
||||
};
|
||||
|
||||
struct DaliDomainSnapshot {
|
||||
uint8_t gateway_id{0};
|
||||
int address{0};
|
||||
std::string kind;
|
||||
std::map<std::string, bool> bools;
|
||||
std::map<std::string, int> ints;
|
||||
std::map<std::string, double> numbers;
|
||||
std::map<std::string, std::vector<int>> int_arrays;
|
||||
std::map<std::string, std::vector<double>> number_arrays;
|
||||
};
|
||||
|
||||
class DaliDomainService {
|
||||
public:
|
||||
DaliDomainService();
|
||||
@@ -97,6 +115,24 @@ class DaliDomainService {
|
||||
bool sendRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const;
|
||||
bool sendExtRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const;
|
||||
std::optional<uint8_t> queryRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const;
|
||||
std::optional<DaliDomainSnapshot> discoverDeviceTypes(
|
||||
uint8_t gateway_id, int short_address, const std::vector<int>& fallback_types = {},
|
||||
int max_next_types = 16) const;
|
||||
std::optional<DaliDomainSnapshot> dt4Snapshot(uint8_t gateway_id, int short_address) const;
|
||||
std::optional<DaliDomainSnapshot> dt5Snapshot(uint8_t gateway_id, int short_address) const;
|
||||
std::optional<DaliDomainSnapshot> dt6Snapshot(uint8_t gateway_id, int short_address) const;
|
||||
std::optional<DaliDomainSnapshot> dt8SceneColorReport(uint8_t gateway_id, int short_address,
|
||||
int scene) const;
|
||||
std::optional<DaliDomainSnapshot> dt8PowerOnLevelColorReport(uint8_t gateway_id,
|
||||
int short_address) const;
|
||||
std::optional<DaliDomainSnapshot> dt8SystemFailureLevelColorReport(uint8_t gateway_id,
|
||||
int short_address) const;
|
||||
bool storeDt8SceneSnapshot(uint8_t gateway_id, int short_address, int scene, int brightness,
|
||||
DaliDt8SceneColorMode color_mode = DaliDt8SceneColorMode::kDisabled,
|
||||
int color_temperature = 0, int red = 0, int green = 0,
|
||||
int blue = 0) const;
|
||||
bool storeDt8PowerOnLevelSnapshot(uint8_t gateway_id, int short_address, int level) const;
|
||||
bool storeDt8SystemFailureLevelSnapshot(uint8_t gateway_id, int short_address, int level) const;
|
||||
bool setBright(uint8_t gateway_id, int short_address, int brightness) const;
|
||||
bool setColTempRaw(uint8_t gateway_id, int short_address, int mirek) const;
|
||||
bool setColTemp(uint8_t gateway_id, int short_address, int kelvin) const;
|
||||
|
||||
@@ -18,6 +18,47 @@ constexpr const char* kTag = "dali_domain";
|
||||
constexpr size_t kSerialRxPacketMaxBytes = 8;
|
||||
constexpr UBaseType_t kSerialRxQueueDepth = 8;
|
||||
|
||||
DaliDomainSnapshot MakeSnapshot(uint8_t gateway_id, int address, const char* kind) {
|
||||
DaliDomainSnapshot snapshot;
|
||||
snapshot.gateway_id = gateway_id;
|
||||
snapshot.address = address;
|
||||
snapshot.kind = kind == nullptr ? "" : kind;
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void PutOptionalInt(DaliDomainSnapshot& snapshot, const char* name, const std::optional<T>& value) {
|
||||
if (value.has_value()) {
|
||||
snapshot.ints[name] = static_cast<int>(value.value());
|
||||
}
|
||||
}
|
||||
|
||||
void PutOptionalBool(DaliDomainSnapshot& snapshot, const char* name,
|
||||
const std::optional<bool>& value) {
|
||||
if (value.has_value()) {
|
||||
snapshot.bools[name] = value.value();
|
||||
}
|
||||
}
|
||||
|
||||
void PutOptionalNumber(DaliDomainSnapshot& snapshot, const char* name,
|
||||
const std::optional<double>& value) {
|
||||
if (value.has_value()) {
|
||||
snapshot.numbers[name] = value.value();
|
||||
}
|
||||
}
|
||||
|
||||
Dt8SceneStoreColorMode ToDaliCppColorMode(DaliDt8SceneColorMode color_mode) {
|
||||
switch (color_mode) {
|
||||
case DaliDt8SceneColorMode::kColorTemperature:
|
||||
return Dt8SceneStoreColorMode::colorTemperature;
|
||||
case DaliDt8SceneColorMode::kRgb:
|
||||
return Dt8SceneStoreColorMode::rgb;
|
||||
case DaliDt8SceneColorMode::kDisabled:
|
||||
default:
|
||||
return Dt8SceneStoreColorMode::disabled;
|
||||
}
|
||||
}
|
||||
|
||||
struct SerialRxPacket {
|
||||
size_t len{0};
|
||||
uint8_t data[kSerialRxPacketMaxBytes]{};
|
||||
@@ -403,6 +444,292 @@ std::optional<uint8_t> DaliDomainService::queryRaw(uint8_t gateway_id, uint8_t r
|
||||
return channel->comm->queryRawNew(raw_addr, command);
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> DaliDomainService::discoverDeviceTypes(
|
||||
uint8_t gateway_id, int short_address, const std::vector<int>& fallback_types,
|
||||
int max_next_types) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::vector<int> fallback = fallback_types.empty() ? std::vector<int>{4, 5, 6, 8}
|
||||
: fallback_types;
|
||||
auto discovery = channel->dali->base.discoverDeviceTypes(short_address, fallback,
|
||||
max_next_types);
|
||||
if (!discovery.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto snapshot = MakeSnapshot(gateway_id, short_address, "device");
|
||||
PutOptionalInt(snapshot, "rawQueryType", discovery->rawQueryType);
|
||||
PutOptionalInt(snapshot, "primaryType", discovery->primaryType());
|
||||
snapshot.int_arrays["types"] = discovery->types;
|
||||
snapshot.int_arrays["extraTypes"] = discovery->extraTypes();
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> DaliDomainService::dt4Snapshot(uint8_t gateway_id,
|
||||
int short_address) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt4");
|
||||
auto& dt4 = channel->dali->dt4;
|
||||
PutOptionalInt(snapshot, "extendedVersion", dt4.getExtendedVersion(short_address));
|
||||
PutOptionalInt(snapshot, "dimmingCurve", dt4.getDimmingCurve(short_address));
|
||||
PutOptionalInt(snapshot, "dimmerTemperatureRaw", dt4.getDimmerTemperatureRaw(short_address));
|
||||
PutOptionalInt(snapshot, "rmsSupplyVoltageRaw", dt4.getRmsSupplyVoltageRaw(short_address));
|
||||
PutOptionalInt(snapshot, "supplyFrequencyRaw", dt4.getSupplyFrequencyRaw(short_address));
|
||||
PutOptionalInt(snapshot, "rmsLoadVoltageRaw", dt4.getRmsLoadVoltageRaw(short_address));
|
||||
PutOptionalInt(snapshot, "rmsLoadCurrentRaw", dt4.getRmsLoadCurrentRaw(short_address));
|
||||
PutOptionalInt(snapshot, "realLoadPowerRaw", dt4.getRealLoadPowerRaw(short_address));
|
||||
PutOptionalInt(snapshot, "loadRatingRaw", dt4.getLoadRatingRaw(short_address));
|
||||
PutOptionalNumber(snapshot, "rmsSupplyVoltageVolts", dt4.getRmsSupplyVoltageVolts(short_address));
|
||||
PutOptionalNumber(snapshot, "supplyFrequencyHertz", dt4.getSupplyFrequencyHertz(short_address));
|
||||
PutOptionalNumber(snapshot, "rmsLoadVoltageVolts", dt4.getRmsLoadVoltageVolts(short_address));
|
||||
PutOptionalNumber(snapshot, "rmsLoadCurrentPercent", dt4.getRmsLoadCurrentPercent(short_address));
|
||||
PutOptionalNumber(snapshot, "realLoadPowerWatts", dt4.getRealLoadPowerWatts(short_address));
|
||||
PutOptionalNumber(snapshot, "loadRatingAmps", dt4.getLoadRatingAmps(short_address));
|
||||
PutOptionalBool(snapshot, "referenceRunning", dt4.isReferenceRunning(short_address));
|
||||
PutOptionalBool(snapshot, "referenceMeasurementFailed",
|
||||
dt4.isReferenceMeasurementFailed(short_address));
|
||||
|
||||
if (const auto status = dt4.getDimmerStatus(short_address)) {
|
||||
snapshot.ints["dimmerStatusRaw"] = status->raw();
|
||||
snapshot.bools["leadingEdgeModeRunning"] = status->leadingEdgeModeRunning();
|
||||
snapshot.bools["trailingEdgeModeRunning"] = status->trailingEdgeModeRunning();
|
||||
snapshot.bools["referenceMeasurementRunning"] = status->referenceMeasurementRunning();
|
||||
snapshot.bools["nonLogarithmicDimmingCurveActive"] =
|
||||
status->nonLogarithmicDimmingCurveActive();
|
||||
}
|
||||
if (const auto features = dt4.getFeatures(short_address)) {
|
||||
snapshot.ints["featuresRaw1"] = features->raw1();
|
||||
snapshot.ints["featuresRaw2"] = features->raw2();
|
||||
snapshot.ints["featuresRaw3"] = features->raw3();
|
||||
snapshot.ints["dimmingMethodCode"] = features->dimmingMethodCode();
|
||||
snapshot.bools["canQueryTemperature"] = features->canQueryTemperature();
|
||||
snapshot.bools["canQuerySupplyVoltage"] = features->canQuerySupplyVoltage();
|
||||
snapshot.bools["canQueryLoadVoltage"] = features->canQueryLoadVoltage();
|
||||
snapshot.bools["canQueryLoadCurrent"] = features->canQueryLoadCurrent();
|
||||
snapshot.bools["canQueryRealLoadPower"] = features->canQueryRealLoadPower();
|
||||
snapshot.bools["canQueryLoadRating"] = features->canQueryLoadRating();
|
||||
snapshot.bools["physicalSelectionSupported"] = features->physicalSelectionSupported();
|
||||
snapshot.bools["canSelectNonLogarithmicDimmingCurve"] =
|
||||
features->canSelectNonLogarithmicDimmingCurve();
|
||||
}
|
||||
if (const auto failure = dt4.getFailureStatus(short_address)) {
|
||||
snapshot.ints["failureRaw1"] = failure->raw1();
|
||||
snapshot.ints["failureRaw2"] = failure->raw2();
|
||||
snapshot.bools["loadOverCurrentShutdown"] = failure->loadOverCurrentShutdown();
|
||||
snapshot.bools["openCircuitDetected"] = failure->openCircuitDetected();
|
||||
snapshot.bools["loadDecreaseDetected"] = failure->loadDecreaseDetected();
|
||||
snapshot.bools["loadIncreaseDetected"] = failure->loadIncreaseDetected();
|
||||
snapshot.bools["thermalShutdown"] = failure->thermalShutdown();
|
||||
snapshot.bools["thermalOverloadReduction"] = failure->thermalOverloadReduction();
|
||||
snapshot.bools["referenceMeasurementFailedStatus"] = failure->referenceMeasurementFailed();
|
||||
snapshot.bools["supplyVoltageOutOfLimits"] = failure->supplyVoltageOutOfLimits();
|
||||
snapshot.bools["loadVoltageOutOfLimits"] = failure->loadVoltageOutOfLimits();
|
||||
snapshot.bools["loadCurrentOverloadReduction"] = failure->loadCurrentOverloadReduction();
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> DaliDomainService::dt5Snapshot(uint8_t gateway_id,
|
||||
int short_address) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt5");
|
||||
auto& dt5 = channel->dali->dt5;
|
||||
PutOptionalInt(snapshot, "extendedVersion", dt5.getExtendedVersion(short_address));
|
||||
PutOptionalInt(snapshot, "dimmingCurve", dt5.getDimmingCurve(short_address));
|
||||
PutOptionalInt(snapshot, "outputLevelRaw", dt5.getOutputLevelRaw(short_address));
|
||||
PutOptionalNumber(snapshot, "outputLevelVolts", dt5.getOutputLevelVolts(short_address));
|
||||
if (const auto features = dt5.getConverterFeatures(short_address)) {
|
||||
snapshot.ints["featuresRaw"] = features->raw();
|
||||
snapshot.bools["outputRange0To10VSelectable"] = features->outputRange0To10VSelectable();
|
||||
snapshot.bools["internalPullUpSelectable"] = features->internalPullUpSelectable();
|
||||
snapshot.bools["outputFaultDetectionSelectable"] =
|
||||
features->outputFaultDetectionSelectable();
|
||||
snapshot.bools["mainsRelay"] = features->mainsRelay();
|
||||
snapshot.bools["outputLevelQueryable"] = features->outputLevelQueryable();
|
||||
snapshot.bools["nonLogarithmicDimmingCurveSupported"] =
|
||||
features->nonLogarithmicDimmingCurveSupported();
|
||||
snapshot.bools["physicalSelectionByOutputLossSupported"] =
|
||||
features->physicalSelectionByOutputLossSupported();
|
||||
snapshot.bools["physicalSelectionSwitchSupported"] =
|
||||
features->physicalSelectionSwitchSupported();
|
||||
}
|
||||
if (const auto failure = dt5.getFailureStatus(short_address)) {
|
||||
snapshot.ints["failureRaw"] = failure->raw();
|
||||
snapshot.bools["outputFaultDetected"] = failure->outputFaultDetected();
|
||||
}
|
||||
if (const auto status = dt5.getConverterStatus(short_address)) {
|
||||
snapshot.ints["converterStatusRaw"] = status->raw();
|
||||
snapshot.bools["zeroToTenVoltOperation"] = status->zeroToTenVoltOperation();
|
||||
snapshot.bools["internalPullUpOn"] = status->internalPullUpOn();
|
||||
snapshot.bools["nonLogarithmicDimmingCurveActive"] =
|
||||
status->nonLogarithmicDimmingCurveActive();
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> DaliDomainService::dt6Snapshot(uint8_t gateway_id,
|
||||
int short_address) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt6");
|
||||
auto& dt6 = channel->dali->dt6;
|
||||
PutOptionalInt(snapshot, "extendedVersion", dt6.getExtendedVersion(short_address));
|
||||
PutOptionalInt(snapshot, "dimmingCurve", dt6.getDimmingCurve(short_address));
|
||||
PutOptionalInt(snapshot, "fastFadeTime", dt6.getFastFadeTime(short_address));
|
||||
PutOptionalInt(snapshot, "minFastFadeTime", dt6.getMinFastFadeTime(short_address));
|
||||
PutOptionalBool(snapshot, "currentProtectorEnabled",
|
||||
dt6.isCurrentProtectorEnabled(short_address));
|
||||
if (const auto gear = dt6.getGearType(short_address)) {
|
||||
snapshot.ints["gearTypeRaw"] = gear->raw();
|
||||
snapshot.bools["ledPowerSupplyIntegrated"] = gear->ledPowerSupplyIntegrated();
|
||||
snapshot.bools["ledModuleIntegrated"] = gear->ledModuleIntegrated();
|
||||
snapshot.bools["acSupplyPossible"] = gear->acSupplyPossible();
|
||||
snapshot.bools["dcSupplyPossible"] = gear->dcSupplyPossible();
|
||||
}
|
||||
if (const auto modes = dt6.getPossibleOperatingModes(short_address)) {
|
||||
snapshot.ints["possibleOperatingModesRaw"] = modes->raw();
|
||||
snapshot.bools["pwmModePossible"] = modes->pwmModePossible();
|
||||
snapshot.bools["amModePossible"] = modes->amModePossible();
|
||||
snapshot.bools["currentControlledOutputPossible"] = modes->currentControlledOutput();
|
||||
snapshot.bools["highCurrentPulseModePossible"] = modes->highCurrentPulseMode();
|
||||
}
|
||||
if (const auto features = dt6.getFeatures(short_address)) {
|
||||
snapshot.ints["featuresRaw"] = features->raw();
|
||||
snapshot.bools["canQueryShortCircuit"] = features->canQueryShortCircuit();
|
||||
snapshot.bools["canQueryOpenCircuit"] = features->canQueryOpenCircuit();
|
||||
snapshot.bools["canQueryLoadDecrease"] = features->canQueryLoadDecrease();
|
||||
snapshot.bools["canQueryLoadIncrease"] = features->canQueryLoadIncrease();
|
||||
snapshot.bools["canQueryCurrentProtector"] = features->canQueryCurrentProtector();
|
||||
snapshot.bools["canQueryThermalShutdown"] = features->canQueryThermalShutdown();
|
||||
snapshot.bools["canQueryThermalOverloadReduction"] =
|
||||
features->canQueryThermalOverloadReduction();
|
||||
snapshot.bools["physicalSelectionSupported"] = features->physicalSelectionSupported();
|
||||
}
|
||||
if (const auto failure = dt6.getFailureStatus(short_address)) {
|
||||
snapshot.ints["failureRaw"] = failure->raw();
|
||||
snapshot.bools["shortCircuit"] = failure->shortCircuit();
|
||||
snapshot.bools["openCircuit"] = failure->openCircuit();
|
||||
snapshot.bools["loadDecrease"] = failure->loadDecrease();
|
||||
snapshot.bools["loadIncrease"] = failure->loadIncrease();
|
||||
snapshot.bools["currentProtectorActive"] = failure->currentProtectorActive();
|
||||
snapshot.bools["thermalShutdown"] = failure->thermalShutdown();
|
||||
snapshot.bools["thermalOverloadReduction"] = failure->thermalOverloadReduction();
|
||||
snapshot.bools["referenceMeasurementFailed"] = failure->referenceMeasurementFailed();
|
||||
}
|
||||
if (const auto mode = dt6.getOperatingMode(short_address)) {
|
||||
snapshot.ints["operatingModeRaw"] = mode->raw();
|
||||
snapshot.bools["pwmModeActive"] = mode->pwmModeActive();
|
||||
snapshot.bools["amModeActive"] = mode->amModeActive();
|
||||
snapshot.bools["currentControlledOutput"] = mode->currentControlledOutput();
|
||||
snapshot.bools["highCurrentPulseModeActive"] = mode->highCurrentPulseModeActive();
|
||||
snapshot.bools["nonLogarithmicDimmingCurveActive"] =
|
||||
mode->nonLogarithmicDimmingCurveActive();
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> DaliDomainService::dt8SceneColorReport(
|
||||
uint8_t gateway_id, int short_address, int scene) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto report = channel->dali->dt8.getSceneColorReport(short_address, scene);
|
||||
if (!report.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt8_scene");
|
||||
snapshot.ints["scene"] = scene;
|
||||
snapshot.ints["brightness"] = report->brightness;
|
||||
snapshot.ints["colorType"] = report->colorTypeValue;
|
||||
if (report->hasColorTemperature()) {
|
||||
snapshot.ints["colorTemperature"] = report->colorTemperature.value();
|
||||
}
|
||||
if (report->hasXy()) {
|
||||
snapshot.number_arrays["xy"] = report->xy;
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> DaliDomainService::dt8PowerOnLevelColorReport(
|
||||
uint8_t gateway_id, int short_address) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto report = channel->dali->dt8.getPowerOnLevelColorReport(short_address);
|
||||
if (!report.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt8_power_on");
|
||||
snapshot.ints["level"] = report->level;
|
||||
snapshot.ints["colorType"] = report->colorTypeValue;
|
||||
if (report->hasColorTemperature()) {
|
||||
snapshot.ints["colorTemperature"] = report->colorTemperature.value();
|
||||
}
|
||||
if (report->hasXy()) {
|
||||
snapshot.number_arrays["xy"] = report->xy;
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> DaliDomainService::dt8SystemFailureLevelColorReport(
|
||||
uint8_t gateway_id, int short_address) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto report = channel->dali->dt8.getSystemFailureLevelColorReport(short_address);
|
||||
if (!report.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt8_system_failure");
|
||||
snapshot.ints["level"] = report->level;
|
||||
snapshot.ints["colorType"] = report->colorTypeValue;
|
||||
if (report->hasColorTemperature()) {
|
||||
snapshot.ints["colorTemperature"] = report->colorTemperature.value();
|
||||
}
|
||||
if (report->hasXy()) {
|
||||
snapshot.number_arrays["xy"] = report->xy;
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
bool DaliDomainService::storeDt8SceneSnapshot(uint8_t gateway_id, int short_address, int scene,
|
||||
int brightness,
|
||||
DaliDt8SceneColorMode color_mode,
|
||||
int color_temperature, int red, int green,
|
||||
int blue) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->dali != nullptr &&
|
||||
channel->dali->dt8.storeSceneSnapshot(short_address, scene, brightness,
|
||||
ToDaliCppColorMode(color_mode), color_temperature,
|
||||
red, green, blue);
|
||||
}
|
||||
|
||||
bool DaliDomainService::storeDt8PowerOnLevelSnapshot(uint8_t gateway_id, int short_address,
|
||||
int level) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->dali != nullptr &&
|
||||
channel->dali->dt8.storePowerOnLevelSnapshot(short_address, level);
|
||||
}
|
||||
|
||||
bool DaliDomainService::storeDt8SystemFailureLevelSnapshot(uint8_t gateway_id,
|
||||
int short_address, int level) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->dali != nullptr &&
|
||||
channel->dali->dt8.storeSystemFailureLevelSnapshot(short_address, level);
|
||||
}
|
||||
|
||||
bool DaliDomainService::setBright(uint8_t gateway_id, int short_address, int brightness) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->dali != nullptr &&
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
set(GATEWAY_BRIDGE_REQUIRES
|
||||
dali_domain
|
||||
dali_cpp
|
||||
espressif__cjson
|
||||
freertos
|
||||
log
|
||||
lwip
|
||||
nvs_flash
|
||||
)
|
||||
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_bridge.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES ${GATEWAY_BRIDGE_REQUIRES}
|
||||
PRIV_REQUIRES gateway_bacnet
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
namespace gateway {
|
||||
|
||||
class DaliDomainService;
|
||||
|
||||
struct GatewayBridgeServiceConfig {
|
||||
bool bridge_enabled{true};
|
||||
bool modbus_enabled{true};
|
||||
bool modbus_startup_enabled{false};
|
||||
bool bacnet_enabled{false};
|
||||
bool bacnet_startup_enabled{false};
|
||||
bool cloud_enabled{true};
|
||||
bool cloud_startup_enabled{false};
|
||||
uint32_t modbus_task_stack_size{6144};
|
||||
UBaseType_t modbus_task_priority{4};
|
||||
uint32_t bacnet_task_stack_size{8192};
|
||||
UBaseType_t bacnet_task_priority{5};
|
||||
};
|
||||
|
||||
struct GatewayBridgeHttpResponse {
|
||||
esp_err_t err{ESP_OK};
|
||||
std::string body;
|
||||
};
|
||||
|
||||
class GatewayBridgeService {
|
||||
public:
|
||||
GatewayBridgeService(DaliDomainService& dali_domain,
|
||||
GatewayBridgeServiceConfig config = {});
|
||||
~GatewayBridgeService();
|
||||
|
||||
esp_err_t start();
|
||||
|
||||
GatewayBridgeHttpResponse handleGet(const std::string& action, int gateway_id = -1,
|
||||
const std::string& query = {}) const;
|
||||
GatewayBridgeHttpResponse handlePost(const std::string& action, int gateway_id,
|
||||
const std::string& body);
|
||||
|
||||
private:
|
||||
struct ChannelRuntime;
|
||||
|
||||
ChannelRuntime* findRuntime(uint8_t gateway_id);
|
||||
const ChannelRuntime* findRuntime(uint8_t gateway_id) const;
|
||||
|
||||
DaliDomainService& dali_domain_;
|
||||
GatewayBridgeServiceConfig config_;
|
||||
std::vector<std::unique_ptr<ChannelRuntime>> runtimes_;
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_network.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES dali_domain esp_event esp_http_server esp_netif esp_wifi freertos gateway_controller gateway_runtime log lwip espressif__cjson
|
||||
REQUIRES dali_domain esp_event esp_http_server esp_netif esp_wifi freertos gateway_bridge gateway_controller gateway_runtime log lwip espressif__cjson
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -21,6 +21,7 @@ namespace gateway {
|
||||
|
||||
class GatewayController;
|
||||
class DaliDomainService;
|
||||
class GatewayBridgeService;
|
||||
struct DaliRawFrame;
|
||||
|
||||
struct GatewayNetworkServiceConfig {
|
||||
@@ -48,7 +49,9 @@ struct GatewayNetworkServiceConfig {
|
||||
class GatewayNetworkService {
|
||||
public:
|
||||
GatewayNetworkService(GatewayController& controller, GatewayRuntime& runtime,
|
||||
DaliDomainService& dali_domain, GatewayNetworkServiceConfig config = {});
|
||||
DaliDomainService& dali_domain,
|
||||
GatewayNetworkServiceConfig config = {},
|
||||
GatewayBridgeService* bridge_service = nullptr);
|
||||
|
||||
esp_err_t start();
|
||||
|
||||
@@ -58,6 +61,8 @@ class GatewayNetworkService {
|
||||
static esp_err_t HandleInfoGet(httpd_req_t* req);
|
||||
static esp_err_t HandleCommandGet(httpd_req_t* req);
|
||||
static esp_err_t HandleCommandPost(httpd_req_t* req);
|
||||
static esp_err_t HandleBridgeGet(httpd_req_t* req);
|
||||
static esp_err_t HandleBridgePost(httpd_req_t* req);
|
||||
static esp_err_t HandleLedOnGet(httpd_req_t* req);
|
||||
static esp_err_t HandleLedOffGet(httpd_req_t* req);
|
||||
static esp_err_t HandleJqJsGet(httpd_req_t* req);
|
||||
@@ -91,12 +96,14 @@ class GatewayNetworkService {
|
||||
std::string deviceInfoJson() const;
|
||||
std::string deviceInfoDoubleEncodedJson() const;
|
||||
std::string gatewaySnapshotJson();
|
||||
esp_err_t sendBridgeResponse(httpd_req_t* req, bool post);
|
||||
void setStatusLed(bool on);
|
||||
|
||||
GatewayController& controller_;
|
||||
GatewayRuntime& runtime_;
|
||||
DaliDomainService& dali_domain_;
|
||||
GatewayNetworkServiceConfig config_;
|
||||
GatewayBridgeService* bridge_service_{nullptr};
|
||||
bool started_{false};
|
||||
httpd_handle_t http_server_{nullptr};
|
||||
esp_netif_t* wifi_sta_netif_{nullptr};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "gateway_network.hpp"
|
||||
|
||||
#include "dali_domain.hpp"
|
||||
#include "gateway_bridge.hpp"
|
||||
#include "gateway_controller.hpp"
|
||||
#include "gateway_runtime.hpp"
|
||||
|
||||
@@ -18,6 +19,7 @@
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -182,14 +184,79 @@ esp_err_t RegisterUri(httpd_handle_t server, const char* uri, httpd_method_t met
|
||||
return httpd_register_uri_handler(server, &route);
|
||||
}
|
||||
|
||||
std::string QueryValue(httpd_req_t* req, const char* key) {
|
||||
if (req == nullptr || key == nullptr) {
|
||||
return {};
|
||||
}
|
||||
const size_t len = httpd_req_get_url_query_len(req) + 1;
|
||||
if (len <= 1) {
|
||||
return {};
|
||||
}
|
||||
std::string query(len, '\0');
|
||||
if (httpd_req_get_url_query_str(req, query.data(), query.size()) != ESP_OK) {
|
||||
return {};
|
||||
}
|
||||
char value[64] = {0};
|
||||
if (httpd_query_key_value(query.c_str(), key, value, sizeof(value)) != ESP_OK) {
|
||||
return {};
|
||||
}
|
||||
return std::string(value);
|
||||
}
|
||||
|
||||
std::string QueryString(httpd_req_t* req) {
|
||||
if (req == nullptr) {
|
||||
return {};
|
||||
}
|
||||
const size_t len = httpd_req_get_url_query_len(req) + 1;
|
||||
if (len <= 1) {
|
||||
return {};
|
||||
}
|
||||
std::string query(len, '\0');
|
||||
if (httpd_req_get_url_query_str(req, query.data(), query.size()) != ESP_OK) {
|
||||
return {};
|
||||
}
|
||||
if (!query.empty() && query.back() == '\0') {
|
||||
query.pop_back();
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
std::optional<uint8_t> QueryGatewayId(httpd_req_t* req) {
|
||||
const auto raw = QueryValue(req, "gw");
|
||||
if (raw.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
char* end = nullptr;
|
||||
const long parsed = std::strtol(raw.c_str(), &end, 10);
|
||||
if (end == raw.c_str() || *end != '\0' || parsed < 0 || parsed > 255) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return static_cast<uint8_t>(parsed);
|
||||
}
|
||||
|
||||
esp_err_t SendJsonResponse(httpd_req_t* req, const GatewayBridgeHttpResponse& response) {
|
||||
if (response.err == ESP_ERR_INVALID_ARG) {
|
||||
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, response.body.c_str());
|
||||
}
|
||||
if (response.err == ESP_ERR_NOT_FOUND) {
|
||||
return httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, response.body.c_str());
|
||||
}
|
||||
if (response.err != ESP_OK) {
|
||||
return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, response.body.c_str());
|
||||
}
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
return httpd_resp_send(req, response.body.data(), response.body.size());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GatewayNetworkService::GatewayNetworkService(GatewayController& controller,
|
||||
GatewayRuntime& runtime,
|
||||
DaliDomainService& dali_domain,
|
||||
GatewayNetworkServiceConfig config)
|
||||
GatewayNetworkServiceConfig config,
|
||||
GatewayBridgeService* bridge_service)
|
||||
: controller_(controller), runtime_(runtime), dali_domain_(dali_domain), config_(config),
|
||||
udp_lock_(xSemaphoreCreateMutex()) {}
|
||||
bridge_service_(bridge_service), udp_lock_(xSemaphoreCreateMutex()) {}
|
||||
|
||||
esp_err_t GatewayNetworkService::start() {
|
||||
if (started_) {
|
||||
@@ -635,6 +702,8 @@ esp_err_t GatewayNetworkService::startHttpServer() {
|
||||
{"/info", HTTP_GET, &GatewayNetworkService::HandleInfoGet},
|
||||
{"/dali/cmd", HTTP_GET, &GatewayNetworkService::HandleCommandGet},
|
||||
{"/dali/cmd", HTTP_POST, &GatewayNetworkService::HandleCommandPost},
|
||||
{"/bridge", HTTP_GET, &GatewayNetworkService::HandleBridgeGet},
|
||||
{"/bridge", HTTP_POST, &GatewayNetworkService::HandleBridgePost},
|
||||
{"/led/1", HTTP_GET, &GatewayNetworkService::HandleLedOnGet},
|
||||
{"/led/0", HTTP_GET, &GatewayNetworkService::HandleLedOffGet},
|
||||
{"/jq.js", HTTP_GET, &GatewayNetworkService::HandleJqJsGet},
|
||||
@@ -1227,6 +1296,42 @@ esp_err_t GatewayNetworkService::HandleCommandPost(httpd_req_t* req) {
|
||||
return httpd_resp_sendstr(req, "ok");
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::HandleBridgeGet(httpd_req_t* req) {
|
||||
auto* service = static_cast<GatewayNetworkService*>(req->user_ctx);
|
||||
if (service == nullptr) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return service->sendBridgeResponse(req, false);
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::HandleBridgePost(httpd_req_t* req) {
|
||||
auto* service = static_cast<GatewayNetworkService*>(req->user_ctx);
|
||||
if (service == nullptr) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return service->sendBridgeResponse(req, true);
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::sendBridgeResponse(httpd_req_t* req, bool post) {
|
||||
if (bridge_service_ == nullptr) {
|
||||
return httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Bridge service is not enabled");
|
||||
}
|
||||
|
||||
const std::string action = QueryValue(req, "action");
|
||||
const auto gateway_id = QueryGatewayId(req);
|
||||
const int selected_gateway_id = gateway_id.has_value() ? gateway_id.value() : -1;
|
||||
if (!post) {
|
||||
return SendJsonResponse(req, bridge_service_->handleGet(action, selected_gateway_id,
|
||||
QueryString(req)));
|
||||
}
|
||||
|
||||
std::string body;
|
||||
if (ReadRequestBody(req, body) != ESP_OK) {
|
||||
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Bad Request");
|
||||
}
|
||||
return SendJsonResponse(req, bridge_service_->handlePost(action, selected_gateway_id, body));
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::HandleLedOnGet(httpd_req_t* req) {
|
||||
auto* service = static_cast<GatewayNetworkService*>(req->user_ctx);
|
||||
if (service == nullptr) {
|
||||
|
||||
Reference in New Issue
Block a user