Add KNX DALI Gateway Module and Message Queue Implementation
- Introduced KnxDaliModule class for handling DALI message queuing, commissioning, and KNX group-object dispatch. - Implemented Message and MessageQueue classes for managing message operations. - Removed obsolete OpenKNX IDF component files and CMake configurations. - Updated submodule reference for KNX. Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_knx.cpp"
|
||||
SRCS
|
||||
"src/gateway_knx.cpp"
|
||||
"src/ets_device_runtime.cpp"
|
||||
"src/ets_memory_loader.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES dali_cpp esp_driver_gpio esp_driver_uart esp_hw_support esp_netif freertos log lwip openknx_idf
|
||||
REQUIRES dali_cpp esp_driver_gpio esp_driver_uart esp_hw_support esp_netif freertos log lwip knx
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_idf_platform.h"
|
||||
#include "ets_memory_loader.h"
|
||||
|
||||
#include "knx/bau07B0.h"
|
||||
#include "knx/cemi_frame.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gateway::openknx {
|
||||
|
||||
class EtsDeviceRuntime {
|
||||
public:
|
||||
using CemiFrameSender = std::function<void(const uint8_t* data, size_t len)>;
|
||||
using GroupWriteHandler = std::function<void(uint16_t group_address, const uint8_t* data,
|
||||
size_t len)>;
|
||||
using FunctionPropertyHandler = std::function<bool(uint8_t object_index, uint8_t property_id,
|
||||
const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response)>;
|
||||
|
||||
EtsDeviceRuntime(std::string nvs_namespace,
|
||||
uint16_t fallback_individual_address,
|
||||
uint16_t tunnel_client_address = 0);
|
||||
~EtsDeviceRuntime();
|
||||
|
||||
uint16_t individualAddress() const;
|
||||
uint16_t tunnelClientAddress() const;
|
||||
bool configured() const;
|
||||
bool programmingMode() const;
|
||||
void setProgrammingMode(bool enabled);
|
||||
void toggleProgrammingMode();
|
||||
EtsMemorySnapshot snapshot() const;
|
||||
|
||||
void setFunctionPropertyHandlers(FunctionPropertyHandler command_handler,
|
||||
FunctionPropertyHandler state_handler);
|
||||
void setGroupWriteHandler(GroupWriteHandler handler);
|
||||
void setNetworkInterface(esp_netif_t* netif);
|
||||
|
||||
bool handleTunnelFrame(const uint8_t* data, size_t len, CemiFrameSender sender);
|
||||
bool handleBusFrame(const uint8_t* data, size_t len);
|
||||
bool emitGroupValue(uint16_t group_object_number, const uint8_t* data, size_t len,
|
||||
CemiFrameSender sender);
|
||||
void loop();
|
||||
|
||||
private:
|
||||
static bool HandleOutboundCemiFrame(CemiFrame& frame, void* context);
|
||||
static void EmitTunnelFrame(CemiFrame& frame, void* context);
|
||||
static void HandleSecureGroupWrite(uint16_t group_address, const uint8_t* data,
|
||||
uint8_t data_length, void* context);
|
||||
static bool HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
|
||||
uint8_t length, uint8_t* data,
|
||||
uint8_t* result_data, uint8_t& result_length);
|
||||
static bool HandleFunctionPropertyState(uint8_t object_index, uint8_t property_id,
|
||||
uint8_t length, uint8_t* data,
|
||||
uint8_t* result_data, uint8_t& result_length);
|
||||
static uint16_t DefaultTunnelClientAddress(uint16_t individual_address);
|
||||
static bool DispatchFunctionProperty(FunctionPropertyHandler* handler, uint8_t object_index,
|
||||
uint8_t property_id, uint8_t length, uint8_t* data,
|
||||
uint8_t* result_data, uint8_t& result_length);
|
||||
bool shouldConsumeTunnelFrame(CemiFrame& frame) const;
|
||||
bool shouldConsumeBusFrame(CemiFrame& frame) const;
|
||||
|
||||
std::string nvs_namespace_;
|
||||
EspIdfPlatform platform_;
|
||||
Bau07B0 device_;
|
||||
CemiFrameSender sender_;
|
||||
GroupWriteHandler group_write_handler_;
|
||||
FunctionPropertyHandler command_handler_;
|
||||
FunctionPropertyHandler state_handler_;
|
||||
};
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gateway::openknx {
|
||||
|
||||
struct EtsAssociation {
|
||||
uint16_t group_address{0};
|
||||
uint16_t group_object_number{0};
|
||||
};
|
||||
|
||||
struct EtsMemorySnapshot {
|
||||
bool configured{false};
|
||||
uint16_t individual_address{0};
|
||||
std::vector<EtsAssociation> associations;
|
||||
};
|
||||
|
||||
EtsMemorySnapshot LoadEtsMemorySnapshot(const std::string& nvs_namespace);
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
// Internal header shared between gateway_knx.cpp and gateway_knx_router.cpp.
|
||||
|
||||
#include "driver/uart.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "soc/uart_periph.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace gateway {
|
||||
namespace knx_internal {
|
||||
|
||||
constexpr const char* kTag = "gateway_knx";
|
||||
|
||||
// RAII semaphore guard.
|
||||
class SemaphoreGuard {
|
||||
public:
|
||||
explicit SemaphoreGuard(SemaphoreHandle_t semaphore) : semaphore_(semaphore) {
|
||||
if (semaphore_ != nullptr) {
|
||||
xSemaphoreTake(semaphore_, portMAX_DELAY);
|
||||
locked_ = true;
|
||||
}
|
||||
}
|
||||
~SemaphoreGuard() {
|
||||
if (locked_) {
|
||||
xSemaphoreGive(semaphore_);
|
||||
}
|
||||
}
|
||||
private:
|
||||
SemaphoreHandle_t semaphore_{nullptr};
|
||||
bool locked_{false};
|
||||
};
|
||||
|
||||
// Resolve a UART IO pin from config or SoC defaults.
|
||||
inline bool ResolveUartIoPin(uart_port_t uart_port, int configured_pin,
|
||||
uint32_t pin_index, int* resolved_pin) {
|
||||
if (resolved_pin == nullptr) return false;
|
||||
if (configured_pin >= 0) {
|
||||
*resolved_pin = configured_pin;
|
||||
return true;
|
||||
}
|
||||
if (uart_port < 0 || uart_port >= SOC_UART_NUM ||
|
||||
pin_index >= SOC_UART_PINS_COUNT) {
|
||||
*resolved_pin = UART_PIN_NO_CHANGE;
|
||||
return false;
|
||||
}
|
||||
const int default_pin =
|
||||
uart_periph_signal[uart_port].pins[pin_index].default_gpio;
|
||||
if (default_pin < 0) {
|
||||
*resolved_pin = UART_PIN_NO_CHANGE;
|
||||
return false;
|
||||
}
|
||||
*resolved_pin = default_pin;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace knx_internal
|
||||
} // namespace gateway
|
||||
@@ -0,0 +1,348 @@
|
||||
#include "ets_device_runtime.h"
|
||||
|
||||
#include "knx/cemi_server.h"
|
||||
#include "knx/secure_application_layer.h"
|
||||
#include "knx/property.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace gateway::openknx {
|
||||
namespace {
|
||||
|
||||
thread_local EtsDeviceRuntime* active_function_property_runtime = nullptr;
|
||||
|
||||
class ActiveFunctionPropertyRuntimeScope {
|
||||
public:
|
||||
explicit ActiveFunctionPropertyRuntimeScope(EtsDeviceRuntime* runtime)
|
||||
: previous_(active_function_property_runtime) {
|
||||
active_function_property_runtime = runtime;
|
||||
}
|
||||
|
||||
~ActiveFunctionPropertyRuntimeScope() { active_function_property_runtime = previous_; }
|
||||
|
||||
private:
|
||||
EtsDeviceRuntime* previous_;
|
||||
};
|
||||
|
||||
constexpr uint16_t kInvalidIndividualAddress = 0xffff;
|
||||
constexpr uint16_t kReg1DaliManufacturerId = 0x00a4;
|
||||
constexpr uint8_t kReg1DaliApplicationNumber = 0x01;
|
||||
constexpr uint8_t kReg1DaliApplicationVersion = 0x05;
|
||||
constexpr uint8_t kReg1DaliOrderNumber[10] = {'R', 'E', 'G', '1', '-', 'D', 'a', 'l', 'i', 0};
|
||||
|
||||
bool IsUsableIndividualAddress(uint16_t address) {
|
||||
return address != 0 && address != kInvalidIndividualAddress;
|
||||
}
|
||||
|
||||
bool IsErasedMemory(const uint8_t* data, size_t size) {
|
||||
if (data == nullptr || size == 0) {
|
||||
return true;
|
||||
}
|
||||
return std::all_of(data, data + size, [](uint8_t value) { return value == 0xff; });
|
||||
}
|
||||
|
||||
void ApplyReg1DaliIdentity(Bau07B0& device, EspIdfPlatform& platform) {
|
||||
device.deviceObject().manufacturerId(kReg1DaliManufacturerId);
|
||||
device.deviceObject().bauNumber(platform.uniqueSerialNumber());
|
||||
device.deviceObject().orderNumber(kReg1DaliOrderNumber);
|
||||
const uint8_t program_version[5] = {0x00, 0xa4, 0x00, kReg1DaliApplicationNumber,
|
||||
kReg1DaliApplicationVersion};
|
||||
device.parameters().property(PID_PROG_VERSION)->write(program_version);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EtsDeviceRuntime::EtsDeviceRuntime(std::string nvs_namespace,
|
||||
uint16_t fallback_individual_address,
|
||||
uint16_t tunnel_client_address)
|
||||
: nvs_namespace_(std::move(nvs_namespace)),
|
||||
platform_(nullptr, nvs_namespace_.c_str()),
|
||||
device_(platform_) {
|
||||
platform_.outboundCemiFrameCallback(&EtsDeviceRuntime::HandleOutboundCemiFrame, this);
|
||||
ApplyReg1DaliIdentity(device_, platform_);
|
||||
if (IsUsableIndividualAddress(fallback_individual_address)) {
|
||||
device_.deviceObject().individualAddress(fallback_individual_address);
|
||||
}
|
||||
const uint8_t* memory = platform_.getNonVolatileMemoryStart();
|
||||
const size_t memory_size = platform_.getNonVolatileMemorySize();
|
||||
if (!IsErasedMemory(memory, memory_size)) {
|
||||
device_.readMemory();
|
||||
}
|
||||
if (!IsUsableIndividualAddress(device_.deviceObject().individualAddress()) &&
|
||||
IsUsableIndividualAddress(fallback_individual_address)) {
|
||||
device_.deviceObject().individualAddress(fallback_individual_address);
|
||||
}
|
||||
if (auto* server = device_.getCemiServer()) {
|
||||
server->clientAddress(IsUsableIndividualAddress(tunnel_client_address)
|
||||
? tunnel_client_address
|
||||
: DefaultTunnelClientAddress(
|
||||
device_.deviceObject().individualAddress()));
|
||||
server->deviceAddressPropertiesTargetClient(false);
|
||||
server->tunnelFrameCallback(&EtsDeviceRuntime::EmitTunnelFrame, this);
|
||||
}
|
||||
device_.functionPropertyCallback(&EtsDeviceRuntime::HandleFunctionPropertyCommand);
|
||||
device_.functionPropertyStateCallback(&EtsDeviceRuntime::HandleFunctionPropertyState);
|
||||
#ifdef USE_DATASECURE
|
||||
device_.secureGroupWriteCallback(&EtsDeviceRuntime::HandleSecureGroupWrite, this);
|
||||
#endif
|
||||
}
|
||||
|
||||
EtsDeviceRuntime::~EtsDeviceRuntime() {
|
||||
platform_.outboundCemiFrameCallback(nullptr, nullptr);
|
||||
#ifdef USE_DATASECURE
|
||||
device_.secureGroupWriteCallback(nullptr, nullptr);
|
||||
#endif
|
||||
device_.functionPropertyCallback(nullptr);
|
||||
device_.functionPropertyStateCallback(nullptr);
|
||||
if (auto* server = device_.getCemiServer()) {
|
||||
server->tunnelFrameCallback(nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t EtsDeviceRuntime::individualAddress() const {
|
||||
return const_cast<Bau07B0&>(device_).deviceObject().individualAddress();
|
||||
}
|
||||
|
||||
uint16_t EtsDeviceRuntime::tunnelClientAddress() const {
|
||||
if (auto* server = const_cast<Bau07B0&>(device_).getCemiServer()) {
|
||||
return server->clientAddress();
|
||||
}
|
||||
return DefaultTunnelClientAddress(individualAddress());
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::configured() const { return const_cast<Bau07B0&>(device_).configured(); }
|
||||
|
||||
bool EtsDeviceRuntime::programmingMode() const {
|
||||
return const_cast<Bau07B0&>(device_).deviceObject().progMode();
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::setProgrammingMode(bool enabled) {
|
||||
device_.deviceObject().progMode(enabled);
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::toggleProgrammingMode() { setProgrammingMode(!programmingMode()); }
|
||||
|
||||
EtsMemorySnapshot EtsDeviceRuntime::snapshot() const {
|
||||
EtsMemorySnapshot out;
|
||||
auto& device = const_cast<Bau07B0&>(device_);
|
||||
out.configured = device.configured();
|
||||
out.individual_address = device.deviceObject().individualAddress();
|
||||
device.forEachEtsAssociation(
|
||||
[](uint16_t group_address, uint16_t group_object_number, void* context) {
|
||||
auto* associations = static_cast<std::vector<EtsAssociation>*>(context);
|
||||
if (associations != nullptr) {
|
||||
associations->push_back(EtsAssociation{group_address, group_object_number});
|
||||
}
|
||||
},
|
||||
&out.associations);
|
||||
std::sort(out.associations.begin(), out.associations.end(),
|
||||
[](const EtsAssociation& lhs, const EtsAssociation& rhs) {
|
||||
if (lhs.group_address != rhs.group_address) {
|
||||
return lhs.group_address < rhs.group_address;
|
||||
}
|
||||
return lhs.group_object_number < rhs.group_object_number;
|
||||
});
|
||||
out.associations.erase(
|
||||
std::unique(out.associations.begin(), out.associations.end(),
|
||||
[](const EtsAssociation& lhs, const EtsAssociation& rhs) {
|
||||
return lhs.group_address == rhs.group_address &&
|
||||
lhs.group_object_number == rhs.group_object_number;
|
||||
}),
|
||||
out.associations.end());
|
||||
return out;
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::setFunctionPropertyHandlers(FunctionPropertyHandler command_handler,
|
||||
FunctionPropertyHandler state_handler) {
|
||||
command_handler_ = std::move(command_handler);
|
||||
state_handler_ = std::move(state_handler);
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::setGroupWriteHandler(GroupWriteHandler handler) {
|
||||
group_write_handler_ = std::move(handler);
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::setNetworkInterface(esp_netif_t* netif) {
|
||||
platform_.networkInterface(netif);
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::handleTunnelFrame(const uint8_t* data, size_t len,
|
||||
CemiFrameSender sender) {
|
||||
auto* server = device_.getCemiServer();
|
||||
if (server == nullptr || data == nullptr || len < 2) {
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> frame_data(data, data + len);
|
||||
CemiFrame frame(frame_data.data(), static_cast<uint16_t>(frame_data.size()));
|
||||
const bool consumed = shouldConsumeTunnelFrame(frame);
|
||||
if (!consumed) {
|
||||
return false;
|
||||
}
|
||||
sender_ = std::move(sender);
|
||||
ActiveFunctionPropertyRuntimeScope callback_scope(this);
|
||||
server->frameReceived(frame);
|
||||
loop();
|
||||
sender_ = nullptr;
|
||||
return consumed;
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::handleBusFrame(const uint8_t* data, size_t len) {
|
||||
auto* data_link_layer = device_.getDataLinkLayer();
|
||||
if (data_link_layer == nullptr || data == nullptr || len < 2) {
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> frame_data(data, data + len);
|
||||
CemiFrame frame(frame_data.data(), static_cast<uint16_t>(frame_data.size()));
|
||||
const bool consumed = shouldConsumeBusFrame(frame);
|
||||
if (!consumed) {
|
||||
return false;
|
||||
}
|
||||
data_link_layer->externalFrameReceived(frame);
|
||||
loop();
|
||||
return consumed;
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::emitGroupValue(uint16_t group_object_number, const uint8_t* data,
|
||||
size_t len, CemiFrameSender sender) {
|
||||
if (group_object_number == 0 || data == nullptr || !sender || !device_.configured()) {
|
||||
return false;
|
||||
}
|
||||
auto& table = device_.groupObjectTable();
|
||||
if (group_object_number > table.entryCount()) {
|
||||
return false;
|
||||
}
|
||||
auto& group_object = table.get(group_object_number);
|
||||
if (len != group_object.valueSize() || group_object.valueRef() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (group_object.sizeInTelegram() == 0) {
|
||||
group_object.valueRef()[0] = data[0] & 0x01;
|
||||
} else {
|
||||
std::copy_n(data, len, group_object.valueRef());
|
||||
}
|
||||
sender_ = std::move(sender);
|
||||
group_object.objectWritten();
|
||||
loop();
|
||||
sender_ = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::loop() { device_.loop(); }
|
||||
|
||||
bool EtsDeviceRuntime::HandleOutboundCemiFrame(CemiFrame& frame, void* context) {
|
||||
auto* self = static_cast<EtsDeviceRuntime*>(context);
|
||||
if (self == nullptr || !self->sender_) {
|
||||
return false;
|
||||
}
|
||||
self->sender_(frame.data(), frame.dataLength());
|
||||
return true;
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) {
|
||||
auto* self = static_cast<EtsDeviceRuntime*>(context);
|
||||
if (self == nullptr || !self->sender_) {
|
||||
return;
|
||||
}
|
||||
self->sender_(frame.data(), frame.dataLength());
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::HandleSecureGroupWrite(uint16_t group_address, const uint8_t* data,
|
||||
uint8_t data_length, void* context) {
|
||||
auto* self = static_cast<EtsDeviceRuntime*>(context);
|
||||
if (self == nullptr || !self->group_write_handler_) {
|
||||
return;
|
||||
}
|
||||
self->group_write_handler_(group_address, data, data_length);
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
|
||||
uint8_t length, uint8_t* data,
|
||||
uint8_t* result_data,
|
||||
uint8_t& result_length) {
|
||||
if (active_function_property_runtime == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return DispatchFunctionProperty(&active_function_property_runtime->command_handler_, object_index,
|
||||
property_id, length, data, result_data, result_length);
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::HandleFunctionPropertyState(uint8_t object_index, uint8_t property_id,
|
||||
uint8_t length, uint8_t* data,
|
||||
uint8_t* result_data,
|
||||
uint8_t& result_length) {
|
||||
if (active_function_property_runtime == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return DispatchFunctionProperty(&active_function_property_runtime->state_handler_, object_index,
|
||||
property_id, length, data, result_data, result_length);
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::DispatchFunctionProperty(FunctionPropertyHandler* handler,
|
||||
uint8_t object_index, uint8_t property_id,
|
||||
uint8_t length, uint8_t* data,
|
||||
uint8_t* result_data, uint8_t& result_length) {
|
||||
if (handler == nullptr || !*handler || result_data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> response;
|
||||
if (!(*handler)(object_index, property_id, data, length, &response)) {
|
||||
return false;
|
||||
}
|
||||
result_length = static_cast<uint8_t>(std::min<size_t>(response.size(), result_length));
|
||||
if (result_length > 0) {
|
||||
std::copy_n(response.begin(), result_length, result_data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t EtsDeviceRuntime::DefaultTunnelClientAddress(uint16_t individual_address) {
|
||||
if (!IsUsableIndividualAddress(individual_address)) {
|
||||
return 0x1101;
|
||||
}
|
||||
const uint16_t line_base = individual_address & 0xff00;
|
||||
uint16_t device = static_cast<uint16_t>((individual_address & 0x00ff) + 1);
|
||||
if (device == 0 || device > 0xff) {
|
||||
device = 1;
|
||||
}
|
||||
return static_cast<uint16_t>(line_base | device);
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::shouldConsumeTunnelFrame(CemiFrame& frame) const {
|
||||
switch (frame.messageCode()) {
|
||||
case M_PropRead_req:
|
||||
case M_PropWrite_req:
|
||||
case M_Reset_req:
|
||||
case M_FuncPropCommand_req:
|
||||
case M_FuncPropStateRead_req:
|
||||
return true;
|
||||
case L_data_req:
|
||||
if (!const_cast<Bau07B0&>(device_).configured() || programmingMode()) {
|
||||
return true;
|
||||
}
|
||||
if (frame.addressType() == IndividualAddress &&
|
||||
frame.destinationAddress() == individualAddress()) {
|
||||
return true;
|
||||
}
|
||||
#ifdef USE_DATASECURE
|
||||
return frame.addressType() == GroupAddress && frame.apdu().type() == SecureService;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::shouldConsumeBusFrame(CemiFrame& frame) const {
|
||||
#ifdef USE_DATASECURE
|
||||
return frame.messageCode() == L_data_ind && frame.addressType() == GroupAddress &&
|
||||
frame.apdu().type() == SecureService;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "ets_memory_loader.h"
|
||||
|
||||
#include "esp_idf_platform.h"
|
||||
|
||||
#include "knx/bau07B0.h"
|
||||
#include "knx/property.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace gateway::openknx {
|
||||
namespace {
|
||||
|
||||
void CollectAssociation(uint16_t group_address, uint16_t group_object_number,
|
||||
void* context) {
|
||||
auto* associations = static_cast<std::vector<EtsAssociation>*>(context);
|
||||
if (associations == nullptr) {
|
||||
return;
|
||||
}
|
||||
associations->push_back(EtsAssociation{group_address, group_object_number});
|
||||
}
|
||||
|
||||
bool IsErasedMemory(const uint8_t* data, size_t size) {
|
||||
if (data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return std::all_of(data, data + size, [](uint8_t value) { return value == 0xff; });
|
||||
}
|
||||
|
||||
constexpr uint16_t kReg1DaliManufacturerId = 0x00a4;
|
||||
constexpr uint8_t kReg1DaliApplicationNumber = 0x01;
|
||||
constexpr uint8_t kReg1DaliApplicationVersion = 0x05;
|
||||
constexpr uint8_t kReg1DaliOrderNumber[10] = {'R', 'E', 'G', '1', '-', 'D', 'a', 'l', 'i', 0};
|
||||
|
||||
void ApplyReg1DaliIdentity(Bau07B0& device, EspIdfPlatform& platform) {
|
||||
device.deviceObject().manufacturerId(kReg1DaliManufacturerId);
|
||||
device.deviceObject().bauNumber(platform.uniqueSerialNumber());
|
||||
device.deviceObject().orderNumber(kReg1DaliOrderNumber);
|
||||
const uint8_t program_version[5] = {0x00, 0xa4, 0x00, kReg1DaliApplicationNumber,
|
||||
kReg1DaliApplicationVersion};
|
||||
device.parameters().property(PID_PROG_VERSION)->write(program_version);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EtsMemorySnapshot LoadEtsMemorySnapshot(const std::string& nvs_namespace) {
|
||||
EspIdfPlatform platform(nullptr, nvs_namespace.c_str());
|
||||
EtsMemorySnapshot snapshot;
|
||||
const uint8_t* memory = platform.getNonVolatileMemoryStart();
|
||||
const size_t memory_size = platform.getNonVolatileMemorySize();
|
||||
if (memory == nullptr || memory_size == 0 || IsErasedMemory(memory, memory_size)) {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
auto device = std::make_unique<Bau07B0>(platform);
|
||||
ApplyReg1DaliIdentity(*device, platform);
|
||||
device->readMemory();
|
||||
|
||||
snapshot.configured = device->configured();
|
||||
snapshot.individual_address = device->deviceObject().individualAddress();
|
||||
device->forEachEtsAssociation(CollectAssociation, &snapshot.associations);
|
||||
std::sort(snapshot.associations.begin(), snapshot.associations.end(),
|
||||
[](const EtsAssociation& lhs, const EtsAssociation& rhs) {
|
||||
if (lhs.group_address != rhs.group_address) {
|
||||
return lhs.group_address < rhs.group_address;
|
||||
}
|
||||
return lhs.group_object_number < rhs.group_object_number;
|
||||
});
|
||||
snapshot.associations.erase(
|
||||
std::unique(snapshot.associations.begin(), snapshot.associations.end(),
|
||||
[](const EtsAssociation& lhs, const EtsAssociation& rhs) {
|
||||
return lhs.group_address == rhs.group_address &&
|
||||
lhs.group_object_number == rhs.group_object_number;
|
||||
}),
|
||||
snapshot.associations.end());
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "esp_log.h"
|
||||
#include "lwip/inet.h"
|
||||
#include "lwip/sockets.h"
|
||||
#include "openknx_idf/ets_device_runtime.h"
|
||||
#include "ets_device_runtime.h"
|
||||
#include "soc/uart_periph.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
Reference in New Issue
Block a user