Initial commit

This commit is contained in:
Tony
2026-04-29 18:53:26 +08:00
commit f4756ce816
45 changed files with 14318 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
**/build/
build/
**/managed_components/
+17
View File
@@ -0,0 +1,17 @@
# Gateway Rewrite
This folder hosts the native ESP-IDF C++ rewrite of the Lua DALI gateway.
## Layout
- `apps/`: standard ESP-IDF applications for each firmware role.
- `apps/gateway/main/Kconfig.projbuild`: project-visible gateway-role settings such as per-channel native/serial PHY selection, gateway ids, and pin mapping.
- `components/`: reusable components shared by all gateway applications.
- `gateway_core/`: boot profile and top-level role bootstrap.
- `dali/`: vendored ESP-IDF DALI HAL/backend reused from LuatOS.
- `dali_domain/`: native DALI domain facade over `dali_cpp`.
- `gateway_runtime/`: persistent runtime state, command queueing, and device info services.
## Current status
The native rewrite now wires a shared `gateway_core` bootstrap component, a multi-channel `dali_domain` wrapper over `dali_cpp`, a local vendored `dali` hardware backend from the LuatOS ESP-IDF port, and an initial `gateway_runtime` service that provides persistent settings, device info, Lua-compatible command framing helpers, and Lua-style query command deduplication. The gateway app exposes per-channel PHY selection through `main/Kconfig.projbuild`; each channel can be disabled, bound to the native DALI GPIO HAL, or bound to a UART1/UART2 serial PHY.
+13
View File
@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.16)
if(NOT IDF_TARGET)
set(IDF_TARGET "esp32s3" CACHE STRING "ESP-IDF target")
endif()
set(EXTRA_COMPONENT_DIRS
"${CMAKE_CURRENT_LIST_DIR}/../../components"
"${CMAKE_CURRENT_LIST_DIR}/../../../dali_cpp"
)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(gateway_app)
+34
View File
@@ -0,0 +1,34 @@
dependencies:
espressif/cjson:
component_hash:
e788323270d90738662d66fffa910bfe1fba019bba087f01557e70c40485b469
dependencies:
- name: idf
require: private
version: '>=5.0'
source:
registry_url: https://components.espressif.com/
type: service
version: 1.7.19~2
espressif/mqtt:
component_hash:
ffdad5659706b4dc14bc63f8eb73ef765efa015bf7e9adf71c813d52a2dc9342
dependencies:
- name: idf
require: private
version: '>=5.3'
source:
registry_url: https://components.espressif.com/
type: service
version: 1.0.0
idf:
source:
type: idf
version: 5.5.4
direct_dependencies:
- espressif/cjson
- espressif/mqtt
- idf
manifest_hash: 9cb68c165ea4256d19cf45872db2feae2aad30a985b77b20da75d7dfa6eeab36
target: esp32s3
version: 2.0.0
+6
View File
@@ -0,0 +1,6 @@
idf_component_register(
SRCS "app_main.cpp"
REQUIRES gateway_core dali_domain gateway_runtime log
)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
+237
View File
@@ -0,0 +1,237 @@
menu "Gateway App"
config GATEWAY_CHANNEL_COUNT
int "Gateway channel count"
range 1 2
default 2
help
Number of logical DALI gateway channels exposed by the native gateway.
menu "Gateway Channel 1"
config GATEWAY_CHANNEL1_GW_ID
int "Gateway id"
range 0 255
default 3
help
Lua-compatible gateway id for the first DALI channel.
choice GATEWAY_CHANNEL1_PHY
prompt "Channel 1 PHY"
default GATEWAY_CHANNEL1_PHY_DISABLED
help
Select the physical transport used by channel 1.
config GATEWAY_CHANNEL1_PHY_DISABLED
bool "Disabled"
config GATEWAY_CHANNEL1_PHY_NATIVE
bool "Native DALI GPIO"
config GATEWAY_CHANNEL1_PHY_UART1
bool "Serial PHY on UART1"
config GATEWAY_CHANNEL1_PHY_UART2
bool "Serial PHY on UART2"
endchoice
config GATEWAY_CHANNEL1_NATIVE_BUS_ID
int "Native DALI bus id"
depends on GATEWAY_CHANNEL1_PHY_NATIVE
range 0 15
default 0
help
Logical DALI HAL bus index for channel 1.
config GATEWAY_CHANNEL1_NATIVE_TX_PIN
int "Native DALI TX pin"
depends on GATEWAY_CHANNEL1_PHY_NATIVE
range 0 48
default 18
help
ESP32-S3 GPIO used for channel 1 native DALI transmit.
config GATEWAY_CHANNEL1_NATIVE_RX_PIN
int "Native DALI RX pin"
depends on GATEWAY_CHANNEL1_PHY_NATIVE
range 0 48
default 8
help
ESP32-S3 GPIO used for channel 1 native DALI receive.
config GATEWAY_CHANNEL1_NATIVE_BAUDRATE
int "Native DALI baudrate"
depends on GATEWAY_CHANNEL1_PHY_NATIVE
range 400 2400
default 1200
help
Runtime baudrate used for channel 1 native DALI timing.
config GATEWAY_CHANNEL1_SERIAL_TX_PIN
int "Serial PHY TX pin"
depends on GATEWAY_CHANNEL1_PHY_UART1 || GATEWAY_CHANNEL1_PHY_UART2
range 0 48
default 1
help
ESP32-S3 GPIO used by the channel 1 serial PHY transmit pin.
config GATEWAY_CHANNEL1_SERIAL_RX_PIN
int "Serial PHY RX pin"
depends on GATEWAY_CHANNEL1_PHY_UART1 || GATEWAY_CHANNEL1_PHY_UART2
range 0 48
default 2
help
ESP32-S3 GPIO used by the channel 1 serial PHY receive pin.
config GATEWAY_CHANNEL1_SERIAL_BAUDRATE
int "Serial PHY baudrate"
depends on GATEWAY_CHANNEL1_PHY_UART1 || GATEWAY_CHANNEL1_PHY_UART2
range 1200 921600
default 9600
help
UART baudrate used by the channel 1 serial PHY.
config GATEWAY_CHANNEL1_SERIAL_RX_BUFFER
int "Serial PHY RX buffer bytes"
depends on GATEWAY_CHANNEL1_PHY_UART1 || GATEWAY_CHANNEL1_PHY_UART2
range 128 4096
default 512
endmenu
menu "Gateway Channel 2"
config GATEWAY_CHANNEL2_GW_ID
int "Gateway id"
depends on GATEWAY_CHANNEL_COUNT >= 2
range 0 255
default 4
help
Lua-compatible gateway id for the second DALI channel.
choice GATEWAY_CHANNEL2_PHY
prompt "Channel 2 PHY"
depends on GATEWAY_CHANNEL_COUNT >= 2
default GATEWAY_CHANNEL2_PHY_DISABLED
help
Select the physical transport used by channel 2.
config GATEWAY_CHANNEL2_PHY_DISABLED
bool "Disabled"
config GATEWAY_CHANNEL2_PHY_NATIVE
bool "Native DALI GPIO"
config GATEWAY_CHANNEL2_PHY_UART1
bool "Serial PHY on UART1"
config GATEWAY_CHANNEL2_PHY_UART2
bool "Serial PHY on UART2"
endchoice
config GATEWAY_CHANNEL2_NATIVE_BUS_ID
int "Native DALI bus id"
depends on GATEWAY_CHANNEL_COUNT >= 2 && GATEWAY_CHANNEL2_PHY_NATIVE
range 0 15
default 1
help
Logical DALI HAL bus index for channel 2.
config GATEWAY_CHANNEL2_NATIVE_TX_PIN
int "Native DALI TX pin"
depends on GATEWAY_CHANNEL_COUNT >= 2 && GATEWAY_CHANNEL2_PHY_NATIVE
range 0 48
default 19
help
ESP32-S3 GPIO used for channel 2 native DALI transmit.
config GATEWAY_CHANNEL2_NATIVE_RX_PIN
int "Native DALI RX pin"
depends on GATEWAY_CHANNEL_COUNT >= 2 && GATEWAY_CHANNEL2_PHY_NATIVE
range 0 48
default 9
help
ESP32-S3 GPIO used for channel 2 native DALI receive.
config GATEWAY_CHANNEL2_NATIVE_BAUDRATE
int "Native DALI baudrate"
depends on GATEWAY_CHANNEL_COUNT >= 2 && GATEWAY_CHANNEL2_PHY_NATIVE
range 400 2400
default 1200
help
Runtime baudrate used for channel 2 native DALI timing.
config GATEWAY_CHANNEL2_SERIAL_TX_PIN
int "Serial PHY TX pin"
depends on GATEWAY_CHANNEL_COUNT >= 2 && (GATEWAY_CHANNEL2_PHY_UART1 || GATEWAY_CHANNEL2_PHY_UART2)
range 0 48
default 6
help
ESP32-S3 GPIO used by the channel 2 serial PHY transmit pin.
config GATEWAY_CHANNEL2_SERIAL_RX_PIN
int "Serial PHY RX pin"
depends on GATEWAY_CHANNEL_COUNT >= 2 && (GATEWAY_CHANNEL2_PHY_UART1 || GATEWAY_CHANNEL2_PHY_UART2)
range 0 48
default 7
help
ESP32-S3 GPIO used by the channel 2 serial PHY receive pin.
config GATEWAY_CHANNEL2_SERIAL_BAUDRATE
int "Serial PHY baudrate"
depends on GATEWAY_CHANNEL_COUNT >= 2 && (GATEWAY_CHANNEL2_PHY_UART1 || GATEWAY_CHANNEL2_PHY_UART2)
range 1200 921600
default 9600
help
UART baudrate used by the channel 2 serial PHY.
config GATEWAY_CHANNEL2_SERIAL_RX_BUFFER
int "Serial PHY RX buffer bytes"
depends on GATEWAY_CHANNEL_COUNT >= 2 && (GATEWAY_CHANNEL2_PHY_UART1 || GATEWAY_CHANNEL2_PHY_UART2)
range 128 4096
default 512
endmenu
config GATEWAY_ENABLE_DALI_BUS
bool "Legacy single local DALI bus switch"
default n
help
Legacy compatibility option retained for existing sdkconfig files. New code uses the
per-channel PHY selectors above.
config GATEWAY_DALI_BUS_ID
int "DALI bus id"
depends on GATEWAY_ENABLE_DALI_BUS
range 0 15
default 0
help
Logical DALI bus index used by the HAL queue set.
config GATEWAY_DALI_TX_PIN
int "DALI TX pin"
depends on GATEWAY_ENABLE_DALI_BUS
range 0 48
default 18
help
ESP32-S3 GPIO used for the DALI transmit pin.
config GATEWAY_DALI_RX_PIN
int "DALI RX pin"
depends on GATEWAY_ENABLE_DALI_BUS
range 0 48
default 8
help
ESP32-S3 GPIO used for the DALI receive pin.
config GATEWAY_DALI_BAUDRATE
int "DALI baudrate"
depends on GATEWAY_ENABLE_DALI_BUS
range 400 2400
default 1200
help
Runtime baudrate used when initializing the local DALI bus.
endmenu
+126
View File
@@ -0,0 +1,126 @@
#include "dali_domain.hpp"
#include "gateway_core.hpp"
#include "gateway_runtime.hpp"
#include "sdkconfig.h"
#include <cstdio>
#ifndef CONFIG_GATEWAY_CHANNEL_COUNT
#define CONFIG_GATEWAY_CHANNEL_COUNT 2
#endif
namespace {
constexpr const char* kProjectName = "DALI_485_Gateway";
constexpr const char* kProjectVersion = "0.1.0";
[[maybe_unused]] void LogBindError(const char* channel_name, esp_err_t err) {
if (err != ESP_OK) {
std::printf("gateway_main: failed to bind %s err=%d\n", channel_name, err);
}
}
void BindConfiguredChannels(gateway::DaliDomainService& dali_domain,
const gateway::GatewayRuntime& runtime) {
#if CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE
gateway::DaliHardwareBusConfig channel1{};
channel1.channel_index = 0;
channel1.gateway_id = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL1_GW_ID);
channel1.bus_id = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL1_NATIVE_BUS_ID);
channel1.tx_pin = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL1_NATIVE_TX_PIN);
channel1.rx_pin = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL1_NATIVE_RX_PIN);
channel1.baudrate = static_cast<uint32_t>(CONFIG_GATEWAY_CHANNEL1_NATIVE_BAUDRATE);
channel1.name = runtime.gatewayName(channel1.gateway_id);
LogBindError("channel1 native DALI", dali_domain.bindHardwareBus(channel1));
#elif CONFIG_GATEWAY_CHANNEL1_PHY_UART1 || CONFIG_GATEWAY_CHANNEL1_PHY_UART2
gateway::DaliSerialBusConfig channel1{};
channel1.channel_index = 0;
channel1.gateway_id = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL1_GW_ID);
#if CONFIG_GATEWAY_CHANNEL1_PHY_UART1
channel1.uart_port = 1;
#else
channel1.uart_port = 2;
#endif
channel1.tx_pin = CONFIG_GATEWAY_CHANNEL1_SERIAL_TX_PIN;
channel1.rx_pin = CONFIG_GATEWAY_CHANNEL1_SERIAL_RX_PIN;
channel1.baudrate = static_cast<uint32_t>(CONFIG_GATEWAY_CHANNEL1_SERIAL_BAUDRATE);
channel1.rx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_CHANNEL1_SERIAL_RX_BUFFER);
channel1.tx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_CHANNEL1_SERIAL_RX_BUFFER);
channel1.name = runtime.gatewayName(channel1.gateway_id);
LogBindError("channel1 serial DALI", dali_domain.bindSerialBus(channel1));
#endif
#if CONFIG_GATEWAY_CHANNEL_COUNT >= 2
#if CONFIG_GATEWAY_CHANNEL2_PHY_NATIVE
gateway::DaliHardwareBusConfig channel2{};
channel2.channel_index = 1;
channel2.gateway_id = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL2_GW_ID);
channel2.bus_id = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL2_NATIVE_BUS_ID);
channel2.tx_pin = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL2_NATIVE_TX_PIN);
channel2.rx_pin = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL2_NATIVE_RX_PIN);
channel2.baudrate = static_cast<uint32_t>(CONFIG_GATEWAY_CHANNEL2_NATIVE_BAUDRATE);
channel2.name = runtime.gatewayName(channel2.gateway_id);
LogBindError("channel2 native DALI", dali_domain.bindHardwareBus(channel2));
#elif CONFIG_GATEWAY_CHANNEL2_PHY_UART1 || CONFIG_GATEWAY_CHANNEL2_PHY_UART2
gateway::DaliSerialBusConfig channel2{};
channel2.channel_index = 1;
channel2.gateway_id = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL2_GW_ID);
#if CONFIG_GATEWAY_CHANNEL2_PHY_UART1
channel2.uart_port = 1;
#else
channel2.uart_port = 2;
#endif
channel2.tx_pin = CONFIG_GATEWAY_CHANNEL2_SERIAL_TX_PIN;
channel2.rx_pin = CONFIG_GATEWAY_CHANNEL2_SERIAL_RX_PIN;
channel2.baudrate = static_cast<uint32_t>(CONFIG_GATEWAY_CHANNEL2_SERIAL_BAUDRATE);
channel2.rx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_CHANNEL2_SERIAL_RX_BUFFER);
channel2.tx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_CHANNEL2_SERIAL_RX_BUFFER);
channel2.name = runtime.gatewayName(channel2.gateway_id);
LogBindError("channel2 serial DALI", dali_domain.bindSerialBus(channel2));
#endif
#endif
}
} // namespace
extern "C" void app_main(void) {
ESP_ERROR_CHECK(gateway::InitializeRuntimeNvs());
const gateway::BootProfile profile{
gateway::AppRole::kGateway,
"gateway",
true,
true,
true,
true,
true,
};
gateway::GatewayCore core(profile);
core.start();
gateway::DaliDomainService dali_domain;
gateway::GatewayRuntime runtime(
profile,
gateway::GatewayRuntimeConfig{
kProjectName,
kProjectVersion,
gateway::ReadRuntimeSerialId(),
},
&dali_domain);
ESP_ERROR_CHECK(runtime.start());
runtime.setGatewayCount(CONFIG_GATEWAY_CHANNEL_COUNT);
BindConfiguredChannels(dali_domain, runtime);
const auto device_info = runtime.deviceInfo();
std::printf("gateway_main: dali domain implementation=%s bound=%d channels=%u\n",
dali_domain.implementationName(), dali_domain.isBound(),
static_cast<unsigned>(dali_domain.channelCount()));
for (const auto& channel : dali_domain.channelInfo()) {
std::printf("gateway_main: channel=%u gateway=%u name=%s\n", channel.channel_index,
channel.gateway_id, channel.name.c_str());
}
std::printf("gateway_main: runtime device type=%s serial=%s project=%s version=%s\n",
device_info.type.c_str(), device_info.serial_id.c_str(),
device_info.project.c_str(), device_info.version.c_str());
}
+8
View File
@@ -0,0 +1,8 @@
# Name, Type, SubType, Offset, Size, Flags
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,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x6000
3 otadata data ota 0xf000 0x2000
4 phy_init data phy 0x11000 0x1000
5 factory app factory 0x20000 0x180000
6 ota_0 app ota_0 0x1a0000 0x180000
7 ota_1 app ota_1 0x320000 0x180000
8 storage data spiffs 0x4a0000 0xb60000
File diff suppressed because it is too large Load Diff
+15
View File
@@ -0,0 +1,15 @@
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
CONFIG_ESPTOOLPY_FLASHSIZE="16MB"
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y
CONFIG_BT_ENABLED=y
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_ETH_ENABLED=y
CONFIG_ETH_USE_SPI_ETHERNET=y
CONFIG_ETH_SPI_ETHERNET_W5500=y
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.16)
if(NOT IDF_TARGET)
set(IDF_TARGET "esp32s3" CACHE STRING "ESP-IDF target")
endif()
set(EXTRA_COMPONENT_DIRS
"${CMAKE_CURRENT_LIST_DIR}/../../components"
"${CMAKE_CURRENT_LIST_DIR}/../../../dali_cpp"
)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(receiver_app)
+34
View File
@@ -0,0 +1,34 @@
dependencies:
espressif/cjson:
component_hash:
e788323270d90738662d66fffa910bfe1fba019bba087f01557e70c40485b469
dependencies:
- name: idf
require: private
version: '>=5.0'
source:
registry_url: https://components.espressif.com/
type: service
version: 1.7.19~2
espressif/mqtt:
component_hash:
ffdad5659706b4dc14bc63f8eb73ef765efa015bf7e9adf71c813d52a2dc9342
dependencies:
- name: idf
require: private
version: '>=5.3'
source:
registry_url: https://components.espressif.com/
type: service
version: 1.0.0
idf:
source:
type: idf
version: 5.5.4
direct_dependencies:
- espressif/cjson
- espressif/mqtt
- idf
manifest_hash: c84936cd9495d1229bcfb1c42ee403b1c8858c73866e5ccacb7bf36de2b929c2
target: esp32s3
version: 2.0.0
+6
View File
@@ -0,0 +1,6 @@
idf_component_register(
SRCS "app_main.cpp"
REQUIRES gateway_core gateway_runtime
)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
+38
View File
@@ -0,0 +1,38 @@
#include "gateway_core.hpp"
#include "gateway_runtime.hpp"
namespace {
constexpr const char* kProjectName = "UartRelayer";
constexpr const char* kProjectVersion = "0.1.0";
gateway::BootProfile ReceiverProfile() {
return gateway::BootProfile{
gateway::AppRole::kReceiver,
"receiver",
false,
false,
false,
true,
true,
};
}
} // namespace
extern "C" void app_main(void) {
ESP_ERROR_CHECK(gateway::InitializeRuntimeNvs());
gateway::GatewayCore core(ReceiverProfile());
core.start();
gateway::GatewayRuntime runtime(
core.profile(),
gateway::GatewayRuntimeConfig{
kProjectName,
kProjectVersion,
gateway::ReadRuntimeSerialId(),
},
nullptr);
ESP_ERROR_CHECK(runtime.start());
}
+8
View File
@@ -0,0 +1,8 @@
# Name, Type, SubType, Offset, Size, Flags
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,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x6000
3 otadata data ota 0xf000 0x2000
4 phy_init data phy 0x11000 0x1000
5 factory app factory 0x20000 0x180000
6 ota_0 app ota_0 0x1a0000 0x180000
7 ota_1 app ota_1 0x320000 0x180000
8 storage data spiffs 0x4a0000 0xb60000
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
CONFIG_ESPTOOLPY_FLASHSIZE="16MB"
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y
+13
View File
@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.16)
if(NOT IDF_TARGET)
set(IDF_TARGET "esp32s3" CACHE STRING "ESP-IDF target")
endif()
set(EXTRA_COMPONENT_DIRS
"${CMAKE_CURRENT_LIST_DIR}/../../components"
"${CMAKE_CURRENT_LIST_DIR}/../../../dali_cpp"
)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(telemetry_app)
+34
View File
@@ -0,0 +1,34 @@
dependencies:
espressif/cjson:
component_hash:
e788323270d90738662d66fffa910bfe1fba019bba087f01557e70c40485b469
dependencies:
- name: idf
require: private
version: '>=5.0'
source:
registry_url: https://components.espressif.com/
type: service
version: 1.7.19~2
espressif/mqtt:
component_hash:
ffdad5659706b4dc14bc63f8eb73ef765efa015bf7e9adf71c813d52a2dc9342
dependencies:
- name: idf
require: private
version: '>=5.3'
source:
registry_url: https://components.espressif.com/
type: service
version: 1.0.0
idf:
source:
type: idf
version: 5.5.4
direct_dependencies:
- espressif/cjson
- espressif/mqtt
- idf
manifest_hash: c84936cd9495d1229bcfb1c42ee403b1c8858c73866e5ccacb7bf36de2b929c2
target: esp32s3
version: 2.0.0
+6
View File
@@ -0,0 +1,6 @@
idf_component_register(
SRCS "app_main.cpp"
REQUIRES gateway_core gateway_runtime
)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
+38
View File
@@ -0,0 +1,38 @@
#include "gateway_core.hpp"
#include "gateway_runtime.hpp"
namespace {
constexpr const char* kProjectName = "TelemetryReceiver";
constexpr const char* kProjectVersion = "0.1.0";
gateway::BootProfile TelemetryProfile() {
return gateway::BootProfile{
gateway::AppRole::kTelemetry,
"telemetry",
false,
false,
false,
true,
false,
};
}
} // namespace
extern "C" void app_main(void) {
ESP_ERROR_CHECK(gateway::InitializeRuntimeNvs());
gateway::GatewayCore core(TelemetryProfile());
core.start();
gateway::GatewayRuntime runtime(
core.profile(),
gateway::GatewayRuntimeConfig{
kProjectName,
kProjectVersion,
gateway::ReadRuntimeSerialId(),
},
nullptr);
ESP_ERROR_CHECK(runtime.start());
}
+8
View File
@@ -0,0 +1,8 @@
# Name, Type, SubType, Offset, Size, Flags
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,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x6000
3 otadata data ota 0xf000 0x2000
4 phy_init data phy 0x11000 0x1000
5 factory app factory 0x20000 0x180000
6 ota_0 app ota_0 0x1a0000 0x180000
7 ota_1 app ota_1 0x320000 0x180000
8 storage data spiffs 0x4a0000 0xb60000
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
CONFIG_ESPTOOLPY_FLASHSIZE="16MB"
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y
+13
View File
@@ -0,0 +1,13 @@
set(dali_srcs
"src/dali.c"
"src/dali_hal_idf5.c"
)
set(dali_include_dirs "src/include")
idf_component_register(
SRCS ${dali_srcs}
INCLUDE_DIRS ${dali_include_dirs}
REQUIRES driver esp_timer
)
+68
View File
@@ -0,0 +1,68 @@
menu "DALI Component"
config DALI_PHY_COUNT
int "Maximum DALI buses"
range 1 16
default 16
help
Maximum number of DALI PHY buses managed by this component.
config DALI_DEFAULT_BAUDRATE
int "Default DALI baudrate"
range 400 2400
default 1200
help
Default baudrate used during initialization.
config DALI_API_QUEUE_LEN
int "Global API queue length"
range 1 64
default 10
config DALI_TX_QUEUE_LEN
int "Per-bus TX queue length"
range 1 16
default 1
config DALI_TX_REPLY_QUEUE_LEN
int "Per-bus TX reply queue length"
range 1 32
default 4
config DALI_RX_QUEUE_LEN
int "Per-bus RX queue length"
range 1 128
default 50
config DALI_DEBUG_QUEUE_LEN
int "Debug queue length"
range 1 256
default 100
config DALI_ENABLE_DEBUG_TASK
bool "Enable debug task"
default n
help
When enabled, starts a low-priority task that prints RX timing traces.
config DALI_DALI_TASK_STACK_SIZE
int "DALI task stack size"
range 1024 8192
default 2048
config DALI_DALI_TASK_PRIORITY
int "DALI task priority"
range 1 24
default 2
config DALI_DEBUG_TASK_STACK_SIZE
int "Debug task stack size"
range 1024 8192
default 2048
config DALI_DEBUG_TASK_PRIORITY
int "Debug task priority"
range 1 24
default 1
endmenu
+18
View File
@@ -0,0 +1,18 @@
# DALI Component Migration Notes
## 0.1.0
### Breaking API cleanup
- Renamed global queue symbol:
- `dali_send_replay_queue` -> `dali_send_reply_queue`
### Build/config changes
- Added `Kconfig` options for queue sizes and task tuning.
- `components/dali/CMakeLists.txt` now uses a fixed explicit source list for the current ESP-IDF HAL path.
### Required action
- Replace all external uses of `dali_send_replay_queue` with `dali_send_reply_queue`.
- Run `idf.py reconfigure` after updating to load new Kconfig defaults.
+60
View File
@@ -0,0 +1,60 @@
# DALI ESP-IDF Component
This component provides a DALI (IEC 62386) protocol stack and ESP-IDF HAL implementation that can be used directly in native FreeRTOS/ESP-IDF projects.
## Features
- DALI forward and backward frame support.
- Multi-bus support (`DALI_PHY_COUNT`, default 16).
- Runtime baudrate control (`400..2400`, default `1200`).
- ESP-IDF GPTimer + GPIO interrupt driven HAL.
## Public Headers
- `dali.h`: high-level DALI helpers.
- `dali_hal.h`: HAL API and queue access.
- `dali_define.h`: DALI command definitions.
## Quick Start
1. Add this component to your project.
2. Configure TX/RX pins and initialize at least one bus.
3. Send a DALI frame.
```c
#include "dali.h"
#include "dali_define.h"
#include "dali_hal.h"
void app_main(void) {
ESP_ERROR_CHECK(dali_hal_set_baudrate(1200));
ESP_ERROR_CHECK(dali_hal_init(0, 4, 5));
Dali_msg_t msg = dali_msg_new(0xFF, DALI_CMD_ON); // broadcast ON command
msg.id = 0;
dali_send(&msg);
}
```
## Kconfig Options
Use `menuconfig` under `DALI Component` to configure:
- Bus count and default baudrate.
- Queue sizes.
- Task stack sizes and priorities.
- Optional debug task.
## API Note
The global TX response queue symbol was renamed:
- old: `dali_send_replay_queue`
- new: `dali_send_reply_queue`
See `MIGRATION.md` for compatibility notes.
## Examples
- `examples/dali_basic`
- `examples/dali_multi_bus`
+11
View File
@@ -0,0 +1,11 @@
version: "0.1.0"
description: "DALI (IEC 62386) protocol and HAL component for ESP-IDF"
url: "https://github.com/openLuat/luatos-soc-idf5"
repository: "https://github.com/openLuat/luatos-soc-idf5.git"
dependencies:
idf:
version: ">=5.0"
tags:
- dali
- lighting
- esp-idf
BIN
View File
Binary file not shown.
+507
View File
@@ -0,0 +1,507 @@
#include "dali.h"
#include "dali_hal.h"
#include "dali_define.h"
#include "freertos/FreeRTOS.h"
#include <memory.h> // for memset
#include "freertos/semphr.h"
static SemaphoreHandle_t s_dali_core_lock;
static SemaphoreHandle_t dali_core_mutex(void)
{
if (s_dali_core_lock == NULL) {
s_dali_core_lock = xSemaphoreCreateRecursiveMutex();
}
return s_dali_core_lock;
}
static inline void dali_core_lock(void)
{
SemaphoreHandle_t mtx = dali_core_mutex();
if (mtx) {
xSemaphoreTakeRecursive(mtx, portMAX_DELAY);
}
}
static inline void dali_core_unlock(void)
{
if (s_dali_core_lock) {
xSemaphoreGiveRecursive(s_dali_core_lock);
}
}
Dali_msg_t dali_msg_new_generic(uint8_t bit_length, uint8_t address, uint8_t cmd1, uint8_t cmd2, uint8_t cmd3) {
Dali_msg_t dali_msg;
dali_msg.id = 0;
dali_msg.type = DALI_MSG_FORWARD;
dali_msg.status = 0;
dali_msg.length = bit_length; // bit count 1-32
dali_msg.data[0] = address;
dali_msg.data[1] = cmd1;
dali_msg.data[2] = cmd2;
dali_msg.data[3] = cmd3;
return dali_msg;
}
Dali_msg_t dali_msg_new(uint8_t address, uint8_t cmd1) {
return dali_msg_new_generic(16, address, cmd1, 0, 0);
}
Dali_msg_t dali_msg_new_1B(uint8_t data) {
return dali_msg_new_generic(8, data, 0, 0, 0);
}
Dali_msg_t dali_msg_new_2B(uint8_t address, uint8_t cmd1) {
return dali_msg_new_generic(16, address, cmd1, 0, 0);
}
Dali_msg_t dali_msg_new_3B(uint8_t address, uint8_t cmd1, uint8_t cmd2) {
return dali_msg_new_generic(24, address, cmd1, cmd2, 0);
}
Dali_msg_t dali_msg_new_4B(uint8_t address, uint8_t cmd1, uint8_t cmd2, uint8_t cmd3) {
return dali_msg_new_generic(32, address, cmd1, cmd2, cmd3);
}
// ----------------------------------------------------------------------------------------------
// send message to DALI task - not expecting reply payload
// TODO also restrict delay - to allow recovery
static void dali_send_locked(Dali_msg_t *tx_msg) {
xQueueSendToBack(dali_send_queue, tx_msg, portMAX_DELAY); // send message to DALI task
xQueueReceive(dali_send_reply_queue, tx_msg, portMAX_DELAY); // always wait for reply that message was sent
// TODO check status
if(rx_debug_enabled & 0x01) {
printf("** tm=%d[ms] st=%d len=%d d0=%d [0x%02X] d1=%d [0x%02X]\n",
tx_msg->type, tx_msg->status, tx_msg->length, tx_msg->data[0], tx_msg->data[0], tx_msg->data[1], tx_msg->data[1]);
}
}
void dali_send(Dali_msg_t *tx_msg) {
dali_core_lock();
dali_send_locked(tx_msg);
dali_core_unlock();
}
// send double message
void dali_send_double(Dali_msg_t *dali_msg) {
dali_core_lock();
dali_send_locked(dali_msg);
// TODO check status
dali_msg->id++; // increment message ID
dali_delay_ms(10); // delay 13ms 101.8.1.2: 13.5 - 75ms
dali_send_locked(dali_msg);
// TODO check status
dali_core_unlock();
}
int dali_query(Dali_msg_t *tx_msg, Dali_msg_t *rx_msg) {
BaseType_t ret;
uint8_t bus_id = tx_msg ? tx_msg->id : 0;
if (bus_id >= DALI_PHY_COUNT || dali_receive_queues[bus_id] == NULL) {
bus_id = 0;
}
QueueHandle_t rx_q = dali_receive_queues[bus_id] ? dali_receive_queues[bus_id] : dali_receive_queue;
if (rx_q == NULL) {
printf("dali_query: rx queue not ready\n");
return -1;
}
// TODO check empty queue
if(xQueueReceive(rx_q, rx_msg, 0) == pdTRUE) {
printf("Queue not empty\n");
return -1;
}
// printf("check A tx=%d tm=%d[ms] st=%d len=%d d0=0x%X\n", ret, tx_msg->type, tx_msg->status, tx_msg->length, tx_msg->data[0]);
dali_core_lock();
dali_send_locked(tx_msg);
// receive message from DALI task
ret = xQueueReceive(rx_q, rx_msg, pdMS_TO_TICKS(50));
dali_core_unlock();
// printf("B rx=%d tm=%d[ms] st=%d len=%d d0=0x%X\n", ret, rx_msg->type, rx_msg->status, rx_msg->length, rx_msg->data[0]);
return ret;
}
// ----------------------------------------------------------------------------------------------
// addr: 0-63
uint32_t dali_cmd_query_rand_addr24(uint8_t addr)
{
Dali_msg_t tx, rx;
uint32_t addr24 = 0;
uint8_t device = dali_short_address(addr);
tx = dali_msg_new(device, DALI_CMD_QUERY_RAND_ADDR_H);
if(dali_query(&tx, &rx) == pdTRUE) {
addr24 |= rx.data[0] << 16;
}
else {
printf("Cannot query long address H\n");
return -1;
}
tx = dali_msg_new(device, DALI_CMD_QUERY_RAND_ADDR_M);
if(dali_query(&tx, &rx) == pdTRUE) {
addr24 |= rx.data[0] << 8;
}
else {
printf("Cannot query long address M\n");
return -1;
}
tx = dali_msg_new(device, DALI_CMD_QUERY_RAND_ADDR_L);
if(dali_query(&tx, &rx) == pdTRUE) {
addr24 |= rx.data[0];
}
else {
printf("Cannot query long address L\n");
return -1;
}
return addr24;
}
// commands
void dali_cmd(uint8_t device, uint8_t cmd)
{
Dali_msg_t tx;
tx = dali_msg_new(device, cmd);
dali_send(&tx);
}
void dali_cmd_double(uint8_t device, uint8_t cmd)
{
Dali_msg_t tx;
tx = dali_msg_new(device, cmd);
dali_send_double(&tx);
}
// device: 102.7.2.1 table 1
void dali_cmd_off(uint8_t device)
{
dali_cmd(device, DALI_CMD_OFF);
}
void dali_cmd_on(uint8_t device)
{
dali_cmd(device, DALI_CMD_ON);
}
// this is broadcast command - all devices set DTR0
void dali_set_dtr0(uint8_t value)
{
dali_cmd(DALI_CMD_DTR0, value);
}
void dali_set_dtr1(uint8_t value)
{
dali_cmd(DALI_CMD_DTR1, value);
}
void dali_set_dtr2(uint8_t value)
{
dali_cmd(DALI_CMD_DTR2, value);
}
// LEVELS
void dali_set_level(uint8_t device, uint8_t cmd, uint8_t level)
{
Dali_msg_t tx;
dali_set_dtr0(level);
tx = dali_msg_new(device, cmd);
dali_send_double(&tx);
}
// GROUP add or remove
// group: 0-15
void dali_group(uint8_t device1, uint8_t cmd, uint8_t group)
{
Dali_msg_t tx;
cmd += group & 0x0F;
tx = dali_msg_new(device1, cmd);
dali_send_double(&tx);
}
// ----------------------------------------------------------------------------------------------
// device address: 0-63
#define _QQ(cmd, val) \
tx = dali_msg_new(device, cmd); \
if(dali_query(&tx, &rx) == pdTRUE) { \
val = rx.data[0]; \
} \
else printf("Cannot query: %s\n", #cmd); \
void dali_device_info(uint8_t addr)
{
Dali_msg_t tx, rx;
// int ret;
uint8_t device = dali_short_address(addr);
uint32_t addr24 = 0;
uint8_t op=0, status=0, dtr0=0, dtr1=0, dtr2=0;
uint8_t levelActual=0, levelMax=0, levelMin=0, levelPowerOn=0, levelSystemFail=0, levelPhysMin=0;
uint8_t fadeTime=0, extFadeTime=0;
addr24 = dali_cmd_query_rand_addr24(addr);
_QQ(DALI_CMD_QUERY_OPERATING_MODE, op);
_QQ(DALI_CMD_QUERY_STATUS, status);
_QQ(DALI_CMD_QUERY_DTR0, dtr0);
_QQ(DALI_CMD_QUERY_DTR1, dtr1);
_QQ(DALI_CMD_QUERY_DTR2, dtr2);
printf("Device %2d [0x%zX]: MODE=0x%02X STAT=0x%02X DTR0=%3d [0x%02X] DTR1=%3d [0x%02X] DTR2=%3d [0x%02X]\n",
addr, (size_t)addr24, op, status, dtr0, dtr0, dtr1, dtr1, dtr2, dtr2);
_QQ(DALI_CMD_QUERY_ACTUAL_LEVEL, levelActual);
_QQ(DALI_CMD_QUERY_MAX_LEVEL, levelMax);
_QQ(DALI_CMD_QUERY_MIN_LEVEL, levelMin);
_QQ(DALI_CMD_QUERY_POWER_ON_LEVEL, levelPowerOn);
_QQ(DALI_CMD_QUERY_SYSTEM_FAILURE_LEVEL, levelSystemFail);
_QQ(DALI_CMD_QUERY_PHYSICAL_MIN, levelPhysMin);
_QQ(DALI_CMD_QUERY_FADE_TIME, fadeTime);
_QQ(DALI_CMD_QUERY_EXT_FADE_TIME, extFadeTime);
printf("* LEVELS ACT=%3d MAX=%3d MIN=%3d PMIN=%3d PWON=%3d SYSF=%3d FTM=0x%02X EFTM=0x%02X\n",
levelActual, levelMax, levelMin, levelPhysMin, levelPowerOn, levelSystemFail, fadeTime, extFadeTime);
// groups
uint8_t group0=0, group1=0;
_QQ(DALI_CMD_QUERY_GROUPS_0_7, group0);
_QQ(DALI_CMD_QUERY_GROUPS_8_15, group1);
printf("* GROUPS 0-7=0x%02X 8-15=0x%02X\n", group0, group1);
uint8_t scenes[16];
for(int i=0; i<16; i++) {
_QQ(DALI_CMD_QUERY_SCENE_LEVEL + i, scenes[i]);
}
printf("* SCENES: ");
for(int i=0; i<16; i++) {
printf("%3d ", scenes[i]);
if(i % 8 == 7) printf(" ");
}
printf("\n");
}
#undef _QQ // undefine macro
void dali_device_identify(uint8_t device)
{
Dali_msg_t tx;
tx = dali_msg_new(device, DALI_CMD_IDENTIFY);
dali_send_double(&tx);
}
// reset
// - random 24-bit address to 0xFFFFFF
// - levels to default values, clear groups and scenes
void dali_device_reset(uint8_t device)
{
Dali_msg_t tx;
tx = dali_msg_new(device, DALI_CMD_RESET);
dali_send_double(&tx);
dali_delay_ms(300); // wait for reset
}
// try identify all devices by asking for operating mode
// - check from 0 to 63
// - uint8 dev[64]
void dali_find_all_short(uint8_t *dev, int with_print)
{
printf("Identifying all devices\n");
Dali_msg_t tx, rx;
int ret;
memset(dev, 0, 64);
int cnt = 0;
for(int i=0; i<64; i++)
{
tx = dali_msg_new(dali_short_address(i), DALI_CMD_QUERY_OPERATING_MODE);
ret = dali_query(&tx, &rx);
if(ret == pdTRUE) {
dev[i] = 1;
cnt++;
// printf("Device %2d: operating mode: 0x%X\n", i, rx.data[0]);
if(with_print) printf("%d", i % 10);
}
else {
if(with_print) printf(".");
}
if(with_print) {
fflush(stdout);
dali_delay_ms(100);
}
}
if(with_print) printf(" \n");
printf("Found %d devices:", cnt);
for(int i=0; i<64; i++) if(dev[i]) printf(" %d", i);
printf("\n");
}
// ----------------------------------------------------------------------------------------------
// RANDOMIZE
// device: 0AAA AAA1 - only device with short address
// 1111 1111 - all devices without short address
// 0000 0000 - all devices
void dali_cmd_randomize(uint8_t device)
{
Dali_msg_t tx;
dali_cmd_initialise(device); // 0x00=ALL 0xFF=without short address 0b0AAA AAA1=with short address
// randomize 24-bit address
tx = dali_msg_new(DALI_CMD_RANDOMISE, 0x00);
dali_send_double(&tx);
dali_delay_ms(100); // wait for randomize
dali_cmd_terminate();
printf("*** Randomize end\n");
}
// - requires INITIALISE state
void dali_set_search_addr24(uint32_t addr24)
{
Dali_msg_t tx;
tx = dali_msg_new(DALI_CMD_SEARCHADDRH, addr24 >> 16);
dali_send(&tx);
tx = dali_msg_new(DALI_CMD_SEARCHADDRM, addr24 >> 8);
dali_send(&tx);
tx = dali_msg_new(DALI_CMD_SEARCHADDRL, addr24);
dali_send(&tx);
}
// only device in INITIALISE state and not in WITHDRAW state
// return: 24-bit address
// 0xFFFFFF - not found
uint32_t dali_binary_search()
{
Dali_msg_t tx, rx;
uint32_t high = 0xFFFFFF; // we must start from 0xFFFFFF but max address is 0xFFFFFE, it simplifies search
uint32_t low = 0x000000;
uint32_t mid;
int ret;
for(int i=0; i<24; i++)
{
mid = (high + low) / 2;
dali_set_search_addr24(mid);
// compare
tx = dali_msg_new(DALI_CMD_COMPARE, 0x00);
ret = dali_query(&tx, &rx);
if(ret) high = mid; // found address <= mid and high is possible address
else low = mid;
// printf("CMP i=%2d rx=%d tm=%d[ms] st=%d len=%d d0=0x%X mid=0x%06X high=0x%06X low=0x%06X\n",
// i, ret, rx.type, rx.status, rx.length, rx.data[0], mid, high, low);
if(high - low < 2) {
if(low == 0 && mid != 0) { // special case for 0
mid = 0;
continue;
}
break;
}
dali_delay_ms(10);
}
uint32_t addr24 = high; // found address is always in high
printf("* Address: 0x%zX\n", (size_t)addr24);
return addr24;
}
// discard address in dali search command COMPARE
// - requires INITIALISE state
void dali_cmd_withdraw(uint32_t addr24)
{
Dali_msg_t tx;
dali_set_search_addr24(addr24);
tx = dali_msg_new(DALI_CMD_WITHDRAW, 0x00);
dali_send(&tx);
printf("Withdraw 0x%06zX\n", (size_t)addr24);
}
// initialise all devices selected by device:
// device: 0AAA AAA1 - only device with this short address
// 1111 1111 - all devices without short address
// 0000 0000 - all devices
void dali_cmd_initialise(uint8_t device)
{
Dali_msg_t tx;
tx = dali_msg_new(DALI_CMD_INITIALISE, device); // 0x00=ALL 0xFF=without short address 0b0AAA AAA1=with short address
dali_send_double(&tx);
}
void dali_cmd_terminate()
{
Dali_msg_t tx;
tx = dali_msg_new(DALI_CMD_TERMINATE, 0x00);
dali_send(&tx);
}
// Reassign short addresses to all devices selected by device:
// - see dali_cmd_initialise(device)
void dali_reindex_addresses(uint8_t device)
{
Dali_msg_t tx;
uint32_t addr24 = 0;
printf("Start searching\n");
dali_cmd_initialise(device); // 0x00=ALL 0xFF=without short address 0b0AAA AAA1=with short address
for(uint8_t i=0; i<63; i++)
{
addr24 = dali_binary_search();
if(addr24 == 0xFFFFFF) break;
uint8_t device = dali_short_address(i);
dali_set_search_addr24(addr24);
tx = dali_msg_new(DALI_CMD_PROGRAM_SHORT, device);
dali_send(&tx);
printf("Assign address: 0x%06zX --> %d\n", (size_t)addr24, i);
dali_cmd_withdraw(addr24);
}
dali_cmd_terminate();
printf("End searching\n");
}
void dali_change_short_address(int addr1, int addr2)
{
Dali_msg_t tx;
uint8_t device1, device2;
device1 = dali_short_address(addr1 & 0x3F);
if(addr2 == -1) device2 = DALI_ADR_BROADCAST; // delete short address
else if(addr2 < 0 || addr2 > 63) {
printf("Invalid address: %d\n", addr2);
return;
}
else {
device2 = dali_short_address(addr2 & 0x3F);
}
int32_t addr24 = dali_cmd_query_rand_addr24(addr1);
if(addr24 == -1) {
printf("Cannot read address 24 for %d\n", addr1);
return;
}
dali_cmd_initialise(device1);
dali_set_search_addr24(addr24);
tx = dali_msg_new(DALI_CMD_PROGRAM_SHORT, device2);
dali_send(&tx);
dali_cmd_terminate();
printf("Address changed: %d --> %d\n", addr1, addr2);
}
+934
View File
@@ -0,0 +1,934 @@
#include <stdio.h>
#include <memory.h>
//ESP-IDF HAL
#ifdef CONFIG_IDF_TARGET
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/portmacro.h"
#include "esp_system.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "esp_timer.h"
#include "driver/gptimer.h"
#include "dali_hal.h"
#ifndef CONFIG_DALI_API_QUEUE_LEN
#define CONFIG_DALI_API_QUEUE_LEN 10
#endif
#ifndef CONFIG_DALI_TX_QUEUE_LEN
#define CONFIG_DALI_TX_QUEUE_LEN 1
#endif
#ifndef CONFIG_DALI_TX_REPLY_QUEUE_LEN
#define CONFIG_DALI_TX_REPLY_QUEUE_LEN 4
#endif
#ifndef CONFIG_DALI_RX_QUEUE_LEN
#define CONFIG_DALI_RX_QUEUE_LEN 50
#endif
#ifndef CONFIG_DALI_DEBUG_QUEUE_LEN
#define CONFIG_DALI_DEBUG_QUEUE_LEN 100
#endif
#ifndef CONFIG_DALI_DALI_TASK_STACK_SIZE
#define CONFIG_DALI_DALI_TASK_STACK_SIZE 2048
#endif
#ifndef CONFIG_DALI_DALI_TASK_PRIORITY
#define CONFIG_DALI_DALI_TASK_PRIORITY 2
#endif
#ifndef CONFIG_DALI_DEBUG_TASK_STACK_SIZE
#define CONFIG_DALI_DEBUG_TASK_STACK_SIZE 2048
#endif
#ifndef CONFIG_DALI_DEBUG_TASK_PRIORITY
#define CONFIG_DALI_DEBUG_TASK_PRIORITY 1
#endif
#define WITHIN_RANGE(x, min, max) ((x) > (min) && (x) < (max))
#define MAX_DELTA_RELOAD_TIME 600000000 // 600s - max u32: 4,294,967,295~4,294s
#define DALI_BAUDRATE_MIN 400U
#define DALI_BAUDRATE_MAX 2400U
typedef struct {
uint32_t hb;
uint32_t rx_hb_min;
uint32_t rx_hb_max;
uint32_t rx_2hb_min;
uint32_t rx_2hb_max;
uint32_t rx_stop_cond;
uint32_t time_bus_down;
uint32_t time_break_min;
uint32_t time_recovery_min;
uint32_t tx_stop_cond;
uint32_t collision_txrx_delta;
} dali_timing_t;
static const char *TAG = "dali_hal";
typedef struct {
uint8_t bus_id;
uint8_t tx_pin;
uint8_t rx_pin;
dali_tx_state_t tx_state;
dali_rx_state_t rx_state;
dali_bus_state_t bus_state;
uint64_t rx_last_edge_time;
uint64_t tx_last_edge_time;
uint32_t rx_tx_delta;
uint32_t rx_pulse_width;
uint8_t rx_level;
Dali_msg_t tx_data;
Dali_msg_t rx_data;
uint8_t tx_half_bit_counter;
uint8_t tx_data_bit_counter;
uint8_t rx_half_bit_counter;
uint8_t rx_data_bit_counter;
QueueHandle_t tx_queue;
QueueHandle_t tx_reply_queue;
QueueHandle_t rx_queue;
bool inited;
} dali_bus_ctx_t;
static volatile dali_timing_t s_timing;
static uint32_t s_baudrate = DALI_DEFAULT_BAUDRATE;
static bool s_timing_ready = false;
static portMUX_TYPE s_timing_spinlock = portMUX_INITIALIZER_UNLOCKED;
static SemaphoreHandle_t s_hal_mutex;
static gptimer_handle_t gptimer = NULL;
static SemaphoreHandle_t dali_hal_mutex(void)
{
if (s_hal_mutex == NULL) {
s_hal_mutex = xSemaphoreCreateRecursiveMutex();
}
return s_hal_mutex;
}
static inline void dali_hal_lock(void)
{
SemaphoreHandle_t mtx = dali_hal_mutex();
if (mtx) {
xSemaphoreTakeRecursive(mtx, portMAX_DELAY);
}
}
static inline void dali_hal_unlock(void)
{
if (s_hal_mutex) {
xSemaphoreGiveRecursive(s_hal_mutex);
}
}
static uint32_t dali_half_bit_from_baud(uint32_t baudrate)
{
if (baudrate < DALI_BAUDRATE_MIN || baudrate > DALI_BAUDRATE_MAX) {
return 0;
}
uint64_t hb = 500000ULL + (baudrate / 2U); // round to nearest
hb /= baudrate;
if (hb == 0 || hb > 2000000ULL) { // should never happen with checked bounds
return 0;
}
return (uint32_t)hb;
}
static inline uint32_t scale_time_by_hb(uint32_t base_us, uint32_t hb_us)
{
return (uint32_t)(((uint64_t)base_us * hb_us + (DALI_TIME_HB / 2U)) / DALI_TIME_HB);
}
static esp_err_t update_timing_locked(uint32_t baudrate)
{
uint32_t hb = dali_half_bit_from_baud(baudrate);
if (hb == 0) {
ESP_LOGE(TAG, "invalid baudrate: %u", baudrate);
return ESP_ERR_INVALID_ARG;
}
dali_timing_t new_timing = {
.hb = hb,
.rx_hb_min = scale_time_by_hb(DALI_RX_HB_MIN, hb),
.rx_hb_max = scale_time_by_hb(DALI_RX_HB_MAX, hb),
.rx_2hb_min = scale_time_by_hb(DALI_RX_2HB_MIN, hb),
.rx_2hb_max = scale_time_by_hb(DALI_RX_2HB_MAX, hb),
.rx_stop_cond = scale_time_by_hb(DALI_RX_STOP_COND, hb),
.time_bus_down = scale_time_by_hb(DALI_TIME_BUS_DOWN, hb),
.time_break_min = scale_time_by_hb(DALI_TIME_BREAK_MIN, hb),
.time_recovery_min = scale_time_by_hb(DALI_TIME_RECOVERY_MIN, hb),
.tx_stop_cond = scale_time_by_hb(DALI_TX_STOP_COND, hb),
.collision_txrx_delta = scale_time_by_hb(DALI_COLLISION_TXRX_DELTA, hb),
};
portENTER_CRITICAL(&s_timing_spinlock);
s_timing = new_timing;
s_baudrate = baudrate;
s_timing_ready = true;
portEXIT_CRITICAL(&s_timing_spinlock);
return ESP_OK;
}
static esp_err_t ensure_timing_ready_locked(void)
{
if (s_timing_ready) {
return ESP_OK;
}
return update_timing_locked(s_baudrate);
}
static esp_err_t apply_timer_alarm_locked(void)
{
if (!gptimer) {
return ESP_OK;
}
gptimer_alarm_config_t timer_alarm_config = {
.alarm_count = s_timing.hb,
.reload_count = 0,
.flags = {
.auto_reload_on_alarm = true,
},
};
return gptimer_set_alarm_action(gptimer, &timer_alarm_config);
}
// public queue for sending and receiving data
QueueHandle_t dali_send_queue; // [Dali_msg_t] from other tasks to dali task
QueueHandle_t dali_send_reply_queue; // [Dali_msg_t] from dali task to other tasks
QueueHandle_t dali_receive_queue; // alias to bus 0 for compatibility
QueueHandle_t dali_receive_queues[DALI_PHY_COUNT];
// internal queues for debug data
struct Dali_rx_dbg_data {
uint32_t rx_pulse_width;
uint32_t rx_tx_delta;
uint8_t level;
uint8_t bus_id;
};
typedef struct Dali_rx_dbg_data Dali_rx_dbg_data_t;
static QueueHandle_t rx_dbg_queue; // [Dali_rx_dbg_data_t] from GPIO ISR to debug task, data:
uint8_t rx_debug_enabled = 0; // enable debug data - set to 1 in main.c
static dali_bus_ctx_t s_bus[DALI_PHY_COUNT];
static bool s_timer_started = false;
static bool s_timer_configured = false;
static bool s_timer_enabled = false;
#if CONFIG_DALI_ENABLE_DEBUG_TASK
static bool s_debug_task_created = false;
#endif
static bool s_dali_task_created = false;
#define DALI_SET_BUS_HIGH(bus) gpio_set_level((bus)->tx_pin, DALI_TX_HIGH) // set bus level
#define DALI_SET_BUS_LOW(bus) gpio_set_level((bus)->tx_pin, DALI_TX_LOW) // set bus level
#define DALI_SET_BUS_LEVEL(bus, x) gpio_set_level((bus)->tx_pin, ((x)==DALI_TX_HIGH)) // set bus level
// !!! read from RX pin, we need real bus level, not logic level of TX pin
// return: 0 - bus level low, active state
// 1 - bus level high, idle state
#define DALI_GET_BUS_LEVEL(bus) (gpio_get_level((bus)->rx_pin) == (DALI_RX_HIGH)) // get bus level
// return: 0 - tx pin drive bus low, active state
// 1 - tx pin drive bus high, idle state
#define DALI_GET_TX_LEVEL(bus) (gpio_get_level((bus)->tx_pin) == (DALI_TX_HIGH)) // get TX pin level
static inline bool bus_valid(uint8_t bus_id) {
return bus_id < DALI_PHY_COUNT && s_bus[bus_id].inited;
}
// GPIO ISR handler
// define rx_gpio_isr_handler on any edge
static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
{
dali_bus_ctx_t *bus = (dali_bus_ctx_t *)arg;
if (bus == NULL || !bus->inited) {
return;
}
uint64_t rx_current_edge_time = esp_timer_get_time(); // get time in us
uint8_t rx_previous_level = bus->rx_level;
Dali_rx_dbg_data_t dbg;
// rx_level = 1 if and only if DALI bus is really high, idle
bus->rx_level = DALI_GET_BUS_LEVEL(bus); // get level of RX pin - not depend on hw: 0 - low, 1 - high
bus->rx_pulse_width = rx_current_edge_time - bus->rx_last_edge_time; // time from last edge
bus->rx_tx_delta = rx_current_edge_time - bus->tx_last_edge_time; // time from last edge
// always save time of last edge
bus->rx_last_edge_time = rx_current_edge_time; // get time in us
if(bus->bus_state == DALI_BUS_READY && bus->rx_level == 0) // found start bit
{
// within range for backward frame
uint32_t time_ms = bus->rx_pulse_width / 1000; // 1ms = 1000us
if(time_ms>255) bus->rx_data.type = 255;
else bus->rx_data.type = time_ms; // pulse width in ms for later identification of type msg
bus->rx_data.id = bus->bus_id; // store bus id
bus->rx_data.length = 0;
bus->rx_data.status = DALI_FRAME_UNKNOWN; // on start in unknown state
memset(bus->rx_data.data, 0, DALI_MAX_BYTES); // clear data
bus->rx_state = RX_STATE_START; // start receiving
bus->bus_state = DALI_BUS_RECEIVING; // bus is receiving
bus->rx_half_bit_counter = 0;
bus->rx_data_bit_counter = 0; // actually received bits count
}
else if(bus->bus_state == DALI_BUS_RECEIVING)
{
if(bus->rx_state == RX_STATE_START) {
// start bit always has width HB and level 1
if(bus->rx_level == 1 && WITHIN_RANGE(bus->rx_pulse_width, s_timing.rx_hb_min, s_timing.rx_hb_max)) {
bus->rx_state = RX_STATE_DATA; // start receiving data
bus->rx_half_bit_counter++;
}
else {
bus->bus_state = DALI_BUS_ERROR; // not in range
bus->rx_data.status = DALI_FRAME_TIME_VIOLATION;
goto end_rx_isr;
}
}
else if(bus->rx_state == RX_STATE_DATA) {
// for long gap there are two valid cases:
// 1. previus_level == 1 and current_level == 0 then result is 0 bit
// 2. previus_level == 0 and current_level == 1 then result is 1 bit
// if for whatever reason we get the same level as previous we are in error, eg we missed edge
if(rx_previous_level == bus->rx_level) {
bus->rx_state = RX_STATE_ERROR; // invalid bit
bus->rx_data.status = DALI_FRAME_SEQUENCE_ERROR;
goto end_rx_isr;
}
if(WITHIN_RANGE(bus->rx_pulse_width, s_timing.rx_2hb_min, s_timing.rx_2hb_max))
{
if((bus->rx_half_bit_counter & 0x01) == 0) { // even half bit
bus->rx_state = RX_STATE_ERROR; // invalid bit
goto end_rx_isr;
}
if(rx_previous_level == 1) { // edge:1->0 value 0, skip because data[] is already set to 0
}
else { // edge:0->1 value 1
if(bus->rx_level == 1) { // result to: 1
bus->rx_data.data[bus->rx_data_bit_counter/8] |= (1 << (7 - (bus->rx_data_bit_counter % 8))); // set bit
}
}
bus->rx_half_bit_counter+=2; // 3,5,7...
bus->rx_data_bit_counter++; // increment bit counter
}
else if(WITHIN_RANGE(bus->rx_pulse_width, s_timing.rx_hb_min, s_timing.rx_hb_max))
{
if(bus->rx_half_bit_counter & 0x01) { // first half of bit
bus->rx_half_bit_counter++;
}
else { // second half of bit
bus->rx_half_bit_counter++;
if(rx_previous_level == 1) { // edge:1->0 value 0 - skip because data[] is already set to 0
}
else { // edge:0->1 value 1
if(bus->rx_level == 1) { // 0->1 is 1
bus->rx_data.data[bus->rx_data_bit_counter/8] |= (1 << (7 - (bus->rx_data_bit_counter % 8))); // set bit
}
}
bus->rx_data_bit_counter++; // increment bit counter - only on second half
}
if(bus->rx_data_bit_counter >= DALI_MAX_BITS) {
bus->rx_state = RX_STATE_STOP; // stop receiving - we cannot receive more bits
}
}
else {
bus->rx_data.status = DALI_FRAME_TIME_VIOLATION;
bus->rx_state = RX_STATE_ERROR; // invalid bit
goto end_rx_isr;
}
}
}
// if collision detected: we are too late after bit was transmitted
else if (bus->bus_state == DALI_BUS_TRANSMITTING && bus->rx_tx_delta > s_timing.collision_txrx_delta)
{
// we need now to start collision recovery with time break: 101.9.2.4
DALI_SET_BUS_LOW(bus); // force TX low - active state, inform about collision, this also generate new GPIO ISR
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
bus->bus_state = DALI_BUS_TIME_BREAK; // we are in time break state
bus->tx_data.status = DALI_FRAME_COLLISION; // collision detected
}
else if (bus->bus_state == DALI_BUS_RECOVERY)
{ // we should not receive data during recover
bus->bus_state = DALI_BUS_ERROR; // not idle wait for idle state or cancel transmission and start receiving
}
end_rx_isr:
if(rx_debug_enabled & 0x02) {
dbg.level = bus->rx_level;
dbg.rx_pulse_width = bus->rx_pulse_width;
dbg.rx_tx_delta = bus->rx_tx_delta;
dbg.bus_id = bus->bus_id;
xQueueSendToBackFromISR(rx_dbg_queue, &dbg, NULL); // send data to queue
}
}
// ---------------------------------------------------
// HW TIMER ISR
// is triggered at interval of 416us (half bit)
static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
{
BaseType_t yield = false;
uint32_t rx_delta = time_now - bus->rx_last_edge_time; // time from last edge
uint32_t tx_delta = time_now - bus->tx_last_edge_time; // time from last edge
if(rx_delta > MAX_DELTA_RELOAD_TIME)
{
bus->rx_last_edge_time = time_now - MAX_DELTA_RELOAD_TIME/2; // half of max time
}
if(tx_delta > MAX_DELTA_RELOAD_TIME)
{
bus->tx_last_edge_time = time_now - MAX_DELTA_RELOAD_TIME/2; // half of max time
}
// recovery from different error states: UNKNOWN, ERROR, POWER_DOWN
if(bus->bus_state <= DALI_BUS_ERROR)
{ // 101.8.2.4 - startup BUS after 2.4ms
if(bus->rx_level==1 && rx_delta > s_timing.rx_stop_cond)
{
bus->bus_state = DALI_BUS_READY; // bus is ready
}
}
// if bus power down - if bus is low for more then 45ms
if(bus->rx_level==0 && rx_delta > s_timing.time_bus_down)
{ // power lost
bus->bus_state = DALI_BUS_POWER_DOWN; // bus is power down - recovery see previous if
DALI_SET_BUS_HIGH(bus); // make sure TX is high
}
// recovery from collision detection
// if end of TIME BREAK BUS is LOW - collision recovery: 101.9.2.4 tab 25
// BUS: IDLE, HIGH - we are last device who keep bus low, if we want to restart transmission wait for TIME RECOVERY
// BUS: ACTIVE, LOW - BUS is busy, let caller to restart transmission
if(bus->bus_state == DALI_BUS_TIME_BREAK && rx_delta > s_timing.time_break_min)
{
DALI_SET_BUS_HIGH(bus); // TX high - idle state - generate ISR on RX pin
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
// read bus state
if(DALI_GET_BUS_LEVEL(bus) == 0) // other device is keeping bus low
{
// wait for bus to be idle before starting recovery - caller will restart transmission
bus->bus_state = DALI_BUS_ERROR; // bus is in multi master state: 101.9.2.4 Fig 17
}
else
{
// bus is free - we are the last one who keeps bus low, we can start recovery
bus->bus_state = DALI_BUS_RECOVERY; // start recovery for 4ms: 101.9.2.4 tab 25
// TODO: add check if we want to restart transmission !!!
}
}
else if(bus->bus_state == DALI_BUS_RECOVERY && rx_delta > s_timing.time_recovery_min)
{
bus->bus_state = DALI_BUS_READY; // bus is ready
// immediately start transmitting if we have data
}
// start transmitting
else if(bus->bus_state == DALI_BUS_READY && bus->tx_state == TX_STATE_IDLE && bus->tx_queue && xQueueReceiveFromISR(bus->tx_queue, &bus->tx_data, NULL) == pdTRUE)
{
bus->tx_data.status = DALI_FRAME_ERROR; // error status - will be set ok on success
bus->bus_state = DALI_BUS_TRANSMITTING; // bus is transmitting
bus->tx_state = TX_STATE_START; // start transmitting
bus->tx_half_bit_counter = 0;
bus->tx_data_bit_counter = 0; // actually sent bits count
DALI_SET_BUS_LOW(bus); // start bit first half
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
}
else if(bus->bus_state == DALI_BUS_TRANSMITTING)
{
// transmit data
if(bus->tx_state == TX_STATE_START) {
bus->tx_state = TX_STATE_DATA; // start transmitting data
bus->tx_half_bit_counter++;
DALI_SET_BUS_HIGH(bus); // start bit second half
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
}
else if(bus->tx_state == TX_STATE_DATA) {
bool value = (bus->tx_data.data[bus->tx_data_bit_counter/8] >> ( 7 - (bus->tx_data_bit_counter % 8) )) & 0x01;
value ^= bus->tx_half_bit_counter & 0x01; // xor=invert value for odd half bit 1:0->1 and 0:1->0
DALI_SET_BUS_LEVEL(bus, value);
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
bus->tx_half_bit_counter++; // increment half bit counter before next test
if(bus->tx_half_bit_counter & 0x01) { // next bit
bus->tx_data_bit_counter++;
if(bus->tx_data_bit_counter >= bus->tx_data.length) {
bus->tx_state = TX_STATE_STOP;
}
}
}
else if(bus->tx_state == TX_STATE_STOP) {
// here we check TX (NOT RX) bit state
if(DALI_GET_TX_LEVEL(bus) == 0) // really ok - otherwise we will keep bus low forever
{
DALI_SET_BUS_HIGH(bus);
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
}
else if(tx_delta > s_timing.tx_stop_cond) {
bus->tx_data.status = DALI_FRAME_OK; // frame is OK
if (bus->tx_reply_queue) {
xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, &yield); // send data to queue
}
bus->tx_state = TX_STATE_IDLE; // final state with transmitted data
bus->bus_state = DALI_BUS_READY; // bus is ready
}
}
}
else if(bus->bus_state == DALI_BUS_READY && bus->tx_state > TX_STATE_IDLE)
{
// we are not transmitting but we have data - reply to queue and let error state in tx_data.status
if (bus->tx_reply_queue) {
xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, &yield); // send data to queue
}
bus->tx_state = TX_STATE_IDLE; // clear state
}
// recover receiving
if(bus->bus_state == DALI_BUS_RECEIVING) {
if(bus->rx_state == RX_STATE_ERROR) {
// wait until bus is idle
if(rx_delta > s_timing.rx_stop_cond) { // minimum time 2400us
bus->rx_state = RX_STATE_IDLE;
bus->bus_state = DALI_BUS_READY;
// rx_data.status = DALI_FRAME_ERROR; // should be set inside ISR
bus->rx_data.length = bus->rx_data_bit_counter; // set length of data
// rx_data.data[0] = 0xAA; // debug
if (bus->rx_queue) {
xQueueSendToBackFromISR(bus->rx_queue, &bus->rx_data, &yield); // send data to queue
}
}
}
else if(bus->rx_state == RX_STATE_DATA || bus->rx_state == RX_STATE_STOP) {
// wait until bus is idle
if(rx_delta > s_timing.rx_stop_cond) { // minimum time 2400us
bus->rx_state = RX_STATE_IDLE;
bus->bus_state = DALI_BUS_READY;
bus->rx_data.status = DALI_FRAME_OK; // frame is OK
bus->rx_data.length = bus->rx_data_bit_counter; // set length of data
// rx_data.data[0] = 0xBB; // debug
if (bus->rx_queue) {
xQueueSendToBackFromISR(bus->rx_queue, &bus->rx_data, &yield); // send data to queue
}
}
}
}
return yield;
}
// HW TIMER ISR wrapper
bool IRAM_ATTR hw_timer_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *arg)
{
bool yield = false;
uint64_t time_now = esp_timer_get_time();
for (int i = 0; i < DALI_PHY_COUNT; i++) {
if (s_bus[i].inited) {
yield |= handle_bus_timer(&s_bus[i], time_now);
}
}
return yield;
}
// ---------------------------------------------------
// should be at lowest priority
#if CONFIG_DALI_ENABLE_DEBUG_TASK
static void debug_task(void *pvParameters)
{
Dali_rx_dbg_data_t dbg;
static uint8_t i=0;
static uint8_t HB=0; // half bit counter
char v = '*'; // interpret value
while (1)
{
if(xQueueReceive(rx_dbg_queue, &dbg, portMAX_DELAY) == pdTRUE) // wait forever
{
if(dbg.rx_pulse_width > 1000) { i=0; HB=0; v='*';}
else if ((HB & 0x01) == 1) {
if(HB == 2) v = 'S';
else v = '0' + dbg.level;
}
else v=' ';
printf("bus[%u] rx: [%2d] pw=%lu v=%u rtd=%lu [%c]",
dbg.bus_id, i++, dbg.rx_pulse_width, dbg.level, dbg.rx_tx_delta, v);
if(dbg.rx_pulse_width < 1000) {
if(dbg.rx_pulse_width > 550) HB+=2;
else HB++;
}
}
}
}
#endif
static esp_err_t ensure_isr_service(void)
{
static bool installed = false;
if (installed) {
return ESP_OK;
}
esp_err_t err = gpio_install_isr_service(0);
if (err == ESP_ERR_INVALID_STATE) {
installed = true;
return ESP_OK;
}
if (err == ESP_OK) {
installed = true;
}
return err;
}
static void ensure_common_queues(void)
{
if (!dali_send_queue) {
dali_send_queue = xQueueCreate(CONFIG_DALI_API_QUEUE_LEN, sizeof(Dali_msg_t));
}
if (!dali_send_reply_queue) {
dali_send_reply_queue = xQueueCreate(CONFIG_DALI_API_QUEUE_LEN, sizeof(Dali_msg_t));
}
if (!rx_dbg_queue) {
rx_dbg_queue = xQueueCreate(CONFIG_DALI_DEBUG_QUEUE_LEN, sizeof(Dali_rx_dbg_data_t));
}
#if CONFIG_DALI_ENABLE_DEBUG_TASK
if (!s_debug_task_created) {
xTaskCreate(debug_task, "debug_task", CONFIG_DALI_DEBUG_TASK_STACK_SIZE, NULL, CONFIG_DALI_DEBUG_TASK_PRIORITY, NULL); // at low priority !!!
s_debug_task_created = true;
}
#endif
}
static esp_err_t ensure_timer_started(void)
{
dali_hal_lock();
if (s_timer_started) {
dali_hal_unlock();
return ESP_OK;
}
esp_err_t err = ensure_timing_ready_locked();
if (err != ESP_OK) {
dali_hal_unlock();
return err;
}
if (gptimer == NULL) {
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.intr_priority = 0,
.flags = {
.intr_shared = true,
},
.resolution_hz = 1 * 1000 * 1000, // 1MHz, 1 tick = 1us
};
err = gptimer_new_timer(&timer_config, &gptimer);
if (err != ESP_OK) {
dali_hal_unlock();
return err;
}
}
if (!s_timer_configured) {
gptimer_event_callbacks_t cbs = {
.on_alarm = hw_timer_callback,
};
err = gptimer_register_event_callbacks(gptimer, &cbs, NULL);
if (err != ESP_OK) {
dali_hal_unlock();
return err;
}
s_timer_configured = true;
}
err = apply_timer_alarm_locked();
if (err != ESP_OK) {
dali_hal_unlock();
return err;
}
if (!s_timer_enabled) {
err = gptimer_enable(gptimer);
if (err != ESP_OK) {
dali_hal_unlock();
return err;
}
s_timer_enabled = true;
}
err = gptimer_start(gptimer);
if (err != ESP_OK) {
dali_hal_unlock();
return err;
}
s_timer_started = true;
dali_hal_unlock();
return ESP_OK;
}
static esp_err_t init_bus(uint8_t bus_id, uint8_t tx_pin, uint8_t rx_pin)
{
if (bus_id >= DALI_PHY_COUNT) {
return ESP_ERR_INVALID_ARG;
}
esp_err_t err = ESP_OK;
dali_bus_ctx_t *bus = &s_bus[bus_id];
if (bus->inited) {
if (bus->tx_pin == tx_pin && bus->rx_pin == rx_pin) {
return ESP_OK;
}
return ESP_ERR_INVALID_STATE;
}
ensure_common_queues();
bus->bus_id = bus_id;
bus->tx_pin = tx_pin;
bus->rx_pin = rx_pin;
gpio_config_t io_conf;
// DALI TX
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = BIT(bus->tx_pin);
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
err = gpio_config(&io_conf);
if (err != ESP_OK) {
return err;
}
// DALI RX
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = BIT(bus->rx_pin);
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0; // no pull-up or pull-down - bus has default pull-down
err = gpio_config(&io_conf);
if (err != ESP_OK) {
return err;
}
// set initial state
DALI_SET_BUS_HIGH(bus); // TX high - idle state
if (!bus->tx_queue) {
bus->tx_queue = xQueueCreate(CONFIG_DALI_TX_QUEUE_LEN, sizeof(Dali_msg_t));
}
if (!bus->tx_reply_queue) {
bus->tx_reply_queue = xQueueCreate(CONFIG_DALI_TX_REPLY_QUEUE_LEN, sizeof(Dali_msg_t));
}
if (!bus->rx_queue) {
bus->rx_queue = xQueueCreate(CONFIG_DALI_RX_QUEUE_LEN, sizeof(Dali_msg_t));
dali_receive_queues[bus_id] = bus->rx_queue;
if (bus_id == 0) {
dali_receive_queue = bus->rx_queue; // backward compatibility
}
}
bus->rx_last_edge_time = esp_timer_get_time(); // get time in us - startup time
bus->rx_level = DALI_GET_BUS_LEVEL(bus); // get level of RX pin
bus->tx_last_edge_time = bus->rx_last_edge_time;
err = ensure_isr_service();
if (err != ESP_OK) {
return err;
}
err = gpio_set_intr_type(bus->rx_pin, GPIO_INTR_ANYEDGE);
if (err != ESP_OK) {
return err;
}
err = gpio_isr_handler_add(bus->rx_pin, rx_gpio_isr_handler, bus); // add isr handler for specific gpio pin
if (err != ESP_OK) {
return err;
}
bus->bus_state = DALI_BUS_READY;
bus->tx_state = TX_STATE_IDLE;
bus->rx_state = RX_STATE_IDLE;
bus->inited = true;
return ESP_OK;
}
esp_err_t dali_init_ports(uint8_t _dali_tx_pin, uint8_t _dali_rx_pin)
{
return dali_hal_init(0, _dali_tx_pin, _dali_rx_pin);
}
// communication with ISR - send data to queue and wait for reply
static int dali_tx_bus(dali_bus_ctx_t *bus, Dali_msg_t *dali_msg)
{
if (bus == NULL || !bus->inited) {
return ESP_FAIL;
}
if(xQueueSendToBack(bus->tx_queue, dali_msg, pdMS_TO_TICKS(50)) == pdFALSE) {
xQueueReset(bus->tx_queue); // clear queue
printf("dali_tx: Queue full\n");
return ESP_FAIL;
}
if(xQueueReceive(bus->tx_reply_queue, dali_msg, pdMS_TO_TICKS(50)) == pdFALSE) {
xQueueReset(bus->tx_reply_queue); // clear queue
printf("dali_tx: No reply\n");
return ESP_FAIL;
}
return ESP_OK;
}
// dali_task - should run at highest priority
void dali_task(void *pvParameters)
{
Dali_msg_t dali_msg;
while (1)
{
// data from queue -> copy to local variable
if(xQueueReceive(dali_send_queue, &dali_msg, portMAX_DELAY) == pdTRUE) {
uint8_t bus_id = dali_msg.id;
if (!bus_valid(bus_id)) {
bus_id = 0;
}
dali_msg.id = bus_id;
dali_bus_ctx_t *bus = &s_bus[bus_id];
dali_tx_bus(bus, &dali_msg);
// send data to queue
xQueueSendToBack(dali_send_reply_queue, &dali_msg, 0);
}
}
}
esp_err_t dali_hal_init(uint8_t dali_id, uint8_t tx_pin, uint8_t rx_pin)
{
dali_hal_lock();
esp_err_t err = ensure_timing_ready_locked();
if (err == ESP_OK) {
ensure_common_queues();
if (!s_dali_task_created) {
BaseType_t created = xTaskCreate(dali_task, "dali_task", CONFIG_DALI_DALI_TASK_STACK_SIZE, NULL, CONFIG_DALI_DALI_TASK_PRIORITY, NULL); // at high priority
if (created != pdPASS) {
err = ESP_FAIL;
} else {
s_dali_task_created = true;
}
}
}
if (err == ESP_OK) {
err = init_bus(dali_id, tx_pin, rx_pin);
}
if (err == ESP_OK) {
err = ensure_timer_started();
}
dali_hal_unlock();
return err;
}
esp_err_t dali_hal_set_baudrate(uint32_t baudrate)
{
dali_hal_lock();
bool resume_timer = s_timer_started && gptimer;
if (resume_timer) {
esp_err_t stop_ret = gptimer_stop(gptimer);
if (stop_ret != ESP_OK) {
dali_hal_unlock();
return stop_ret;
}
s_timer_started = false;
}
esp_err_t err = update_timing_locked(baudrate);
if (err == ESP_OK && gptimer) {
err = apply_timer_alarm_locked();
}
if (resume_timer && gptimer) {
esp_err_t restart_ret = gptimer_start(gptimer);
if (restart_ret == ESP_OK) {
s_timer_started = true;
} else if (err == ESP_OK) {
err = restart_ret;
}
}
dali_hal_unlock();
return err;
}
uint32_t dali_hal_get_baudrate(void)
{
dali_hal_lock();
uint32_t baud = s_baudrate;
dali_hal_unlock();
return baud;
}
size_t dali_hal_get_inited_buses(uint8_t *ids, size_t max_ids)
{
dali_hal_lock();
size_t count = 0;
for (uint8_t i = 0; i < DALI_PHY_COUNT; i++) {
if (s_bus[i].inited) {
if (ids && count < max_ids) {
ids[count] = i;
}
count++;
}
}
dali_hal_unlock();
return count;
}
esp_err_t dali_hal_get_bus_info(uint8_t bus_id, dali_hal_bus_info_t *info)
{
if (info == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (bus_id >= DALI_PHY_COUNT) {
return ESP_ERR_INVALID_ARG;
}
dali_hal_lock();
info->bus_id = bus_id;
info->tx_pin = s_bus[bus_id].tx_pin;
info->rx_pin = s_bus[bus_id].rx_pin;
info->inited = s_bus[bus_id].inited;
dali_hal_unlock();
return ESP_OK;
}
#endif // CONFIG_IDF_TARGET
+81
View File
@@ -0,0 +1,81 @@
#pragma once
#include <stdint.h>
#include "dali_hal.h"
/*
Addressing: 102.7.2.1
0AAA AAAx - short address AAAAAA 0-63
100G GGGx - group address GGG 0-15
1111 1101 - broadcast unaddressed
1111 1111 - broadcast
x - 0 - direct arc power control (DAPC), 1 - standard command
*/
// create generic DALI message - for any bit length
Dali_msg_t dali_msg_new_generic(uint8_t bit_length, uint8_t address, uint8_t cmd1, uint8_t cmd2, uint8_t cmd3);
// create standard DALI message: 16,24,32 bits
Dali_msg_t dali_msg_new(uint8_t address, uint8_t cmd1);
Dali_msg_t dali_msg_new_3B(uint8_t address, uint8_t cmd1, uint8_t cmd2);
Dali_msg_t dali_msg_new_4B(uint8_t address, uint8_t cmd1, uint8_t cmd2, uint8_t cmd3);
// short address 0-63 - 6 bits mask 0b00111111=0x3F
// - create device address from short address
// - device address: 0b 0AAA AAA1
inline uint8_t dali_short_address(uint8_t address) {
return ((address & 0x3F) << 1) | 0x01;
}
// group address 0-15 - 4 bits mask 0b00001111=0x0F
// - create group address from short address
// - group address: 0b100G GGG1
inline uint8_t dali_group_address(uint8_t grp) {
return ((grp & 0x0F) << 1) | 0x81;
}
void dali_send(Dali_msg_t *tx_msg);
void dali_send_double(Dali_msg_t *dali_msg);
int dali_query(Dali_msg_t *tx_msg, Dali_msg_t *rx_msg);
// DALI queries
uint32_t dali_cmd_query_rand_addr24(uint8_t addr);
void dali_find_all_short(uint8_t *dev, int with_print);
void dali_device_info(uint8_t addr);
void dali_device_identify(uint8_t device);
void dali_device_reset(uint8_t device);
// DALI commands
void dali_cmd(uint8_t device, uint8_t cmd);
void dali_cmd_double(uint8_t device, uint8_t cmd);
void dali_cmd_off(uint8_t device);
void dali_cmd_on(uint8_t device);
void dali_set_level(uint8_t device, uint8_t cmd, uint8_t level);
void dali_group(uint8_t device1, uint8_t cmd, uint8_t group);
void dali_set_dtr0(uint8_t value);
void dali_set_dtr1(uint8_t value);
void dali_set_dtr2(uint8_t value);
void dali_cmd_initialise(uint8_t device);
void dali_cmd_terminate();
void dali_cmd_randomize(uint8_t device);
void dali_cmd_withdraw(uint32_t addr24);
// DALI special commands
void dali_reindex_addresses(uint8_t device);
void dali_set_search_addr24(uint32_t addr24);
uint32_t dali_binary_search();
void dali_change_short_address(int addr1, int addr2);
+91
View File
@@ -0,0 +1,91 @@
#pragma once
#define DALI_ADR_BROADCAST 0b11111111
#define DALI_CMD_OFF 0x00 // 102.9.7.2 11.3.2: tagetLevel=0 & OFF
#define DALI_CMD_UP 0x01 // 102.11.3.3: for 200 ms up: tagetLevel calculated from actualLevel & fadeRate
#define DALI_CMD_DOWN 0x02 // 102.11.3.4: for 200 ms down:
#define DALI_CMD_STEP_UP 0x03 // 102.11.3.5: immediate one step up
#define DALI_CMD_STEP_DOWN 0x04 // 102.11.3.6: immediate one step down
#define DALI_CMD_RECALL_MAX 0x05 // 102.11.3.7: tagetLevel=actualLevel=maxLevel
// INIT: moreover & output 100%, see RECALL MIN to identify light
#define DALI_CMD_ON DALI_CMD_RECALL_MAX
#define DALI_CMD_RECALL_MIN 0x06 // 102.11.3.7: tagetLevel=actualLevel=minLevel
// INIT: moreover & output 0% or OFF, see RECALL MAX to identify light
#define DALI_CMD_STEP_DOWN_OFF 0x07 //
#define DALI_CMD_ON_STEP_UP 0x08 //
#define DALI_CMD_DAPC_SEQ 0x09 //
#define DALI_CMD_LAST_ACTIVE 0x0A //
#define DALI_CMD_CONTIN_UP 0x0B //
#define DALI_CMD_CONTIN_DOWN 0x0C //
#define DALI_CMD_GOTO_SCENE 0x10 // + scene number 0-15
#define DALI_CMD_RESET 0x20 // 102.9.11.1 11.4.2
#define DALI_CMD_ACTUAL_IN_DTR0 0x21 //
#define DALI_CMD_SET_OPERATING_MODE 0x23 //
#define DALI_CMD_IDENTIFY 0x25 // 102.9.14.3 11.4.6
// SCENE commands
#define DALI_CMD_GO_TO_SCENE 0x10 //
#define DALI_CMD_SET_SCENE 0x40 //
#define DALI_CMD_REMOVE_FROM_SCENE 0x50 //
// GROUP commands
#define DALI_CMD_ADD_TO_GROUP 0x60 //
#define DALI_CMD_REMOVE_FROM_GROUP 0x70 //
// SET commands
#define DALI_CMD_SET_MAX_LEVEL 0x2A // 102.9.6 11.4.7
#define DALI_CMD_SET_MIN_LEVEL 0x2B // 102.9.6 11.4.8
#define DALI_CMD_SET_SYS_FAIL_LEVEL 0x2C // 102.9.12 11.4.9
#define DALI_CMD_SET_PW_ON_LEVEL 0x2D // 102.9.13 11.4.10
#define DALI_CMD_SET_FADE_TIME 0x2E // 102.9.5.2 11.4.11
#define DALI_CMD_SET_FADE_RATE 0x2F // 102.9.5.3 11.4.12
#define DALI_CMD_SET_EXT_FADE_TIME 0x30 // 102.9.5.4 11.4.13
// QUERY commands
#define DALI_CMD_QUERY_STATUS 0x90 // 102.9.16 102.11.5.2
#define DALI_CMD_QUERY_DTR0 0x98 // 102.11.5.11
#define DALI_CMD_QUERY_DTR1 0x9C // 102.11.5.16
#define DALI_CMD_QUERY_DTR2 0x9D // 102.11.5.17
#define DALI_CMD_QUERY_OPERATING_MODE 0x9E // 102.9.9 102.11.5.18
#define DALI_CMD_QUERY_PHYSICAL_MIN 0x9A // 102.11.5.14
#define DALI_CMD_QUERY_ACTUAL_LEVEL 0xA0 // 102.11.5.20
#define DALI_CMD_QUERY_MAX_LEVEL 0xA1 // 102.11.5.21
#define DALI_CMD_QUERY_MIN_LEVEL 0xA2 // 102.11.5.22
#define DALI_CMD_QUERY_POWER_ON_LEVEL 0xA3 // 102.11.5.23
#define DALI_CMD_QUERY_SYSTEM_FAILURE_LEVEL 0xA4 // 102.11.5.24
#define DALI_CMD_QUERY_FADE_TIME 0xA5 // 102.11.5.25
#define DALI_CMD_QUERY_EXT_FADE_TIME 0xA8 // 102.11.5.65
#define DALI_CMD_QUERY_SCENE_LEVEL 0xB0 //
#define DALI_CMD_QUERY_GROUPS_0_7 0xC0 //
#define DALI_CMD_QUERY_GROUPS_8_15 0xC1 //
#define DALI_CMD_QUERY_RAND_ADDR_H 0xC2 // 102.11.4.31
#define DALI_CMD_QUERY_RAND_ADDR_M 0xC3 // 102.11.4.32
#define DALI_CMD_QUERY_RAND_ADDR_L 0xC4 // 102.11.4.33
// Special commands
#define DALI_CMD_TERMINATE 0xA1 // Releases the INITIALISE state.
#define DALI_CMD_DTR0 0xA3 // Data Transfer Register 0
#define DALI_CMD_INITIALISE 0xA5 // 2x Initialise 0x00=ALL 0xFF=without short address 0b0AAA AAA1=with short address
#define DALI_CMD_RANDOMISE 0xA7 // 2x Randomises the short address of the control gear.
#define DALI_CMD_COMPARE 0xA9 // Compares the stored short address with the received short address.
#define DALI_CMD_WITHDRAW 0xAB // Withdraws the control gear from the system.
#define DALI_CMD_PING 0xAD //
#define DALI_CMD_SEARCHADDRH 0xB1 // Search Address H
#define DALI_CMD_SEARCHADDRM 0xB3 // Search Address M
#define DALI_CMD_SEARCHADDRL 0xB5 // Search Address L
#define DALI_CMD_PROGRAM_SHORT 0xB7 // Program Short Address
#define DALI_CMD_VERIFY_SHORT 0xB9 // Verify Short Address
#define DALI_CMD_QUERY_SHORT 0xBB // Query Short Address
#define DALI_CMD_ENABLE_DEVICE 0xC1 // Enable Device Type
#define DALI_CMD_DTR1 0xC3 // Data Transfer Register 1
#define DALI_CMD_DTR2 0xC5 // Data Transfer Register 2
#define DALI_CMD_WRITE_MEM_LOC 0xC7 // Write Memory Location
#define DALI_CMD_WRITE_MEM_NR 0xC9 // Write Memory no reply
+228
View File
@@ -0,0 +1,228 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "sdkconfig.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#ifdef CONFIG_DALI_PHY_COUNT
#define DALI_PHY_COUNT CONFIG_DALI_PHY_COUNT
#else
#define DALI_PHY_COUNT 16
#endif
#define DALI_MAX_BYTES 4
#define DALI_MAX_BITS (DALI_MAX_BYTES * 8) // total bits
#ifdef CONFIG_DALI_DEFAULT_BAUDRATE
#define DALI_DEFAULT_BAUDRATE CONFIG_DALI_DEFAULT_BAUDRATE
#else
#define DALI_DEFAULT_BAUDRATE 1200
#endif
//
// DALI speed: 1200 bps = 833us/bit half period = 416us
// DALI coding 1 = low to high, 0 = high to low
//
// Check if hardware is capable of multi master, very restricted timing
// #define DALI_MULTIMASTER
// -------------------------------------------------------------------------------------
// Common Timing: 101.8.1.1 tab 16
#define DALI_TIME_HB 416 // us = HB - half bit
#define DALI_TIME_2HB 833 // us = 2*HB - full bit or double half bit
#define DALI_TIME_BUS_DOWN 45000 // us = 45ms = 108*HB bus power down time
// we use hardware timer for timing and crystal so values are very accurate and we assume 416us pulse width
#ifndef DALI_MULTIMASTER
// Transmit Timing: 101.8.1.1 tab 16
#define DALI_TX_HB_MIN 366 // us = 0.88*HB
#define DALI_TX_HB_MAX 467 // us = 1.12*HB
#define DALI_TX_2HB_MIN 733 // us = 0.88*2*HB
#define DALI_TX_2HB_MAX 934 // us = 1.12*2*HB
#else
// Multimaster transmission: 101.8.3.1 tab 21
#define DALI_TX_HB_MIN 400 // us = 0.96*HB
#define DALI_TX_HB_MAX 433 // us = 1.04*HB
#define DALI_TX_2HB_MIN 800 // us = 0.96*2*HB
#define DALI_TX_2HB_MAX 867 // us = 1.04*2*HB
#endif // DALI_MULTIMASTER
#define DALI_TX_STOP_COND 2450 // us = 2450/416 = 5.89 = 6*HB from last edge
// Transmit Timing: 101.8.1.2 tab 17
// backward frame are sent independently of bus state, even if bus is busy
#define DALI_TX_FF_BF_MIN 5500 // us = 5.5ms = 13*HB forward to backward frame time interval
#define DALI_TX_FF_BF_MAX 10500 // us = 10.5ms = 25*HB
#define DALI_TX_FF_FF_MIN 13500 // us = 13.5ms = 32*HB forward to forward frame time interval
#define DALI_TX_FF_FF_MAX 75500 // us = 75.5ms = 182*HB twice forward frame 101.9.4
// -------------------------------------------------------------------------------------
// Receiving Timing:
#ifndef DALI_MULTIMASTER
// Receiving Timing: 101.8.2.1 tab 18 and 19
#define DALI_RX_HB_MIN 333 // us = 0.8*HB
#define DALI_RX_HB_MAX 500 // us = 1.2*HB
#define DALI_RX_2HB_MIN 666 // us = 0.8*2*HB
#define DALI_RX_2HB_MAX 1000 // us = 1.2*2*HB
#else
// Multimaster reception: 101.9.2.3 tab 23 and 24
#define DALI_RX_HB_MIN 400 // us = 0.96*HB
#define DALI_RX_HB_MAX 433 // us = 1.04*HB
#define DALI_RX_2HB_MIN 800 // us = 0.96*2*HB
#define DALI_RX_2HB_MAX 867 // us = 1.04*2*HB
#endif // DALI_MULTIMASTER
#define DALI_RX_STOP_COND 2400 // us = 2400/416 = 5.77 = 6*HB from last edge
// Timing violation may inform about not identical backward frame: 101.9.6.2
// Timing violation for start bit
#define DALI_RX_START_TIMING_MIN 750 // us = 750/416 = 1.8 = 2*HB
#define DALI_RX_START_TIMING_MAX 1400 // us = 1400/416 = 3.4 = 4*HB
// Timing violation for other bits, see break time in collision detection
#define DALI_RX_BIT_TIMING_MIN 1200 // us = 1200/416 = 2.9 = 3*HB
#define DALI_RX_BIT_TIMING_MAX 1400 // us = 1400/416 = 3.4 = 4*HB
// collision recovery: 101.9.2.4 tab 25
#define DALI_TIME_BREAK_MIN 1200 // us = 1200/416 = 2.9 = 3*HB
#define DALI_TIME_RECOVERY_MIN 4000 // us = 4000/416 = 9.6 = 10*HB
// Receiver setting times: 101.8.2.4 tab 20
#define DALI_RX_FF_BF_MIN 2400 // us = 2400/416 = 5.77 = 6*HB forward to backward frame time interval
#define DALI_RX_FF_BF_MAX 12400 // us = 12400/416 = 29.8 = 30*HB
#define DALI_RX_BF_FF_MIN 2400 // us = 2400/416 = 5.77 = 6*HB backward to forward frame time interval
// -------------------------------------------------------------------------------------
// Collision detection:
// if we are transmitting we also listen to the bus in GPIO ISR, delay between TX and RX should be less then 84us
// - value is hardware specific but in worst case it should be 500-416=84us or 416-333=83us - we use 100us
// - multimaster is more restrictive 416-400=16us or 433-416=17us - we use 50us
#ifndef DALI_MULTIMASTER
#define DALI_COLLISION_TXRX_DELTA 100 // us = 100/416 = 0.24 = 1/4*HB
#else
#define DALI_COLLISION_TXRX_DELTA 50 // us = 50/416 = 0.12 = 1/8*HB
#endif // DALI_MULTIMASTER
typedef enum {
DALI_BUS_UNKNOWN = 0, // after startup
DALI_BUS_POWER_DOWN, // bus power down
DALI_BUS_ERROR, // states <= this state are all errors
DALI_BUS_READY, // bus powered - ready to transmit or receive
DALI_BUS_TRANSMITTING, // bus is transmitting
DALI_BUS_RECEIVING, // bus is receiving
DALI_BUS_TIME_BREAK, // bus is in time break after collision 101.9.2.4 tab 25, now is LOW=ACTIVE
DALI_BUS_RECOVERY, // bus is in recovery after collision, wait for bus to be free, now is HIGH=IDLE
} dali_bus_state_t;
typedef enum {
TX_STATE_ERROR = 0,
TX_STATE_COLLISION, // collision detected
TX_STATE_IDLE, // we are not transmitting
TX_STATE_START, // sending start bit
TX_STATE_DATA, // sending data bits
TX_STATE_STOP, // sending stop state
} dali_tx_state_t;
typedef enum {
RX_STATE_ERROR = 0,
RX_STATE_IDLE, // we are not receiving
RX_STATE_START,
RX_STATE_DATA,
RX_STATE_STOP,
RX_STATE_END, // final state with received data
} dali_rx_state_t;
// Frame types: 101.7.4
typedef enum {
DALI_MSG_FORWARD = 0, // 16,24,32 bits
DALI_MSG_BACKWARD, // 8 bits
DALI_MSG_RESERVED, // 20 bits - should not be used
DALI_MSG_PROPRIETARY, // other, 8-to-15 bits may conflict with backward frames, priority only 5 101.8.3.2 tab 22
} dali_msg_type_t;
// Message status
typedef enum {
DALI_FRAME_UNKNOWN = 0, // status not defined
DALI_FRAME_ERROR, // error in frame
DALI_FRAME_SEQUENCE_ERROR, // such frame should be ignored
DALI_FRAME_COLLISION, // such frame should be ignored
DALI_FRAME_SIZE_VIOLATION, // such frame should be ignored
DALI_FRAME_TIME_VIOLATION, // such frame should be ignored
DALI_FRAME_GRAY_AREA, // allow multiple interpretations - such frame should be ignored
DALI_FRAME_OK, // frame is OK
} dali_msg_status_t;
// size is 8 bytes
struct Dali_msg {
uint8_t id; // message ID (0-255)
uint8_t type; // [dali_msg_type_t] type of message (forward, backward, reserved, proprietary)
// receiving data contains time of reception from last edge change in [ms]
uint8_t status; // [dali_msg_status_t] status of message frame (0-255)
uint8_t length; // [1-32] length of data in bits: 8,16,24,32 bits or nonstandard eg 20 bits reserved
uint8_t data[DALI_MAX_BYTES];
} __attribute__((packed));
typedef struct Dali_msg Dali_msg_t;
// DEFINE in main.c and then stored in dali.c in global variables
// #define DALI_TX_PIN ?
// #define DALI_RX_PIN ?
// ----------------------------------------------
// define HW DALI for gpio functions
#ifndef DALI_HW_PINS
// define HW DALI for gpio functions
// - what we should write to pin to get HIGH/LOW
#define DALI_TX_HIGH 1 // idle state
#define DALI_TX_LOW 0 // active state
// - what we should read from pin to get HIGH/LOW
#define DALI_RX_HIGH 1 // idle state
#define DALI_RX_LOW 0 // active state
#endif
// LED onboard - debug
#define DALI_LED_PIN 2 // output D4 = GPIO2 esp8266 and esp32
// onboard LED uses inverted logic - debug
#define DALI_LED_ON 0
#define DALI_LED_OFF 1
// ----------------------------------------------
extern QueueHandle_t dali_send_queue;
extern QueueHandle_t dali_send_reply_queue;
extern QueueHandle_t dali_receive_queue;
extern QueueHandle_t dali_receive_queues[DALI_PHY_COUNT];
extern uint8_t rx_debug_enabled; // 1 - enable debug for received messages, timing, etc
// preferred way: pdMS_TO_TICKS(ms) == ms * (configTICK_RATE_HZ ) / 1000
// old way: ms / portTICK_PERIOD_MS == ms / ( 1000 / configTICK_RATE_HZ )
inline void dali_delay_ms(TickType_t ms) { vTaskDelay(pdMS_TO_TICKS(ms)); }
typedef struct {
uint8_t bus_id;
uint8_t tx_pin;
uint8_t rx_pin;
bool inited;
} dali_hal_bus_info_t;
esp_err_t dali_init_ports(uint8_t _dali_tx_pin, uint8_t _dali_rx_pin);
esp_err_t dali_hal_init(uint8_t dali_id, uint8_t tx_pin, uint8_t rx_pin);
esp_err_t dali_hal_set_baudrate(uint32_t baudrate);
uint32_t dali_hal_get_baudrate(void);
size_t dali_hal_get_inited_buses(uint8_t *ids, size_t max_ids);
esp_err_t dali_hal_get_bus_info(uint8_t bus_id, dali_hal_bus_info_t *info);
void dali_task(void *pvParameters);
+7
View File
@@ -0,0 +1,7 @@
idf_component_register(
SRCS "src/dali_domain.cpp"
INCLUDE_DIRS "include"
REQUIRES dali_cpp dali esp_driver_uart freertos log
)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
@@ -0,0 +1,105 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "esp_err.h"
class Dali;
class DaliComm;
namespace gateway {
struct DaliTransportHooks {
std::function<bool(const uint8_t* data, size_t len)> send;
std::function<std::vector<uint8_t>(size_t len, uint32_t timeout_ms)> read;
std::function<std::vector<uint8_t>(const uint8_t* data, size_t len)> transact;
std::function<void(uint32_t ms)> delay;
};
struct DaliHardwareBusConfig {
uint8_t channel_index{0};
uint8_t gateway_id{0};
uint8_t bus_id{0};
uint8_t tx_pin{0};
uint8_t rx_pin{0};
uint32_t baudrate{1200};
std::string name{"gateway"};
};
struct DaliSerialBusConfig {
uint8_t channel_index{0};
uint8_t gateway_id{0};
int uart_port{1};
int tx_pin{0};
int rx_pin{1};
uint32_t baudrate{9600};
size_t rx_buffer_size{256};
size_t tx_buffer_size{256};
uint32_t query_timeout_ms{500};
std::string name{"gateway"};
};
struct DaliChannelConfig {
uint8_t channel_index{0};
uint8_t gateway_id{0};
std::string name{"gateway"};
};
enum class DaliPhyKind {
kCustom,
kNativeHardware,
kSerialUart,
};
struct DaliChannelInfo {
uint8_t channel_index{0};
uint8_t gateway_id{0};
DaliPhyKind phy_kind{DaliPhyKind::kCustom};
std::string name;
};
class DaliDomainService {
public:
DaliDomainService();
~DaliDomainService();
bool bindTransport(const DaliChannelConfig& config, DaliTransportHooks hooks);
esp_err_t bindHardwareBus(const DaliHardwareBusConfig& config);
esp_err_t bindSerialBus(const DaliSerialBusConfig& config);
bool isBound() const;
bool isHardwareBound(uint8_t gateway_id) const;
const char* implementationName() const;
size_t channelCount() const;
std::vector<DaliChannelInfo> channelInfo() const;
bool resetBus(uint8_t gateway_id) const;
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;
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;
bool setColourRaw(uint8_t gateway_id, int raw_addr, int x, int y) const;
bool setColourRGB(uint8_t gateway_id, int short_address, int r, int g, int b) const;
bool on(uint8_t gateway_id, int short_address) const;
bool off(uint8_t gateway_id, int short_address) const;
bool off(int short_address) const;
private:
struct DaliChannel;
DaliChannel* findChannelByGateway(uint8_t gateway_id);
const DaliChannel* findChannelByGateway(uint8_t gateway_id) const;
DaliChannel* findChannelByIndex(uint8_t channel_index);
bool hasSerialPort(int uart_port) const;
std::vector<std::unique_ptr<DaliChannel>> channels_;
};
} // namespace gateway
+395
View File
@@ -0,0 +1,395 @@
#include "dali_domain.hpp"
#include "dali.h"
#include "dali_hal.h"
#include "dali.hpp"
#include "driver/uart.h"
#include "esp_log.h"
#include <algorithm>
#include <utility>
namespace gateway {
namespace {
constexpr const char* kTag = "dali_domain";
bool SendHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) {
if (data == nullptr || len != 3) {
return false;
}
if (data[0] == 0x00) {
return true;
}
Dali_msg_t tx = dali_msg_new(data[1], data[2]);
tx.id = bus_id;
switch (data[0]) {
case 0x10:
dali_send(&tx);
return true;
case 0x11:
dali_send_double(&tx);
return true;
default:
return false;
}
}
std::vector<uint8_t> TransactHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) {
if (data == nullptr || len != 3) {
return {};
}
switch (data[0]) {
case 0x00:
return {0xFF};
case 0x01:
return {1};
case 0x10:
case 0x11:
return SendHardwareFrame(bus_id, data, len) ? std::vector<uint8_t>{0xFF}
: std::vector<uint8_t>{0xFD};
case 0x12: {
Dali_msg_t tx = dali_msg_new(data[1], data[2]);
tx.id = bus_id;
Dali_msg_t rx = {};
if (dali_query(&tx, &rx) == pdTRUE) {
return {0xFF, rx.data[0]};
}
return {0xFE};
}
default:
return {};
}
}
bool WriteSerialFrame(int uart_port, const uint8_t* data, size_t len) {
if (data == nullptr || len == 0) {
return false;
}
return uart_write_bytes(static_cast<uart_port_t>(uart_port), data, len) == static_cast<int>(len);
}
std::vector<uint8_t> ReadSerialFrame(int uart_port, size_t len, uint32_t timeout_ms) {
std::vector<uint8_t> data(len, 0);
const int read_len = uart_read_bytes(static_cast<uart_port_t>(uart_port), data.data(), len,
pdMS_TO_TICKS(timeout_ms));
if (read_len <= 0) {
return {};
}
data.resize(static_cast<size_t>(read_len));
return data;
}
std::vector<uint8_t> TransactSerialFrame(int uart_port, uint32_t query_timeout_ms,
const uint8_t* data, size_t len) {
if (!WriteSerialFrame(uart_port, data, len)) {
return {0xFD};
}
if (data == nullptr || len == 0 || data[0] != 0x12) {
return {0xFF};
}
const auto response = ReadSerialFrame(uart_port, 2, query_timeout_ms);
if (response.empty()) {
return {0xFE};
}
return response;
}
} // namespace
struct DaliDomainService::DaliChannel {
DaliChannelConfig config;
DaliPhyKind phy_kind{DaliPhyKind::kCustom};
DaliTransportHooks hooks;
std::unique_ptr<DaliComm> comm;
std::unique_ptr<Dali> dali;
std::optional<DaliHardwareBusConfig> hardware_bus;
std::optional<DaliSerialBusConfig> serial_bus;
};
DaliDomainService::DaliDomainService() = default;
DaliDomainService::~DaliDomainService() = default;
bool DaliDomainService::bindTransport(const DaliChannelConfig& config, DaliTransportHooks hooks) {
if (!hooks.send) {
return false;
}
auto channel = std::make_unique<DaliChannel>();
channel->config = config;
channel->hooks = std::move(hooks);
channel->comm = std::make_unique<DaliComm>(channel->hooks.send, channel->hooks.read,
channel->hooks.transact, channel->hooks.delay);
channel->dali = std::make_unique<Dali>(*channel->comm, config.gateway_id, config.name);
auto* existing = findChannelByIndex(config.channel_index);
if (existing != nullptr) {
*existing = std::move(*channel);
return true;
}
channels_.push_back(std::move(channel));
return true;
}
esp_err_t DaliDomainService::bindHardwareBus(const DaliHardwareBusConfig& config) {
esp_err_t err = dali_hal_set_baudrate(config.baudrate);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to set baudrate=%lu: %s", static_cast<unsigned long>(config.baudrate),
esp_err_to_name(err));
return err;
}
err = dali_hal_init(config.bus_id, config.tx_pin, config.rx_pin);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to init bus=%u tx=%u rx=%u: %s", config.bus_id, config.tx_pin,
config.rx_pin, esp_err_to_name(err));
return err;
}
DaliTransportHooks hooks;
hooks.send = [bus_id = config.bus_id](const uint8_t* data, size_t len) {
return SendHardwareFrame(bus_id, data, len);
};
hooks.transact = [bus_id = config.bus_id](const uint8_t* data, size_t len) {
return TransactHardwareFrame(bus_id, data, len);
};
hooks.delay = [](uint32_t ms) { vTaskDelay(pdMS_TO_TICKS(ms)); };
const DaliChannelConfig channel_config{config.channel_index, config.gateway_id, config.name};
if (!bindTransport(channel_config, std::move(hooks))) {
return ESP_ERR_INVALID_STATE;
}
auto* channel = findChannelByIndex(config.channel_index);
if (channel != nullptr) {
channel->phy_kind = DaliPhyKind::kNativeHardware;
channel->hardware_bus = config;
}
ESP_LOGI(kTag, "bound channel=%u gateway=%u hardware bus=%u tx=%u rx=%u baudrate=%lu",
config.channel_index, config.gateway_id, config.bus_id, config.tx_pin, config.rx_pin,
static_cast<unsigned long>(config.baudrate));
return ESP_OK;
}
esp_err_t DaliDomainService::bindSerialBus(const DaliSerialBusConfig& config) {
if (config.uart_port < UART_NUM_0 || config.uart_port >= UART_NUM_MAX) {
return ESP_ERR_INVALID_ARG;
}
if (hasSerialPort(config.uart_port)) {
ESP_LOGE(kTag, "uart%d is already assigned to another DALI channel", config.uart_port);
return ESP_ERR_INVALID_STATE;
}
uart_config_t uart_config = {};
uart_config.baud_rate = static_cast<int>(config.baudrate);
uart_config.data_bits = UART_DATA_8_BITS;
uart_config.parity = UART_PARITY_DISABLE;
uart_config.stop_bits = UART_STOP_BITS_1;
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
uart_config.rx_flow_ctrl_thresh = 0;
uart_config.source_clk = UART_SCLK_DEFAULT;
auto uart = static_cast<uart_port_t>(config.uart_port);
esp_err_t err = uart_param_config(uart, &uart_config);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to configure uart%d: %s", config.uart_port, esp_err_to_name(err));
return err;
}
err = uart_set_pin(uart, config.tx_pin, config.rx_pin, UART_PIN_NO_CHANGE,
UART_PIN_NO_CHANGE);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to set uart%d pins tx=%d rx=%d: %s", config.uart_port,
config.tx_pin, config.rx_pin, esp_err_to_name(err));
return err;
}
err = uart_driver_install(uart, config.rx_buffer_size, config.tx_buffer_size, 0, nullptr, 0);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
ESP_LOGE(kTag, "failed to install uart%d driver: %s", config.uart_port, esp_err_to_name(err));
return err;
}
uart_flush_input(uart);
DaliTransportHooks hooks;
hooks.send = [uart_port = config.uart_port](const uint8_t* data, size_t len) {
return WriteSerialFrame(uart_port, data, len);
};
hooks.read = [uart_port = config.uart_port](size_t len, uint32_t timeout_ms) {
return ReadSerialFrame(uart_port, len, timeout_ms);
};
hooks.transact = [uart_port = config.uart_port, timeout_ms = config.query_timeout_ms](
const uint8_t* data, size_t len) {
return TransactSerialFrame(uart_port, timeout_ms, data, len);
};
hooks.delay = [](uint32_t ms) { vTaskDelay(pdMS_TO_TICKS(ms)); };
const DaliChannelConfig channel_config{config.channel_index, config.gateway_id, config.name};
if (!bindTransport(channel_config, std::move(hooks))) {
return ESP_ERR_INVALID_STATE;
}
auto* channel = findChannelByIndex(config.channel_index);
if (channel != nullptr) {
channel->phy_kind = DaliPhyKind::kSerialUart;
channel->serial_bus = config;
}
ESP_LOGI(kTag, "bound channel=%u gateway=%u serial uart%d tx=%d rx=%d baudrate=%lu",
config.channel_index, config.gateway_id, config.uart_port, config.tx_pin, config.rx_pin,
static_cast<unsigned long>(config.baudrate));
return ESP_OK;
}
bool DaliDomainService::isBound() const {
return !channels_.empty();
}
bool DaliDomainService::isHardwareBound(uint8_t gateway_id) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->hardware_bus.has_value();
}
const char* DaliDomainService::implementationName() const {
if (channels_.empty()) {
return "dali_cpp";
}
const bool has_hardware = std::any_of(channels_.begin(), channels_.end(), [](const auto& channel) {
return channel->phy_kind == DaliPhyKind::kNativeHardware;
});
const bool has_serial = std::any_of(channels_.begin(), channels_.end(), [](const auto& channel) {
return channel->phy_kind == DaliPhyKind::kSerialUart;
});
if (has_hardware && has_serial) {
return "dali_cpp+mixed_phy";
}
if (has_hardware) {
return "dali_cpp+dali_hal";
}
if (has_serial) {
return "dali_cpp+uart";
}
return "dali_cpp";
}
size_t DaliDomainService::channelCount() const {
return channels_.size();
}
std::vector<DaliChannelInfo> DaliDomainService::channelInfo() const {
std::vector<DaliChannelInfo> info;
info.reserve(channels_.size());
for (const auto& channel : channels_) {
info.push_back(DaliChannelInfo{channel->config.channel_index, channel->config.gateway_id,
channel->phy_kind, channel->config.name});
}
return info;
}
bool DaliDomainService::resetBus(uint8_t gateway_id) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->comm != nullptr && channel->comm->resetBus();
}
bool DaliDomainService::sendRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->comm != nullptr && channel->comm->sendRawNew(raw_addr, command);
}
bool DaliDomainService::sendExtRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->comm != nullptr && channel->comm->sendExtRawNew(raw_addr, command);
}
std::optional<uint8_t> DaliDomainService::queryRaw(uint8_t gateway_id, uint8_t raw_addr,
uint8_t command) const {
const auto* channel = findChannelByGateway(gateway_id);
if (channel == nullptr || channel->comm == nullptr) {
return std::nullopt;
}
return channel->comm->queryRawNew(raw_addr, command);
}
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 &&
channel->dali->base.setBright(short_address, brightness);
}
bool DaliDomainService::setColTempRaw(uint8_t gateway_id, int short_address, int mirek) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->dali != nullptr &&
channel->dali->dt8.setColTempRaw(short_address, mirek);
}
bool DaliDomainService::setColTemp(uint8_t gateway_id, int short_address, int kelvin) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->dali != nullptr &&
channel->dali->dt8.setColorTemperature(short_address, kelvin);
}
bool DaliDomainService::setColourRaw(uint8_t gateway_id, int raw_addr, int x, int y) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->dali != nullptr &&
channel->dali->dt8.setColourRaw(raw_addr, x, y);
}
bool DaliDomainService::setColourRGB(uint8_t gateway_id, int short_address, int r, int g,
int b) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->dali != nullptr &&
channel->dali->dt8.setColourRGB(short_address, r, g, b);
}
bool DaliDomainService::on(uint8_t gateway_id, int short_address) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->dali != nullptr && channel->dali->base.on(short_address);
}
bool DaliDomainService::off(uint8_t gateway_id, int short_address) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->dali != nullptr && channel->dali->base.off(short_address);
}
bool DaliDomainService::off(int short_address) const {
if (channels_.empty()) {
return false;
}
return off(channels_.front()->config.gateway_id, short_address);
}
DaliDomainService::DaliChannel* DaliDomainService::findChannelByGateway(uint8_t gateway_id) {
const auto it = std::find_if(channels_.begin(), channels_.end(), [gateway_id](const auto& channel) {
return channel->config.gateway_id == gateway_id;
});
return it == channels_.end() ? nullptr : it->get();
}
const DaliDomainService::DaliChannel* DaliDomainService::findChannelByGateway(
uint8_t gateway_id) const {
const auto it = std::find_if(channels_.begin(), channels_.end(), [gateway_id](const auto& channel) {
return channel->config.gateway_id == gateway_id;
});
return it == channels_.end() ? nullptr : it->get();
}
DaliDomainService::DaliChannel* DaliDomainService::findChannelByIndex(uint8_t channel_index) {
const auto it = std::find_if(channels_.begin(), channels_.end(),
[channel_index](const auto& channel) {
return channel->config.channel_index == channel_index;
});
return it == channels_.end() ? nullptr : it->get();
}
bool DaliDomainService::hasSerialPort(int uart_port) const {
return std::any_of(channels_.begin(), channels_.end(), [uart_port](const auto& channel) {
return channel->serial_bus.has_value() && channel->serial_bus->uart_port == uart_port;
});
}
} // namespace gateway
+7
View File
@@ -0,0 +1,7 @@
idf_component_register(
SRCS "src/gateway_core.cpp"
INCLUDE_DIRS "include"
REQUIRES log
)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
@@ -0,0 +1,36 @@
#pragma once
#include <string_view>
namespace gateway {
enum class AppRole {
kGateway,
kReceiver,
kTelemetry,
};
struct BootProfile {
AppRole role;
std::string_view instance_name;
bool enable_wifi;
bool enable_ble;
bool enable_eth;
bool enable_espnow;
bool enable_usb;
};
class GatewayCore {
public:
explicit GatewayCore(BootProfile profile);
void start() const;
const BootProfile& profile() const;
static const char* RoleToString(AppRole role);
private:
BootProfile profile_;
};
} // namespace gateway
@@ -0,0 +1,45 @@
#include "gateway_core.hpp"
#include "esp_log.h"
namespace gateway {
namespace {
constexpr const char* kTag = "gateway_core";
} // namespace
GatewayCore::GatewayCore(BootProfile profile) : profile_(profile) {}
void GatewayCore::start() const {
ESP_LOGI(kTag,
"boot role=%s instance=%.*s wifi=%d ble=%d eth=%d espnow=%d usb=%d",
RoleToString(profile_.role),
static_cast<int>(profile_.instance_name.size()),
profile_.instance_name.data(),
profile_.enable_wifi,
profile_.enable_ble,
profile_.enable_eth,
profile_.enable_espnow,
profile_.enable_usb);
}
const BootProfile& GatewayCore::profile() const {
return profile_;
}
const char* GatewayCore::RoleToString(AppRole role) {
switch (role) {
case AppRole::kGateway:
return "gateway";
case AppRole::kReceiver:
return "receiver";
case AppRole::kTelemetry:
return "telemetry";
}
return "unknown";
}
} // namespace gateway
@@ -0,0 +1,7 @@
idf_component_register(
SRCS "src/gateway_runtime.cpp"
INCLUDE_DIRS "include"
REQUIRES gateway_core dali_domain log nvs_flash esp_hw_support
)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
@@ -0,0 +1,128 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <deque>
#include <functional>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "esp_err.h"
#include "gateway_core.hpp"
#include "nvs.h"
namespace gateway {
esp_err_t InitializeRuntimeNvs();
std::string ReadRuntimeSerialId();
class DaliDomainService;
struct WirelessInfo {
std::string ssid;
std::string password;
std::string mac;
std::string ip;
};
struct GatewayRuntimeConfig {
std::string_view project_name;
std::string_view version;
std::string serial_id;
size_t command_queue_capacity{16};
};
struct GatewayDeviceInfo {
std::string serial_id;
std::string type;
std::string project;
std::string version;
size_t dali_gateway_count{0};
bool ble_enabled{false};
std::optional<WirelessInfo> wlan;
};
class GatewaySettingsStore {
public:
GatewaySettingsStore();
~GatewaySettingsStore();
esp_err_t open();
void close();
bool getBleEnabled(bool default_value = false) const;
bool setBleEnabled(bool enabled);
std::optional<std::string> getWifiSsid() const;
std::optional<std::string> getWifiPassword() const;
bool setWifiCredentials(std::string_view ssid, std::string_view password);
std::string getGatewayName(uint8_t gateway_id, std::string_view fallback) const;
bool setGatewayName(uint8_t gateway_id, std::string_view name);
private:
std::optional<std::string> readString(std::string_view key) const;
bool writeString(std::string_view key, std::string_view value);
std::string makeGatewayNameKey(uint8_t gateway_id) const;
mutable nvs_handle_t handle_{0};
};
class GatewayRuntime {
public:
enum class CommandDropReason {
kNone,
kDuplicate,
kQueueFull,
};
GatewayRuntime(BootProfile profile, GatewayRuntimeConfig config,
DaliDomainService* dali_domain);
esp_err_t start();
static std::vector<uint8_t> checksum(std::vector<uint8_t> frame);
static bool hasValidChecksum(const std::vector<uint8_t>& frame);
static bool isGatewayCommandFrame(const std::vector<uint8_t>& frame);
static std::vector<uint8_t> buildNotificationFrame(const std::vector<uint8_t>& payload);
bool enqueueCommand(std::vector<uint8_t> command);
std::optional<std::vector<uint8_t>> popNextCommand();
void completeCurrentCommand();
bool hasPendingQueryCommand(const std::vector<uint8_t>& command) const;
CommandDropReason lastEnqueueDropReason() const;
void setGatewayCount(size_t gateway_count);
void setWirelessInfo(WirelessInfo info);
void setCommandAddressResolver(std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> resolver);
GatewayDeviceInfo deviceInfo() const;
std::string gatewayName(uint8_t gateway_id) const;
bool setGatewayName(uint8_t gateway_id, std::string_view name);
std::string gatewaySerialHex(uint8_t gateway_id) const;
std::string bleMacHex() const;
std::string defaultBleGatewayName() const;
private:
bool isQueryCommand(const std::vector<uint8_t>& command) const;
std::optional<std::string> queryCommandKey(const std::vector<uint8_t>& command) const;
std::string defaultGatewayName(uint8_t gateway_id) const;
std::vector<uint8_t> serialBytes() const;
static std::string toHex(const std::vector<uint8_t>& bytes);
BootProfile profile_;
GatewayRuntimeConfig config_;
DaliDomainService* dali_domain_;
GatewaySettingsStore settings_;
std::optional<std::vector<uint8_t>> current_command_;
std::deque<std::vector<uint8_t>> pending_commands_;
size_t gateway_count_{0};
bool ble_enabled_{false};
CommandDropReason last_enqueue_drop_reason_{CommandDropReason::kNone};
std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> command_address_resolver_;
std::optional<WirelessInfo> wireless_info_;
};
} // namespace gateway
@@ -0,0 +1,411 @@
#include "gateway_runtime.hpp"
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <utility>
#include "dali_domain.hpp"
#include "esp_mac.h"
#include "esp_log.h"
#include "nvs_flash.h"
namespace gateway {
namespace {
constexpr const char* kTag = "gateway_runtime";
constexpr const char* kNamespace = "gateway_rt";
constexpr const char* kBleEnabledKey = "ble_enabled";
constexpr const char* kWifiSsidKey = "wifi_ssid";
constexpr const char* kWifiPasswordKey = "wifi_passwd";
constexpr size_t kMaxGatewayNameBytes = 32;
constexpr uint8_t kCommandFramePrefix0 = 0x28;
constexpr uint8_t kCommandFramePrefix1 = 0x01;
constexpr uint8_t kNotifyFramePrefix = 0x22;
} // namespace
esp_err_t InitializeRuntimeNvs() {
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
return err;
}
std::string ReadRuntimeSerialId() {
uint8_t mac[6] = {0};
if (esp_read_mac(mac, ESP_MAC_WIFI_STA) != ESP_OK) {
return "DALIGW";
}
char serial[13] = {0};
std::snprintf(serial, sizeof(serial), "%02X%02X%02X%02X%02X%02X", mac[0], mac[1],
mac[2], mac[3], mac[4], mac[5]);
return std::string(serial);
}
GatewaySettingsStore::GatewaySettingsStore() = default;
GatewaySettingsStore::~GatewaySettingsStore() {
close();
}
esp_err_t GatewaySettingsStore::open() {
if (handle_ != 0) {
return ESP_OK;
}
return nvs_open(kNamespace, NVS_READWRITE, &handle_);
}
void GatewaySettingsStore::close() {
if (handle_ != 0) {
nvs_close(handle_);
handle_ = 0;
}
}
bool GatewaySettingsStore::getBleEnabled(bool default_value) const {
if (handle_ == 0) {
return default_value;
}
uint8_t enabled = default_value ? 1 : 0;
if (nvs_get_u8(handle_, kBleEnabledKey, &enabled) != ESP_OK) {
return default_value;
}
return enabled != 0;
}
bool GatewaySettingsStore::setBleEnabled(bool enabled) {
if (handle_ == 0) {
return false;
}
return nvs_set_u8(handle_, kBleEnabledKey, enabled ? 1 : 0) == ESP_OK &&
nvs_commit(handle_) == ESP_OK;
}
std::optional<std::string> GatewaySettingsStore::getWifiSsid() const {
return readString(kWifiSsidKey);
}
std::optional<std::string> GatewaySettingsStore::getWifiPassword() const {
return readString(kWifiPasswordKey);
}
bool GatewaySettingsStore::setWifiCredentials(std::string_view ssid,
std::string_view password) {
return writeString(kWifiSsidKey, ssid) && writeString(kWifiPasswordKey, password);
}
std::string GatewaySettingsStore::getGatewayName(uint8_t gateway_id,
std::string_view fallback) const {
const auto value = readString(makeGatewayNameKey(gateway_id));
if (!value.has_value() || value->empty()) {
return std::string(fallback);
}
return *value;
}
bool GatewaySettingsStore::setGatewayName(uint8_t gateway_id,
std::string_view name) {
return writeString(makeGatewayNameKey(gateway_id), name);
}
std::optional<std::string> GatewaySettingsStore::readString(std::string_view key) const {
if (handle_ == 0) {
return std::nullopt;
}
size_t required_size = 0;
const esp_err_t err = nvs_get_str(handle_, std::string(key).c_str(), nullptr, &required_size);
if (err != ESP_OK || required_size == 0) {
return std::nullopt;
}
std::string value(required_size - 1, '\0');
if (nvs_get_str(handle_, std::string(key).c_str(), value.data(), &required_size) != ESP_OK) {
return std::nullopt;
}
return value;
}
bool GatewaySettingsStore::writeString(std::string_view key, std::string_view value) {
if (handle_ == 0) {
return false;
}
return nvs_set_str(handle_, std::string(key).c_str(), std::string(value).c_str()) == ESP_OK &&
nvs_commit(handle_) == ESP_OK;
}
std::string GatewaySettingsStore::makeGatewayNameKey(uint8_t gateway_id) const {
char key[24] = {0};
std::snprintf(key, sizeof(key), "dali_gw_name_%u", gateway_id);
return std::string(key);
}
GatewayRuntime::GatewayRuntime(BootProfile profile, GatewayRuntimeConfig config,
DaliDomainService* dali_domain)
: profile_(profile),
config_(std::move(config)),
dali_domain_(dali_domain),
command_address_resolver_([](uint8_t, uint8_t raw_addr) { return raw_addr; }) {}
esp_err_t GatewayRuntime::start() {
const esp_err_t err = settings_.open();
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to open settings store: %s", esp_err_to_name(err));
return err;
}
ble_enabled_ = settings_.getBleEnabled(profile_.enable_ble);
if (!wireless_info_.has_value()) {
WirelessInfo info;
const auto ssid = settings_.getWifiSsid();
const auto password = settings_.getWifiPassword();
if (ssid.has_value()) {
info.ssid = *ssid;
}
if (password.has_value()) {
info.password = *password;
}
if (!info.ssid.empty() || !info.password.empty()) {
wireless_info_ = std::move(info);
}
}
ESP_LOGI(kTag,
"runtime project=%.*s version=%.*s serial=%s ble=%d dali_bound=%d",
static_cast<int>(config_.project_name.size()), config_.project_name.data(),
static_cast<int>(config_.version.size()), config_.version.data(),
config_.serial_id.c_str(), ble_enabled_,
dali_domain_ != nullptr && dali_domain_->isBound());
return ESP_OK;
}
std::vector<uint8_t> GatewayRuntime::checksum(std::vector<uint8_t> frame) {
uint32_t sum = 0;
for (const auto byte : frame) {
sum += byte;
}
frame.push_back(static_cast<uint8_t>(sum & 0xFF));
return frame;
}
bool GatewayRuntime::hasValidChecksum(const std::vector<uint8_t>& frame) {
if (frame.empty()) {
return false;
}
if (frame.back() == 0xFF) {
return true;
}
uint32_t sum = 0;
for (size_t i = 0; i + 1 < frame.size(); ++i) {
sum += frame[i];
}
return static_cast<uint8_t>(sum & 0xFF) == frame.back();
}
bool GatewayRuntime::isGatewayCommandFrame(const std::vector<uint8_t>& frame) {
return frame.size() >= 2 && frame[0] == kCommandFramePrefix0 && frame[1] == kCommandFramePrefix1;
}
std::vector<uint8_t> GatewayRuntime::buildNotificationFrame(const std::vector<uint8_t>& payload) {
std::vector<uint8_t> frame;
frame.reserve(payload.size() + 2);
frame.push_back(kNotifyFramePrefix);
frame.insert(frame.end(), payload.begin(), payload.end());
return checksum(std::move(frame));
}
bool GatewayRuntime::enqueueCommand(std::vector<uint8_t> command) {
last_enqueue_drop_reason_ = CommandDropReason::kNone;
if (isQueryCommand(command) && hasPendingQueryCommand(command)) {
last_enqueue_drop_reason_ = CommandDropReason::kDuplicate;
return false;
}
if (pending_commands_.size() >= config_.command_queue_capacity) {
last_enqueue_drop_reason_ = CommandDropReason::kQueueFull;
return false;
}
pending_commands_.push_back(std::move(command));
return true;
}
std::optional<std::vector<uint8_t>> GatewayRuntime::popNextCommand() {
if (pending_commands_.empty()) {
current_command_.reset();
return std::nullopt;
}
current_command_ = std::move(pending_commands_.front());
pending_commands_.pop_front();
return current_command_;
}
void GatewayRuntime::completeCurrentCommand() {
current_command_.reset();
}
bool GatewayRuntime::hasPendingQueryCommand(const std::vector<uint8_t>& command) const {
const auto command_key = queryCommandKey(command);
if (!command_key.has_value()) {
return false;
}
if (current_command_.has_value() && queryCommandKey(*current_command_) == command_key) {
return true;
}
return std::any_of(pending_commands_.begin(), pending_commands_.end(),
[&](const std::vector<uint8_t>& pending) {
return queryCommandKey(pending) == command_key;
});
}
GatewayRuntime::CommandDropReason GatewayRuntime::lastEnqueueDropReason() const {
return last_enqueue_drop_reason_;
}
void GatewayRuntime::setGatewayCount(size_t gateway_count) {
gateway_count_ = gateway_count;
}
void GatewayRuntime::setWirelessInfo(WirelessInfo info) {
if (!info.ssid.empty() || !info.password.empty()) {
settings_.setWifiCredentials(info.ssid, info.password);
}
wireless_info_ = std::move(info);
}
void GatewayRuntime::setCommandAddressResolver(
std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> resolver) {
if (resolver) {
command_address_resolver_ = std::move(resolver);
return;
}
command_address_resolver_ = [](uint8_t, uint8_t raw_addr) { return raw_addr; };
}
GatewayDeviceInfo GatewayRuntime::deviceInfo() const {
GatewayDeviceInfo info;
info.serial_id = config_.serial_id;
info.type = GatewayCore::RoleToString(profile_.role);
info.project = std::string(config_.project_name);
info.version = std::string(config_.version);
info.dali_gateway_count = gateway_count_;
info.ble_enabled = ble_enabled_;
info.wlan = wireless_info_;
return info;
}
std::string GatewayRuntime::gatewayName(uint8_t gateway_id) const {
return settings_.getGatewayName(gateway_id, defaultGatewayName(gateway_id));
}
bool GatewayRuntime::setGatewayName(uint8_t gateway_id, std::string_view name) {
std::string normalized(name);
if (normalized.size() > kMaxGatewayNameBytes) {
normalized.resize(kMaxGatewayNameBytes);
}
if (normalized.empty()) {
normalized = defaultGatewayName(gateway_id);
}
return settings_.setGatewayName(gateway_id, normalized);
}
std::string GatewayRuntime::gatewaySerialHex(uint8_t gateway_id) const {
const auto bytes = serialBytes();
std::vector<uint8_t> serial(6, 0);
for (size_t i = 0; i < 5; ++i) {
serial[i] = i + 1 < bytes.size() ? bytes[i + 1] : 0;
}
serial[5] = gateway_id;
return toHex(serial);
}
std::string GatewayRuntime::bleMacHex() const {
return toHex(serialBytes());
}
std::string GatewayRuntime::defaultBleGatewayName() const {
return "DALIGW_" + bleMacHex();
}
bool GatewayRuntime::isQueryCommand(const std::vector<uint8_t>& command) const {
return command.size() >= 6 && isGatewayCommandFrame(command) && command[3] >= 0x14 &&
command[3] <= 0x16;
}
std::optional<std::string> GatewayRuntime::queryCommandKey(
const std::vector<uint8_t>& command) const {
if (!isQueryCommand(command)) {
return std::nullopt;
}
const auto gw = command[2];
const auto cmd = command[3];
if (cmd == 0x16) {
char key[16] = {0};
std::snprintf(key, sizeof(key), "%u:%u", gw, cmd);
return std::string(key);
}
const auto target_addr = command_address_resolver_(gw, command[4]);
char key[32] = {0};
std::snprintf(key, sizeof(key), "%u:%u:%u:%u", gw, cmd, target_addr, command[5]);
return std::string(key);
}
std::string GatewayRuntime::defaultGatewayName(uint8_t) const {
const std::string serial_hex = bleMacHex();
if (serial_hex.size() <= 6) {
return "DALIGW_" + serial_hex;
}
return "DALIGW_" + serial_hex.substr(serial_hex.size() - 6);
}
std::vector<uint8_t> GatewayRuntime::serialBytes() const {
std::vector<uint8_t> bytes;
const std::string& serial = config_.serial_id;
bytes.reserve(6);
for (size_t i = 0; i + 1 < serial.size() && bytes.size() < 6; i += 2) {
if (!std::isxdigit(static_cast<unsigned char>(serial[i])) ||
!std::isxdigit(static_cast<unsigned char>(serial[i + 1]))) {
break;
}
char pair[3] = {serial[i], serial[i + 1], 0};
bytes.push_back(static_cast<uint8_t>(std::strtoul(pair, nullptr, 16)));
}
while (bytes.size() < 6) {
bytes.push_back(0);
}
return bytes;
}
std::string GatewayRuntime::toHex(const std::vector<uint8_t>& bytes) {
static constexpr char kHex[] = "0123456789ABCDEF";
std::string out;
out.reserve(bytes.size() * 2);
for (const auto byte : bytes) {
out.push_back(kHex[(byte >> 4) & 0x0F]);
out.push_back(kHex[byte & 0x0F]);
}
return out;
}
} // namespace gateway