Initial commit
This commit is contained in:
@@ -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