8211514fe3
- Added support for OAM router configuration in app_main, allowing the IP interface individual address to be set based on the OAM router's individual address. - Updated GatewayBridgeService to validate IP interface addresses only when OAM router is disabled, ensuring proper address management. - Introduced KnxResponseDeduplicator to prevent duplicate responses in KNX communication. - Enhanced ETS device runtime to handle bus frames and set up frame receivers for OAM router. - Improved GatewayKnxTpIpRouter to manage OAM router interactions, including handling tunnel frames and bus frames. - Updated CMakeLists to include new knx_device_broker source file. - Refined logging messages to provide clearer context regarding the IP interface being used. - Added methods to retrieve IP interface names and friendly names based on the OAM router configuration. Signed-off-by: Tony <tonylu@tony-cloud.com>
980 lines
38 KiB
C++
980 lines
38 KiB
C++
#include "gateway_knx_private.hpp"
|
|
|
|
namespace gateway {
|
|
|
|
GatewayKnxTpIpRouter::GatewayKnxTpIpRouter(GatewayKnxBridge& bridge,
|
|
std::string openknx_namespace)
|
|
: bridge_(bridge),
|
|
openknx_namespace_(std::move(openknx_namespace)) {
|
|
openknx_lock_ = xSemaphoreCreateMutex();
|
|
startup_semaphore_ = xSemaphoreCreateBinary();
|
|
}
|
|
|
|
GatewayKnxTpIpRouter::~GatewayKnxTpIpRouter() {
|
|
stop();
|
|
if (startup_semaphore_ != nullptr) {
|
|
vSemaphoreDelete(startup_semaphore_);
|
|
startup_semaphore_ = nullptr;
|
|
}
|
|
if (openknx_lock_ != nullptr) {
|
|
vSemaphoreDelete(openknx_lock_);
|
|
openknx_lock_ = nullptr;
|
|
}
|
|
}
|
|
|
|
void GatewayKnxTpIpRouter::setConfig(const GatewayKnxConfig& config) { config_ = config; }
|
|
|
|
void GatewayKnxTpIpRouter::setCommissioningOnly(bool enabled) {
|
|
commissioning_only_ = enabled;
|
|
}
|
|
|
|
void GatewayKnxTpIpRouter::setGroupWriteHandler(GroupWriteHandler handler) {
|
|
group_write_handler_ = std::move(handler);
|
|
}
|
|
|
|
void GatewayKnxTpIpRouter::setGroupObjectWriteHandler(GroupObjectWriteHandler handler) {
|
|
group_object_write_handler_ = std::move(handler);
|
|
}
|
|
|
|
void GatewayKnxTpIpRouter::setOamIpSecureCredentials(
|
|
const GatewayKnxIpSecureCredentialMaterial& credentials) {
|
|
oam_ip_secure_credentials_ = credentials;
|
|
}
|
|
|
|
void GatewayKnxTpIpRouter::setOamIpSecureRoutingSequenceStoreHandler(
|
|
RoutingSequenceStoreHandler handler) {
|
|
routing_sequence_store_handler_ = std::move(handler);
|
|
}
|
|
|
|
void GatewayKnxTpIpRouter::setCloudCemiPublisher(CloudCemiPublisher publisher) {
|
|
cloud_cemi_publisher_ = std::move(publisher);
|
|
}
|
|
|
|
const GatewayKnxConfig& GatewayKnxTpIpRouter::config() const { return config_; }
|
|
|
|
bool GatewayKnxTpIpRouter::injectCloudCemiFrame(const uint8_t* data, size_t len) {
|
|
if (data == nullptr || len == 0 || !config_.oam_router.cloud_remote.enabled) {
|
|
return false;
|
|
}
|
|
cloud_cemi_downlink_frames_.fetch_add(1, std::memory_order_relaxed);
|
|
return handleOpenKnxTunnelFrame(data, len, nullptr, kServiceTunnellingRequest);
|
|
}
|
|
|
|
GatewayKnxTpIpRouter::CloudCemiStats GatewayKnxTpIpRouter::cloudCemiStats() const {
|
|
return CloudCemiStats{
|
|
config_.oam_router.cloud_remote.enabled,
|
|
cloud_cemi_uplink_frames_.load(std::memory_order_relaxed),
|
|
cloud_cemi_downlink_frames_.load(std::memory_order_relaxed)};
|
|
}
|
|
|
|
bool GatewayKnxTpIpRouter::tpUartOnline() const { return tp_uart_online_; }
|
|
|
|
bool GatewayKnxTpIpRouter::programmingMode() {
|
|
if (openknx_lock_ == nullptr) {
|
|
return false;
|
|
}
|
|
SemaphoreGuard guard(openknx_lock_);
|
|
return ets_device_ != nullptr && ets_device_->programmingMode();
|
|
}
|
|
|
|
esp_err_t GatewayKnxTpIpRouter::setProgrammingMode(bool enabled) {
|
|
if (openknx_lock_ == nullptr) {
|
|
last_error_ = "KNX runtime lock is unavailable";
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
SemaphoreGuard guard(openknx_lock_);
|
|
if (ets_device_ == nullptr) {
|
|
last_error_ = "KNX OpenKNX runtime is unavailable";
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
ets_device_->setProgrammingMode(enabled);
|
|
setProgrammingLed(enabled);
|
|
ESP_LOGI(kTag, "KNX programming mode %s namespace=%s",
|
|
enabled ? "enabled" : "disabled", openknx_namespace_.c_str());
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t GatewayKnxTpIpRouter::toggleProgrammingMode() {
|
|
return setProgrammingMode(!programmingMode());
|
|
}
|
|
|
|
bool GatewayKnxTpIpRouter::oamProgrammingMode() {
|
|
if (openknx_lock_ == nullptr) {
|
|
return false;
|
|
}
|
|
SemaphoreGuard guard(openknx_lock_);
|
|
return oam_router_ != nullptr ? oam_router_->programmingMode() : oam_programming_mode_;
|
|
}
|
|
|
|
esp_err_t GatewayKnxTpIpRouter::setOamProgrammingMode(bool enabled) {
|
|
if (openknx_lock_ == nullptr) {
|
|
last_error_ = "KNX runtime lock is unavailable";
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
if (!config_.oam_router.enabled) {
|
|
last_error_ = "OAM KNX/IP router persona is disabled";
|
|
return ESP_ERR_NOT_SUPPORTED;
|
|
}
|
|
SemaphoreGuard guard(openknx_lock_);
|
|
oam_programming_mode_ = enabled;
|
|
if (oam_router_ != nullptr) {
|
|
oam_router_->setProgrammingMode(enabled);
|
|
}
|
|
setOamProgrammingLed(enabled);
|
|
ESP_LOGI(kTag, "OAM KNX/IP router programming mode %s namespace=%s",
|
|
enabled ? "enabled" : "disabled", openknx_namespace_.c_str());
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t GatewayKnxTpIpRouter::toggleOamProgrammingMode() {
|
|
return setOamProgrammingMode(!oamProgrammingMode());
|
|
}
|
|
|
|
esp_err_t GatewayKnxTpIpRouter::start(uint32_t task_stack_size, UBaseType_t task_priority) {
|
|
if (started_ || task_handle_ != nullptr) {
|
|
return ESP_OK;
|
|
}
|
|
if (openknx_lock_ == nullptr || startup_semaphore_ == nullptr) {
|
|
last_error_ = "failed to allocate KNX runtime synchronization primitives";
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
if (!config_.ip_router_enabled) {
|
|
last_error_ = "KNXnet/IP router is disabled in config";
|
|
return ESP_ERR_NOT_SUPPORTED;
|
|
}
|
|
stop_requested_ = false;
|
|
last_error_.clear();
|
|
int log_tp_uart_tx_pin = -1;
|
|
int log_tp_uart_rx_pin = -1;
|
|
if (config_.tp_uart.uart_port >= 0 && config_.tp_uart.uart_port < SOC_UART_NUM) {
|
|
const uart_port_t log_uart_port = static_cast<uart_port_t>(config_.tp_uart.uart_port);
|
|
ResolveUartIoPin(log_uart_port, config_.tp_uart.tx_pin, SOC_UART_TX_PIN_IDX,
|
|
&log_tp_uart_tx_pin);
|
|
ResolveUartIoPin(log_uart_port, config_.tp_uart.rx_pin, SOC_UART_RX_PIN_IDX,
|
|
&log_tp_uart_rx_pin);
|
|
}
|
|
ESP_LOGI(kTag,
|
|
"starting KNXnet/IP router ipInterface=%s runtimeNamespace=%s udp=%u tunnel=%d multicast=%d group=%s "
|
|
"tpUart=%d tx=%s rx=%s nineBit=%d commissioningOnly=%d",
|
|
ipInterfaceName(), openknx_namespace_.c_str(), static_cast<unsigned>(config_.udp_port),
|
|
config_.tunnel_enabled, config_.multicast_enabled,
|
|
config_.multicast_address.c_str(), config_.tp_uart.uart_port,
|
|
UartPinDescription(config_.tp_uart.tx_pin, log_tp_uart_tx_pin).c_str(),
|
|
UartPinDescription(config_.tp_uart.rx_pin, log_tp_uart_rx_pin).c_str(),
|
|
config_.tp_uart.nine_bit_mode, commissioning_only_);
|
|
if (!configureSocket()) {
|
|
return ESP_FAIL;
|
|
}
|
|
while (xSemaphoreTake(startup_semaphore_, 0) == pdTRUE) {
|
|
}
|
|
startup_result_ = ESP_ERR_TIMEOUT;
|
|
const BaseType_t created = xTaskCreate(&GatewayKnxTpIpRouter::TaskEntry, "gw_knx_ip",
|
|
task_stack_size, this, task_priority, &task_handle_);
|
|
if (created != pdPASS) {
|
|
task_handle_ = nullptr;
|
|
closeSockets();
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
if (xSemaphoreTake(startup_semaphore_, pdMS_TO_TICKS(10000)) != pdTRUE) {
|
|
last_error_ = "timed out starting KNXnet/IP OpenKNX runtime";
|
|
stop_requested_ = true;
|
|
closeSockets();
|
|
return ESP_ERR_TIMEOUT;
|
|
}
|
|
return startup_result_;
|
|
}
|
|
|
|
esp_err_t GatewayKnxTpIpRouter::stop() {
|
|
stop_requested_ = true;
|
|
closeSockets();
|
|
const TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
|
|
for (int attempt = 0; task_handle_ != nullptr && task_handle_ != current_task && attempt < 50;
|
|
++attempt) {
|
|
vTaskDelay(pdMS_TO_TICKS(10));
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
bool GatewayKnxTpIpRouter::started() const { return started_; }
|
|
|
|
const std::string& GatewayKnxTpIpRouter::lastError() const { return last_error_; }
|
|
|
|
bool GatewayKnxTpIpRouter::publishDaliStatus(const GatewayKnxDaliTarget& target,
|
|
uint8_t actual_level) {
|
|
if (!started_ || !config_.ip_router_enabled || !shouldRouteDaliApplicationFrames()) {
|
|
return false;
|
|
}
|
|
uint16_t switch_object = 0;
|
|
uint16_t dimm_object = 0;
|
|
if (target.kind == GatewayKnxDaliTargetKind::kShortAddress) {
|
|
if (target.address < 0 || target.address > 63) {
|
|
return false;
|
|
}
|
|
const uint16_t base = kGwReg1AdrKoOffset +
|
|
kGwReg1AdrKoBlockSize * static_cast<uint16_t>(target.address);
|
|
switch_object = base + kGwReg1KoSwitchState;
|
|
dimm_object = base + kGwReg1KoDimmState;
|
|
} else if (target.kind == GatewayKnxDaliTargetKind::kGroup) {
|
|
if (target.address < 0 || target.address > 15) {
|
|
return false;
|
|
}
|
|
const uint16_t base = kGwReg1GrpKoOffset +
|
|
kGwReg1GrpKoBlockSize * static_cast<uint16_t>(target.address);
|
|
switch_object = base + kGwReg1KoSwitchState;
|
|
dimm_object = base + kGwReg1KoDimmState;
|
|
} else if (target.kind == GatewayKnxDaliTargetKind::kBroadcast) {
|
|
switch_object = kGwReg1AppKoBroadcastSwitch;
|
|
dimm_object = kGwReg1AppKoBroadcastDimm;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
const uint8_t switch_value = actual_level > 0 ? 1 : 0;
|
|
const uint8_t dimm_value = DaliArcLevelToDpt5(actual_level);
|
|
bool emitted = emitOpenKnxGroupValue(switch_object, &switch_value, 1);
|
|
emitted = emitOpenKnxGroupValue(dimm_object, &dimm_value, 1) || emitted;
|
|
return emitted;
|
|
}
|
|
|
|
void GatewayKnxTpIpRouter::TaskEntry(void* arg) {
|
|
static_cast<GatewayKnxTpIpRouter*>(arg)->taskLoop();
|
|
}
|
|
|
|
esp_err_t GatewayKnxTpIpRouter::initializeRuntime() {
|
|
{
|
|
SemaphoreGuard guard(openknx_lock_);
|
|
auto tp_uart_interface = createOpenKnxTpUartInterface();
|
|
if (GatewayKnxConfigUsesTpUart(config_) && tp_uart_interface == nullptr && !last_error_.empty()) {
|
|
return ESP_FAIL;
|
|
}
|
|
ets_device_ = std::make_unique<openknx::EtsDeviceRuntime>(openknx_namespace_,
|
|
config_.individual_address,
|
|
effectiveTunnelAddress(),
|
|
std::move(tp_uart_interface));
|
|
bridge_.setRuntimeContext(ets_device_.get());
|
|
knx_ip_parameters_ = std::make_unique<IpParameterObject>(
|
|
ets_device_->deviceObject(), ets_device_->platform());
|
|
if (config_.oam_router.enabled) {
|
|
oam_router_ = std::make_unique<openknx::OamRouterRuntime>(
|
|
openknx_namespace_ + "_oam", config_.oam_router.individual_address,
|
|
config_.oam_router.tunnel_address_base);
|
|
if (oam_router_->available()) {
|
|
oam_router_->setProgrammingMode(oam_programming_mode_);
|
|
} else {
|
|
ESP_LOGW(kTag, "OAM router persona requested but BAU091A support is not compiled in");
|
|
oam_router_.reset();
|
|
}
|
|
} else {
|
|
oam_router_.reset();
|
|
}
|
|
openknx_configured_.store(ets_device_->configured());
|
|
ESP_LOGI(kTag,
|
|
"OpenKNX runtime namespace=%s configured=%d ipInterface=0x%04x "
|
|
"device=0x%04x tunnelClient=0x%04x commissioningOnly=%d",
|
|
openknx_namespace_.c_str(), ets_device_->configured(),
|
|
effectiveIpInterfaceIndividualAddress(), ets_device_->individualAddress(),
|
|
ets_device_->tunnelClientAddress(), commissioning_only_);
|
|
if (oam_router_ != nullptr) {
|
|
ESP_LOGI(kTag,
|
|
"OAM router persona namespace=%s_oam configured=%d device=0x%04x tunnelClient=0x%04x secureTunnel=%d secureRouting=%d",
|
|
openknx_namespace_.c_str(), oam_router_->configured(),
|
|
oam_router_->individualAddress(), oam_router_->tunnelClientAddress(),
|
|
config_.oam_router.secure_tunnel_enabled,
|
|
config_.oam_router.secure_routing_enabled);
|
|
}
|
|
ets_device_->setFunctionPropertyHandlers(
|
|
[this](uint8_t object_index, uint8_t property_id, const uint8_t* data, size_t len,
|
|
std::vector<uint8_t>* response) {
|
|
if (!shouldRouteDaliApplicationFrames()) {
|
|
return false;
|
|
}
|
|
return bridge_.handleFunctionPropertyCommand(object_index, property_id, data, len,
|
|
response);
|
|
},
|
|
[this](uint8_t object_index, uint8_t property_id, const uint8_t* data, size_t len,
|
|
std::vector<uint8_t>* response) {
|
|
if (!shouldRouteDaliApplicationFrames()) {
|
|
return false;
|
|
}
|
|
return bridge_.handleFunctionPropertyState(object_index, property_id, data, len,
|
|
response);
|
|
});
|
|
ets_device_->setFunctionPropertyExtHandlers(
|
|
[this](uint16_t object_type, uint8_t object_instance, uint8_t property_id,
|
|
const uint8_t* data, size_t len, std::vector<uint8_t>* response) {
|
|
return handleFunctionPropertyExtCommand(object_type, object_instance, property_id,
|
|
data, len, response);
|
|
},
|
|
[this](uint16_t object_type, uint8_t object_instance, uint8_t property_id,
|
|
const uint8_t* data, size_t len, std::vector<uint8_t>* response) {
|
|
return handleFunctionPropertyExtState(object_type, object_instance, property_id,
|
|
data, len, response);
|
|
});
|
|
ets_device_->setGroupWriteHandler(
|
|
[this](uint16_t group_address, const uint8_t* data, size_t len) {
|
|
if (!shouldRouteDaliApplicationFrames()) {
|
|
return;
|
|
}
|
|
const DaliBridgeResult result = group_write_handler_
|
|
? group_write_handler_(group_address, data, len)
|
|
: bridge_.handleGroupWrite(group_address, data, len);
|
|
if (!result.ok && !result.error.empty()) {
|
|
ESP_LOGD(kTag, "secure KNX group write not routed to DALI: %s", result.error.c_str());
|
|
}
|
|
});
|
|
ets_device_->setGroupObjectWriteHandler(
|
|
[this](uint16_t group_object_number, const uint8_t* data, size_t len) {
|
|
if (!shouldRouteDaliApplicationFrames()) {
|
|
return IgnoredResult(
|
|
GwReg1GroupAddressForObject(config_.main_group, group_object_number),
|
|
group_object_number,
|
|
"routing blocked by commissioning-only state");
|
|
}
|
|
const DaliBridgeResult result = group_object_write_handler_
|
|
? group_object_write_handler_(group_object_number,
|
|
data, len)
|
|
: bridge_.handleGroupObjectWrite(group_object_number,
|
|
data, len);
|
|
const bool ignored = getObjectBool(result.metadata, "ignored").value_or(false);
|
|
if (ignored) {
|
|
const auto reason = getObjectString(result.metadata, "reason").value_or("ignored");
|
|
ESP_LOGW(kTag, "OpenKNX group object %u accepted by ETS but ignored: %s",
|
|
static_cast<unsigned>(group_object_number), reason.c_str());
|
|
} else if (!result.ok && !result.error.empty()) {
|
|
ESP_LOGW(kTag, "OpenKNX group object %u not routed to DALI: %s",
|
|
static_cast<unsigned>(group_object_number), result.error.c_str());
|
|
}
|
|
return result;
|
|
});
|
|
ets_device_->setBusFrameSender([this](const uint8_t* data, size_t len) {
|
|
publishCloudCemiFrame(data, len);
|
|
sendTunnelIndication(data, len);
|
|
sendRoutingIndication(data, len);
|
|
});
|
|
ets_device_->setTpFrameReceiver([this](const uint8_t* data, size_t len) {
|
|
return handleOpenKnxTpIngressFrame(data, len);
|
|
});
|
|
if (oam_router_ != nullptr) {
|
|
oam_router_->setBusFrameSender([this](const uint8_t* data, size_t len) {
|
|
publishCloudCemiFrame(data, len);
|
|
sendTunnelIndication(data, len);
|
|
sendRoutingIndication(data, len);
|
|
});
|
|
}
|
|
syncOpenKnxConfigFromDevice();
|
|
}
|
|
if (!configureTpUart()) {
|
|
last_error_ = last_error_.empty() ? "failed to configure KNX TP-UART" : last_error_;
|
|
return ESP_FAIL;
|
|
}
|
|
if (!configureProgrammingGpio()) {
|
|
last_error_ = last_error_.empty() ? "failed to configure KNX programming GPIO" : last_error_;
|
|
return ESP_FAIL;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
void GatewayKnxTpIpRouter::taskLoop() {
|
|
startup_result_ = initializeRuntime();
|
|
if (startup_result_ == ESP_OK) {
|
|
started_ = true;
|
|
}
|
|
if (startup_semaphore_ != nullptr) {
|
|
xSemaphoreGive(startup_semaphore_);
|
|
}
|
|
if (startup_result_ != ESP_OK || stop_requested_) {
|
|
finishTask();
|
|
return;
|
|
}
|
|
std::array<uint8_t, 768> buffer{};
|
|
auto run_maintenance = [this]() {
|
|
{
|
|
SemaphoreGuard guard(openknx_lock_);
|
|
if (ets_device_ != nullptr) {
|
|
pollProgrammingButton();
|
|
ets_device_->loop();
|
|
if (oam_router_ != nullptr) {
|
|
oam_router_->loop();
|
|
oam_programming_mode_ = oam_router_->programmingMode();
|
|
}
|
|
tp_uart_online_ = ets_device_->tpUartOnline();
|
|
updateProgrammingLed();
|
|
}
|
|
}
|
|
};
|
|
while (!stop_requested_) {
|
|
const TickType_t now = xTaskGetTickCount();
|
|
if (network_refresh_tick_ == 0 ||
|
|
now - network_refresh_tick_ >= pdMS_TO_TICKS(1000)) {
|
|
refreshNetworkInterfaces(false);
|
|
pruneStaleTunnelClients();
|
|
network_refresh_tick_ = now;
|
|
}
|
|
|
|
fd_set read_fds;
|
|
FD_ZERO(&read_fds);
|
|
int max_fd = -1;
|
|
if (udp_sock_ >= 0) {
|
|
FD_SET(udp_sock_, &read_fds);
|
|
max_fd = std::max(max_fd, udp_sock_);
|
|
}
|
|
if (tcp_sock_ >= 0) {
|
|
FD_SET(tcp_sock_, &read_fds);
|
|
max_fd = std::max(max_fd, tcp_sock_);
|
|
}
|
|
for (const auto& client : tcp_clients_) {
|
|
if (client.sock >= 0) {
|
|
FD_SET(client.sock, &read_fds);
|
|
max_fd = std::max(max_fd, client.sock);
|
|
}
|
|
}
|
|
|
|
timeval timeout{};
|
|
timeout.tv_sec = 0;
|
|
timeout.tv_usec = 20000;
|
|
const int selected = max_fd >= 0 ? select(max_fd + 1, &read_fds, nullptr, nullptr, &timeout)
|
|
: 0;
|
|
if (selected < 0) {
|
|
ESP_LOGW(kTag, "KNXnet/IP socket select failed: errno=%d (%s)", errno,
|
|
std::strerror(errno));
|
|
run_maintenance();
|
|
vTaskDelay(pdMS_TO_TICKS(10));
|
|
continue;
|
|
}
|
|
if (selected == 0) {
|
|
run_maintenance();
|
|
continue;
|
|
}
|
|
|
|
if (tcp_sock_ >= 0 && FD_ISSET(tcp_sock_, &read_fds)) {
|
|
handleTcpAccept();
|
|
}
|
|
for (auto& client : tcp_clients_) {
|
|
if (client.sock >= 0 && FD_ISSET(client.sock, &read_fds)) {
|
|
handleTcpClient(client);
|
|
}
|
|
}
|
|
sockaddr_in remote{};
|
|
socklen_t remote_len = sizeof(remote);
|
|
if (udp_sock_ >= 0 && FD_ISSET(udp_sock_, &read_fds)) {
|
|
const int received = recvfrom(udp_sock_, buffer.data(), buffer.size(), 0,
|
|
reinterpret_cast<sockaddr*>(&remote), &remote_len);
|
|
if (received > 0) {
|
|
handleUdpDatagram(buffer.data(), static_cast<size_t>(received), remote);
|
|
}
|
|
}
|
|
run_maintenance();
|
|
}
|
|
finishTask();
|
|
}
|
|
|
|
void GatewayKnxTpIpRouter::finishTask() {
|
|
closeSockets();
|
|
{
|
|
SemaphoreGuard guard(openknx_lock_);
|
|
setProgrammingLed(false);
|
|
setOamProgrammingLed(false);
|
|
oam_programming_mode_ = false;
|
|
knx_ip_parameters_.reset();
|
|
bridge_.setRuntimeContext(nullptr);
|
|
oam_router_.reset();
|
|
ets_device_.reset();
|
|
openknx_configured_.store(false);
|
|
}
|
|
started_ = false;
|
|
task_handle_ = nullptr;
|
|
vTaskDelete(nullptr);
|
|
}
|
|
|
|
void GatewayKnxTpIpRouter::pollProgrammingButton() {
|
|
const TickType_t now = xTaskGetTickCount();
|
|
|
|
if (config_.programming_button_gpio >= 0 && ets_device_ != nullptr) {
|
|
const int level = gpio_get_level(static_cast<gpio_num_t>(config_.programming_button_gpio));
|
|
const bool pressed = config_.programming_button_active_low ? level == 0 : level != 0;
|
|
if (pressed && !programming_button_last_pressed_ &&
|
|
now - programming_button_last_toggle_tick_ >= pdMS_TO_TICKS(200)) {
|
|
ets_device_->toggleProgrammingMode();
|
|
ESP_LOGI(kTag, "KNX programming mode %s namespace=%s",
|
|
ets_device_->programmingMode() ? "enabled" : "disabled",
|
|
openknx_namespace_.c_str());
|
|
programming_button_last_toggle_tick_ = now;
|
|
}
|
|
programming_button_last_pressed_ = pressed;
|
|
}
|
|
|
|
if (!config_.oam_router.enabled || config_.oam_router.programming_button_gpio < 0) {
|
|
return;
|
|
}
|
|
const int oam_level = gpio_get_level(
|
|
static_cast<gpio_num_t>(config_.oam_router.programming_button_gpio));
|
|
const bool oam_pressed = config_.oam_router.programming_button_active_low
|
|
? oam_level == 0
|
|
: oam_level != 0;
|
|
if (oam_pressed && !oam_programming_button_last_pressed_ &&
|
|
now - oam_programming_button_last_toggle_tick_ >= pdMS_TO_TICKS(200)) {
|
|
oam_programming_mode_ = !oam_programming_mode_;
|
|
if (oam_router_ != nullptr) {
|
|
oam_router_->setProgrammingMode(oam_programming_mode_);
|
|
}
|
|
setOamProgrammingLed(oam_programming_mode_);
|
|
ESP_LOGI(kTag, "OAM KNX/IP router programming mode %s namespace=%s",
|
|
oam_programming_mode_ ? "enabled" : "disabled",
|
|
openknx_namespace_.c_str());
|
|
oam_programming_button_last_toggle_tick_ = now;
|
|
}
|
|
oam_programming_button_last_pressed_ = oam_pressed;
|
|
}
|
|
|
|
void GatewayKnxTpIpRouter::updateProgrammingLed() {
|
|
if (config_.programming_led_gpio >= 0 && ets_device_ != nullptr) {
|
|
const bool programming_mode = ets_device_->programmingMode();
|
|
if (programming_mode != programming_led_state_) {
|
|
setProgrammingLed(programming_mode);
|
|
}
|
|
}
|
|
|
|
if (config_.oam_router.enabled && config_.oam_router.programming_led_gpio >= 0 &&
|
|
oam_programming_mode_ != oam_programming_led_state_) {
|
|
setOamProgrammingLed(oam_programming_mode_);
|
|
}
|
|
}
|
|
|
|
void GatewayKnxTpIpRouter::setProgrammingLed(bool on) {
|
|
if (config_.programming_led_gpio < 0) {
|
|
programming_led_state_ = on;
|
|
return;
|
|
}
|
|
const bool level = config_.programming_led_active_high ? on : !on;
|
|
gpio_set_level(static_cast<gpio_num_t>(config_.programming_led_gpio), level ? 1 : 0);
|
|
programming_led_state_ = on;
|
|
}
|
|
|
|
void GatewayKnxTpIpRouter::setOamProgrammingLed(bool on) {
|
|
if (config_.oam_router.programming_led_gpio < 0) {
|
|
oam_programming_led_state_ = on;
|
|
return;
|
|
}
|
|
const bool level = config_.oam_router.programming_led_active_high ? on : !on;
|
|
gpio_set_level(static_cast<gpio_num_t>(config_.oam_router.programming_led_gpio),
|
|
level ? 1 : 0);
|
|
oam_programming_led_state_ = on;
|
|
}
|
|
|
|
void GatewayKnxTpIpRouter::closeSockets() {
|
|
if (udp_sock_ >= 0) {
|
|
shutdown(udp_sock_, SHUT_RDWR);
|
|
close(udp_sock_);
|
|
udp_sock_ = -1;
|
|
}
|
|
if (tcp_sock_ >= 0) {
|
|
shutdown(tcp_sock_, SHUT_RDWR);
|
|
close(tcp_sock_);
|
|
tcp_sock_ = -1;
|
|
}
|
|
for (auto& client : tcp_clients_) {
|
|
closeTcpClient(client);
|
|
}
|
|
active_tcp_sock_ = -1;
|
|
multicast_joined_interfaces_.clear();
|
|
network_refresh_tick_ = 0;
|
|
for (auto& client : tunnel_clients_) {
|
|
resetTunnelClient(client);
|
|
}
|
|
last_tunnel_channel_id_ = 0;
|
|
}
|
|
|
|
bool GatewayKnxTpIpRouter::configureSocket() {
|
|
udp_sock_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (udp_sock_ < 0) {
|
|
last_error_ = ErrnoDetail("failed to create KNXnet/IP UDP socket", errno);
|
|
ESP_LOGE(kTag, "%s", last_error_.c_str());
|
|
return false;
|
|
}
|
|
int broadcast = 1;
|
|
if (setsockopt(udp_sock_, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast)) < 0) {
|
|
ESP_LOGW(kTag, "failed to enable broadcast for KNX UDP port %u: errno=%d (%s)",
|
|
static_cast<unsigned>(config_.udp_port), errno, std::strerror(errno));
|
|
}
|
|
|
|
sockaddr_in bind_addr{};
|
|
bind_addr.sin_family = AF_INET;
|
|
bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
bind_addr.sin_port = htons(config_.udp_port);
|
|
if (bind(udp_sock_, reinterpret_cast<sockaddr*>(&bind_addr), sizeof(bind_addr)) < 0) {
|
|
const int saved_errno = errno;
|
|
last_error_ = ErrnoDetail("failed to bind KNXnet/IP UDP socket on port " +
|
|
std::to_string(config_.udp_port),
|
|
saved_errno);
|
|
ESP_LOGE(kTag, "%s", last_error_.c_str());
|
|
closeSockets();
|
|
return false;
|
|
}
|
|
|
|
timeval timeout{};
|
|
timeout.tv_sec = 0;
|
|
timeout.tv_usec = 20000;
|
|
if (setsockopt(udp_sock_, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
|
|
ESP_LOGW(kTag, "failed to set KNX UDP receive timeout on port %u: errno=%d (%s)",
|
|
static_cast<unsigned>(config_.udp_port), errno, std::strerror(errno));
|
|
}
|
|
|
|
if (config_.multicast_enabled) {
|
|
uint8_t multicast_loop = 0;
|
|
if (setsockopt(udp_sock_, IPPROTO_IP, IP_MULTICAST_LOOP, &multicast_loop,
|
|
sizeof(multicast_loop)) < 0) {
|
|
ESP_LOGW(kTag, "failed to disable KNX multicast loopback for %s: errno=%d (%s)",
|
|
config_.multicast_address.c_str(), errno, std::strerror(errno));
|
|
}
|
|
refreshNetworkInterfaces(true);
|
|
}
|
|
|
|
tcp_sock_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (tcp_sock_ < 0) {
|
|
last_error_ = ErrnoDetail("failed to create KNXnet/IP TCP socket", errno);
|
|
ESP_LOGE(kTag, "%s", last_error_.c_str());
|
|
closeSockets();
|
|
return false;
|
|
}
|
|
int reuse = 1;
|
|
if (setsockopt(tcp_sock_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
|
|
ESP_LOGW(kTag, "failed to enable TCP reuse for KNX port %u: errno=%d (%s)",
|
|
static_cast<unsigned>(config_.udp_port), errno, std::strerror(errno));
|
|
}
|
|
sockaddr_in tcp_bind_addr{};
|
|
tcp_bind_addr.sin_family = AF_INET;
|
|
tcp_bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
tcp_bind_addr.sin_port = htons(config_.udp_port);
|
|
if (bind(tcp_sock_, reinterpret_cast<sockaddr*>(&tcp_bind_addr), sizeof(tcp_bind_addr)) < 0) {
|
|
const int saved_errno = errno;
|
|
last_error_ = ErrnoDetail("failed to bind KNXnet/IP TCP socket on port " +
|
|
std::to_string(config_.udp_port),
|
|
saved_errno);
|
|
ESP_LOGE(kTag, "%s", last_error_.c_str());
|
|
closeSockets();
|
|
return false;
|
|
}
|
|
if (listen(tcp_sock_, static_cast<int>(kMaxTcpClients)) < 0) {
|
|
const int saved_errno = errno;
|
|
last_error_ = ErrnoDetail("failed to listen on KNXnet/IP TCP port " +
|
|
std::to_string(config_.udp_port),
|
|
saved_errno);
|
|
ESP_LOGE(kTag, "%s", last_error_.c_str());
|
|
closeSockets();
|
|
return false;
|
|
}
|
|
ESP_LOGI(kTag, "KNXnet/IP listening on UDP/TCP port %u",
|
|
static_cast<unsigned>(config_.udp_port));
|
|
return true;
|
|
}
|
|
|
|
void GatewayKnxTpIpRouter::handleTcpAccept() {
|
|
sockaddr_in remote{};
|
|
socklen_t remote_len = sizeof(remote);
|
|
const int client_sock = accept(tcp_sock_, reinterpret_cast<sockaddr*>(&remote), &remote_len);
|
|
if (client_sock < 0) {
|
|
ESP_LOGW(kTag, "failed to accept KNXnet/IP TCP client: errno=%d (%s)", errno,
|
|
std::strerror(errno));
|
|
return;
|
|
}
|
|
TcpClient* slot = nullptr;
|
|
for (auto& client : tcp_clients_) {
|
|
if (client.sock < 0) {
|
|
slot = &client;
|
|
break;
|
|
}
|
|
}
|
|
if (slot == nullptr) {
|
|
ESP_LOGW(kTag, "reject KNXnet/IP TCP client from %s: no free TCP slots",
|
|
EndpointString(remote).c_str());
|
|
close(client_sock);
|
|
return;
|
|
}
|
|
slot->sock = client_sock;
|
|
slot->remote = remote;
|
|
slot->rx_buffer.clear();
|
|
slot->last_activity_tick = xTaskGetTickCount();
|
|
ESP_LOGI(kTag, "accepted KNXnet/IP TCP client from %s", EndpointString(remote).c_str());
|
|
}
|
|
|
|
void GatewayKnxTpIpRouter::handleTcpClient(TcpClient& client) {
|
|
if (client.sock < 0) {
|
|
return;
|
|
}
|
|
std::array<uint8_t, 512> buffer{};
|
|
const int received = recv(client.sock, buffer.data(), buffer.size(), 0);
|
|
if (received <= 0) {
|
|
ESP_LOGI(kTag, "closed KNXnet/IP TCP client from %s", EndpointString(client.remote).c_str());
|
|
closeTcpClient(client);
|
|
return;
|
|
}
|
|
client.last_activity_tick = xTaskGetTickCount();
|
|
client.rx_buffer.insert(client.rx_buffer.end(), buffer.begin(), buffer.begin() + received);
|
|
while (client.rx_buffer.size() >= 6) {
|
|
uint16_t service = 0;
|
|
uint16_t total_len = 0;
|
|
if (!ParseKnxNetIpHeader(client.rx_buffer.data(), client.rx_buffer.size(), &service,
|
|
&total_len)) {
|
|
ESP_LOGW(kTag, "invalid KNXnet/IP TCP packet from %s; closing stream",
|
|
EndpointString(client.remote).c_str());
|
|
closeTcpClient(client);
|
|
return;
|
|
}
|
|
if (client.rx_buffer.size() < total_len) {
|
|
return;
|
|
}
|
|
std::vector<uint8_t> packet(client.rx_buffer.begin(), client.rx_buffer.begin() + total_len);
|
|
client.rx_buffer.erase(client.rx_buffer.begin(), client.rx_buffer.begin() + total_len);
|
|
active_tcp_sock_ = client.sock;
|
|
handleUdpDatagram(packet.data(), packet.size(), client.remote);
|
|
active_tcp_sock_ = -1;
|
|
if (client.sock < 0) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GatewayKnxTpIpRouter::closeTcpClient(TcpClient& client) {
|
|
if (client.sock < 0) {
|
|
client.rx_buffer.clear();
|
|
return;
|
|
}
|
|
const int sock = client.sock;
|
|
for (auto& tunnel : tunnel_clients_) {
|
|
if (tunnel.connected && tunnel.tcp_sock == sock) {
|
|
resetTunnelClient(tunnel);
|
|
}
|
|
}
|
|
closeSecureSessionsForTcp(sock);
|
|
if (active_tcp_sock_ == sock) {
|
|
active_tcp_sock_ = -1;
|
|
}
|
|
shutdown(sock, SHUT_RDWR);
|
|
close(sock);
|
|
client.sock = -1;
|
|
client.rx_buffer.clear();
|
|
client.last_activity_tick = 0;
|
|
}
|
|
|
|
void GatewayKnxTpIpRouter::refreshNetworkInterfaces(bool force_log) {
|
|
if (!config_.multicast_enabled || udp_sock_ < 0) {
|
|
return;
|
|
}
|
|
const auto netifs = ActiveKnxNetifs();
|
|
if (netifs.empty()) {
|
|
SemaphoreGuard guard(openknx_lock_);
|
|
if (ets_device_ != nullptr) {
|
|
ets_device_->setNetworkInterface(nullptr);
|
|
}
|
|
if (force_log) {
|
|
ESP_LOGW(kTag, "KNX multicast group %s not joined yet: no IPv4 interface is up",
|
|
config_.multicast_address.c_str());
|
|
}
|
|
return;
|
|
}
|
|
|
|
{
|
|
SemaphoreGuard guard(openknx_lock_);
|
|
if (ets_device_ != nullptr) {
|
|
ets_device_->setNetworkInterface(netifs.front().netif);
|
|
}
|
|
}
|
|
|
|
const uint32_t multicast_address = inet_addr(config_.multicast_address.c_str());
|
|
for (const auto& netif : netifs) {
|
|
if (std::find(multicast_joined_interfaces_.begin(), multicast_joined_interfaces_.end(),
|
|
netif.address) != multicast_joined_interfaces_.end()) {
|
|
continue;
|
|
}
|
|
ip_mreq mreq{};
|
|
mreq.imr_multiaddr.s_addr = multicast_address;
|
|
mreq.imr_interface.s_addr = netif.address;
|
|
if (setsockopt(udp_sock_, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
|
|
ESP_LOGW(kTag,
|
|
"failed to join KNX multicast group %s on %s %s UDP port %u: errno=%d (%s)",
|
|
config_.multicast_address.c_str(), netif.key, Ipv4String(netif.address).c_str(),
|
|
static_cast<unsigned>(config_.udp_port), errno, std::strerror(errno));
|
|
continue;
|
|
}
|
|
multicast_joined_interfaces_.push_back(netif.address);
|
|
ESP_LOGI(kTag, "joined KNX multicast group %s on %s %s UDP port %u",
|
|
config_.multicast_address.c_str(), netif.key, Ipv4String(netif.address).c_str(),
|
|
static_cast<unsigned>(config_.udp_port));
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<openknx::TpuartUartInterface>
|
|
GatewayKnxTpIpRouter::createOpenKnxTpUartInterface() {
|
|
if (!GatewayKnxConfigUsesTpUart(config_)) {
|
|
tp_uart_port_ = -1;
|
|
tp_uart_tx_pin_ = -1;
|
|
tp_uart_rx_pin_ = -1;
|
|
tp_uart_online_ = false;
|
|
ESP_LOGI(kTag, "KNX TP-UART disabled by UART port; KNXnet/IP uses IP-only runtime");
|
|
return nullptr;
|
|
}
|
|
const auto& serial = config_.tp_uart;
|
|
if (serial.uart_port < 0 || serial.uart_port > 2) {
|
|
last_error_ = "invalid KNX TP-UART port " + std::to_string(serial.uart_port);
|
|
ESP_LOGE(kTag, "%s", last_error_.c_str());
|
|
return nullptr;
|
|
}
|
|
const uart_port_t uart_port = static_cast<uart_port_t>(serial.uart_port);
|
|
int tx_pin = UART_PIN_NO_CHANGE;
|
|
int rx_pin = UART_PIN_NO_CHANGE;
|
|
const bool tx_pin_ok = ResolveUartIoPin(uart_port, serial.tx_pin, SOC_UART_TX_PIN_IDX, &tx_pin);
|
|
const bool rx_pin_ok = ResolveUartIoPin(uart_port, serial.rx_pin, SOC_UART_RX_PIN_IDX, &rx_pin);
|
|
if (!tx_pin_ok || !rx_pin_ok) {
|
|
last_error_ = "KNX TP-UART UART" + std::to_string(serial.uart_port) +
|
|
" has no ESP-IDF default " + (!tx_pin_ok ? std::string("TX") : std::string("")) +
|
|
(!tx_pin_ok && !rx_pin_ok ? "/" : "") +
|
|
(!rx_pin_ok ? std::string("RX") : std::string("")) +
|
|
" pin; configure explicit txPin/rxPin values";
|
|
ESP_LOGE(kTag, "%s", last_error_.c_str());
|
|
return nullptr;
|
|
}
|
|
tp_uart_port_ = serial.uart_port;
|
|
tp_uart_tx_pin_ = tx_pin;
|
|
tp_uart_rx_pin_ = rx_pin;
|
|
return std::make_unique<openknx::TpuartUartInterface>(
|
|
uart_port, serial.tx_pin, serial.rx_pin, serial.rx_buffer_size,
|
|
serial.tx_buffer_size, serial.nine_bit_mode);
|
|
}
|
|
|
|
bool GatewayKnxTpIpRouter::configureTpUart() {
|
|
if (tp_uart_port_ < 0) {
|
|
return true;
|
|
}
|
|
if (ets_device_ == nullptr || !ets_device_->hasTpUart()) {
|
|
last_error_ = "KNX TP-UART interface is unavailable in OpenKNX runtime";
|
|
ESP_LOGE(kTag, "%s", last_error_.c_str());
|
|
return false;
|
|
}
|
|
const TickType_t startup_timeout_ticks =
|
|
pdMS_TO_TICKS(config_.tp_uart.startup_timeout_ms);
|
|
const TickType_t retry_poll_ticks = std::max<TickType_t>(1, pdMS_TO_TICKS(20));
|
|
const TickType_t startup_begin_tick = xTaskGetTickCount();
|
|
tp_uart_online_ = ets_device_->enableTpUart(true);
|
|
while (!tp_uart_online_ && startup_timeout_ticks > 0 &&
|
|
(xTaskGetTickCount() - startup_begin_tick) < startup_timeout_ticks) {
|
|
vTaskDelay(retry_poll_ticks);
|
|
ets_device_->loop();
|
|
tp_uart_online_ = ets_device_->tpUartOnline();
|
|
}
|
|
if (!tp_uart_online_) {
|
|
last_error_ = "OpenKNX failed to initialize KNX TP-UART uart=" +
|
|
std::to_string(config_.tp_uart.uart_port) + " tx=" +
|
|
UartPinDescription(config_.tp_uart.tx_pin, tp_uart_tx_pin_) + " rx=" +
|
|
UartPinDescription(config_.tp_uart.rx_pin, tp_uart_rx_pin_);
|
|
const bool configured = ets_device_ != nullptr && ets_device_->configured();
|
|
ESP_LOGW(kTag,
|
|
"%s; continuing KNXnet/IP in %s IP mode while TP-UART stays offline",
|
|
last_error_.c_str(), configured ? "configured" : "commissioning-only");
|
|
tp_uart_port_ = -1;
|
|
tp_uart_online_ = false;
|
|
return true;
|
|
}
|
|
const TickType_t startup_elapsed_ticks = xTaskGetTickCount() - startup_begin_tick;
|
|
if (startup_elapsed_ticks > 0) {
|
|
ESP_LOGI(kTag, "KNX TP-UART startup settled after %lu ms",
|
|
static_cast<unsigned long>(pdTICKS_TO_MS(startup_elapsed_ticks)));
|
|
}
|
|
ESP_LOGI(kTag, "KNX TP-UART online uart=%d tx=%s rx=%s baud=%u nineBit=%d",
|
|
config_.tp_uart.uart_port,
|
|
UartPinDescription(config_.tp_uart.tx_pin, tp_uart_tx_pin_).c_str(),
|
|
UartPinDescription(config_.tp_uart.rx_pin, tp_uart_rx_pin_).c_str(),
|
|
static_cast<unsigned>(config_.tp_uart.baudrate), config_.tp_uart.nine_bit_mode);
|
|
return true;
|
|
}
|
|
|
|
bool GatewayKnxTpIpRouter::configureProgrammingGpio() {
|
|
programming_button_last_pressed_ = false;
|
|
programming_button_last_toggle_tick_ = 0;
|
|
programming_led_state_ = false;
|
|
oam_programming_button_last_pressed_ = false;
|
|
oam_programming_button_last_toggle_tick_ = 0;
|
|
oam_programming_led_state_ = false;
|
|
|
|
if (config_.programming_button_gpio >= 0) {
|
|
gpio_config_t button_config{};
|
|
button_config.pin_bit_mask = 1ULL << static_cast<uint32_t>(config_.programming_button_gpio);
|
|
button_config.mode = GPIO_MODE_INPUT;
|
|
button_config.pull_up_en = config_.programming_button_active_low ? GPIO_PULLUP_ENABLE
|
|
: GPIO_PULLUP_DISABLE;
|
|
button_config.pull_down_en = config_.programming_button_active_low ? GPIO_PULLDOWN_DISABLE
|
|
: GPIO_PULLDOWN_ENABLE;
|
|
button_config.intr_type = GPIO_INTR_DISABLE;
|
|
const esp_err_t err = gpio_config(&button_config);
|
|
if (err != ESP_OK) {
|
|
last_error_ = EspErrDetail("failed to configure KNX programming button GPIO" +
|
|
std::to_string(config_.programming_button_gpio),
|
|
err);
|
|
ESP_LOGE(kTag, "%s", last_error_.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (config_.programming_led_gpio >= 0) {
|
|
gpio_config_t led_config{};
|
|
led_config.pin_bit_mask = 1ULL << static_cast<uint32_t>(config_.programming_led_gpio);
|
|
led_config.mode = GPIO_MODE_OUTPUT;
|
|
led_config.pull_up_en = GPIO_PULLUP_DISABLE;
|
|
led_config.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
|
led_config.intr_type = GPIO_INTR_DISABLE;
|
|
const esp_err_t err = gpio_config(&led_config);
|
|
if (err != ESP_OK) {
|
|
last_error_ = EspErrDetail("failed to configure KNX programming LED GPIO" +
|
|
std::to_string(config_.programming_led_gpio),
|
|
err);
|
|
ESP_LOGE(kTag, "%s", last_error_.c_str());
|
|
return false;
|
|
}
|
|
setProgrammingLed(false);
|
|
}
|
|
|
|
if (config_.oam_router.enabled && config_.oam_router.programming_button_gpio >= 0) {
|
|
gpio_config_t button_config{};
|
|
button_config.pin_bit_mask =
|
|
1ULL << static_cast<uint32_t>(config_.oam_router.programming_button_gpio);
|
|
button_config.mode = GPIO_MODE_INPUT;
|
|
button_config.pull_up_en = config_.oam_router.programming_button_active_low
|
|
? GPIO_PULLUP_ENABLE
|
|
: GPIO_PULLUP_DISABLE;
|
|
button_config.pull_down_en = config_.oam_router.programming_button_active_low
|
|
? GPIO_PULLDOWN_DISABLE
|
|
: GPIO_PULLDOWN_ENABLE;
|
|
button_config.intr_type = GPIO_INTR_DISABLE;
|
|
const esp_err_t err = gpio_config(&button_config);
|
|
if (err != ESP_OK) {
|
|
last_error_ = EspErrDetail("failed to configure OAM KNX programming button GPIO" +
|
|
std::to_string(config_.oam_router.programming_button_gpio),
|
|
err);
|
|
ESP_LOGE(kTag, "%s", last_error_.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (config_.oam_router.enabled && config_.oam_router.programming_led_gpio >= 0) {
|
|
gpio_config_t led_config{};
|
|
led_config.pin_bit_mask =
|
|
1ULL << static_cast<uint32_t>(config_.oam_router.programming_led_gpio);
|
|
led_config.mode = GPIO_MODE_OUTPUT;
|
|
led_config.pull_up_en = GPIO_PULLUP_DISABLE;
|
|
led_config.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
|
led_config.intr_type = GPIO_INTR_DISABLE;
|
|
const esp_err_t err = gpio_config(&led_config);
|
|
if (err != ESP_OK) {
|
|
last_error_ = EspErrDetail("failed to configure OAM KNX programming LED GPIO" +
|
|
std::to_string(config_.oam_router.programming_led_gpio),
|
|
err);
|
|
ESP_LOGE(kTag, "%s", last_error_.c_str());
|
|
return false;
|
|
}
|
|
setOamProgrammingLed(false);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace gateway
|