Initial commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
**/build/
|
||||||
|
build/
|
||||||
|
**/managed_components/
|
||||||
@@ -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.
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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`
|
||||||
@@ -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
|
||||||
Vendored
BIN
Binary file not shown.
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user