626f86ec4e
- Introduced configuration options for wired Ethernet support in Kconfig and sdkconfig. - Implemented Ethernet initialization and event handling in GatewayNetworkService. - Enhanced app_main to manage Ethernet alongside Wi-Fi. - Updated GatewayRuntime to store Ethernet information. - Modified CMakeLists and include files to accommodate new Ethernet dependencies. - Ensured backward compatibility by allowing Ethernet initialization failures to be ignored. Signed-off-by: Tony <tonylu@tony-cloud.com>
1695 lines
53 KiB
C++
1695 lines
53 KiB
C++
#include "gateway_network.hpp"
|
|
|
|
#include "dali_domain.hpp"
|
|
#include "gateway_bridge.hpp"
|
|
#include "gateway_controller.hpp"
|
|
#include "gateway_runtime.hpp"
|
|
|
|
#include "cJSON.h"
|
|
#include "driver/gpio.h"
|
|
#include "driver/spi_master.h"
|
|
#include "esp_event.h"
|
|
#include "esp_eth_driver.h"
|
|
#include "esp_eth_mac_spi.h"
|
|
#include "esp_log.h"
|
|
#include "esp_mac.h"
|
|
#include "esp_netif.h"
|
|
#include "esp_netif_ip_addr.h"
|
|
#include "esp_smartconfig.h"
|
|
#include "esp_system.h"
|
|
#include "esp_wifi.h"
|
|
#include "lwip/inet.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <cstdlib>
|
|
#include <string_view>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace gateway {
|
|
|
|
namespace {
|
|
|
|
constexpr const char* kTag = "gateway_network";
|
|
constexpr const char* kSetupApSsid = "LAMMIN_Gateway";
|
|
constexpr size_t kUdpBufferSize = 256;
|
|
constexpr uint8_t kEspNowBroadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
|
|
|
GatewayNetworkService* s_espnow_service = nullptr;
|
|
|
|
class LockGuard {
|
|
public:
|
|
explicit LockGuard(SemaphoreHandle_t lock) : lock_(lock) {
|
|
if (lock_ != nullptr) {
|
|
xSemaphoreTake(lock_, portMAX_DELAY);
|
|
}
|
|
}
|
|
|
|
~LockGuard() {
|
|
if (lock_ != nullptr) {
|
|
xSemaphoreGive(lock_);
|
|
}
|
|
}
|
|
|
|
private:
|
|
SemaphoreHandle_t lock_;
|
|
};
|
|
|
|
bool HexValue(char ch, uint8_t& value) {
|
|
if (ch >= '0' && ch <= '9') {
|
|
value = static_cast<uint8_t>(ch - '0');
|
|
return true;
|
|
}
|
|
if (ch >= 'a' && ch <= 'f') {
|
|
value = static_cast<uint8_t>(10 + ch - 'a');
|
|
return true;
|
|
}
|
|
if (ch >= 'A' && ch <= 'F') {
|
|
value = static_cast<uint8_t>(10 + ch - 'A');
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::vector<uint8_t> DecodeHex(std::string_view hex) {
|
|
if ((hex.size() & 1U) != 0U) {
|
|
return {};
|
|
}
|
|
|
|
std::vector<uint8_t> bytes;
|
|
bytes.reserve(hex.size() / 2);
|
|
for (size_t i = 0; i < hex.size(); i += 2) {
|
|
uint8_t high = 0;
|
|
uint8_t low = 0;
|
|
if (!HexValue(hex[i], high) || !HexValue(hex[i + 1], low)) {
|
|
return {};
|
|
}
|
|
bytes.push_back(static_cast<uint8_t>((high << 4) | low));
|
|
}
|
|
return bytes;
|
|
}
|
|
|
|
std::string MacToHex(const uint8_t mac[6]) {
|
|
char out[13] = {0};
|
|
std::snprintf(out, sizeof(out), "%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3],
|
|
mac[4], mac[5]);
|
|
return std::string(out);
|
|
}
|
|
|
|
std::string BoundedString(const uint8_t* data, size_t len) {
|
|
if (data == nullptr) {
|
|
return {};
|
|
}
|
|
size_t actual_len = 0;
|
|
while (actual_len < len && data[actual_len] != 0) {
|
|
++actual_len;
|
|
}
|
|
return std::string(reinterpret_cast<const char*>(data), actual_len);
|
|
}
|
|
|
|
std::string LocalMacHex(wifi_interface_t interface) {
|
|
uint8_t mac[6] = {};
|
|
if (esp_wifi_get_mac(interface, mac) != ESP_OK) {
|
|
return {};
|
|
}
|
|
return MacToHex(mac);
|
|
}
|
|
|
|
const char* JsonString(cJSON* parent, const char* name) {
|
|
cJSON* item = cJSON_GetObjectItem(parent, name);
|
|
return cJSON_IsString(item) ? item->valuestring : nullptr;
|
|
}
|
|
|
|
std::vector<uint8_t> BytesFromJsonString(const char* value) {
|
|
if (value == nullptr) {
|
|
return {};
|
|
}
|
|
|
|
std::string_view text(value);
|
|
auto decoded = DecodeHex(text);
|
|
if (!decoded.empty() || text.empty()) {
|
|
return decoded;
|
|
}
|
|
return std::vector<uint8_t>(text.begin(), text.end());
|
|
}
|
|
|
|
std::string BytesToHex(const std::vector<uint8_t>& bytes) {
|
|
static constexpr char kHex[] = "0123456789ABCDEF";
|
|
std::string out;
|
|
out.reserve(bytes.size() * 2);
|
|
for (uint8_t byte : bytes) {
|
|
out.push_back(kHex[(byte >> 4) & 0x0F]);
|
|
out.push_back(kHex[byte & 0x0F]);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::string PrintJson(cJSON* node) {
|
|
if (node == nullptr) {
|
|
return {};
|
|
}
|
|
|
|
char* rendered = cJSON_PrintUnformatted(node);
|
|
if (rendered == nullptr) {
|
|
return {};
|
|
}
|
|
std::string out(rendered);
|
|
cJSON_free(rendered);
|
|
return out;
|
|
}
|
|
|
|
esp_err_t ReadRequestBody(httpd_req_t* req, std::string& body) {
|
|
body.clear();
|
|
if (req == nullptr) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
body.resize(static_cast<size_t>(req->content_len));
|
|
int received = 0;
|
|
while (received < req->content_len) {
|
|
const int ret = httpd_req_recv(req, body.data() + received, req->content_len - received);
|
|
if (ret <= 0) {
|
|
return ESP_FAIL;
|
|
}
|
|
received += ret;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t RegisterUri(httpd_handle_t server, const char* uri, httpd_method_t method,
|
|
esp_err_t (*handler)(httpd_req_t*), void* user_ctx) {
|
|
httpd_uri_t route = {};
|
|
route.uri = uri;
|
|
route.method = method;
|
|
route.handler = handler;
|
|
route.user_ctx = user_ctx;
|
|
return httpd_register_uri_handler(server, &route);
|
|
}
|
|
|
|
std::string QueryValue(httpd_req_t* req, const char* key) {
|
|
if (req == nullptr || key == nullptr) {
|
|
return {};
|
|
}
|
|
const size_t len = httpd_req_get_url_query_len(req) + 1;
|
|
if (len <= 1) {
|
|
return {};
|
|
}
|
|
std::string query(len, '\0');
|
|
if (httpd_req_get_url_query_str(req, query.data(), query.size()) != ESP_OK) {
|
|
return {};
|
|
}
|
|
char value[64] = {0};
|
|
if (httpd_query_key_value(query.c_str(), key, value, sizeof(value)) != ESP_OK) {
|
|
return {};
|
|
}
|
|
return std::string(value);
|
|
}
|
|
|
|
std::string QueryString(httpd_req_t* req) {
|
|
if (req == nullptr) {
|
|
return {};
|
|
}
|
|
const size_t len = httpd_req_get_url_query_len(req) + 1;
|
|
if (len <= 1) {
|
|
return {};
|
|
}
|
|
std::string query(len, '\0');
|
|
if (httpd_req_get_url_query_str(req, query.data(), query.size()) != ESP_OK) {
|
|
return {};
|
|
}
|
|
if (!query.empty() && query.back() == '\0') {
|
|
query.pop_back();
|
|
}
|
|
return query;
|
|
}
|
|
|
|
std::optional<uint8_t> QueryGatewayId(httpd_req_t* req) {
|
|
const auto raw = QueryValue(req, "gw");
|
|
if (raw.empty()) {
|
|
return std::nullopt;
|
|
}
|
|
char* end = nullptr;
|
|
const long parsed = std::strtol(raw.c_str(), &end, 10);
|
|
if (end == raw.c_str() || *end != '\0' || parsed < 0 || parsed > 255) {
|
|
return std::nullopt;
|
|
}
|
|
return static_cast<uint8_t>(parsed);
|
|
}
|
|
|
|
esp_err_t SendJsonResponse(httpd_req_t* req, const GatewayBridgeHttpResponse& response) {
|
|
if (response.err == ESP_ERR_INVALID_ARG) {
|
|
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, response.body.c_str());
|
|
}
|
|
if (response.err == ESP_ERR_NOT_FOUND) {
|
|
return httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, response.body.c_str());
|
|
}
|
|
if (response.err != ESP_OK) {
|
|
return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, response.body.c_str());
|
|
}
|
|
httpd_resp_set_type(req, "application/json");
|
|
return httpd_resp_send(req, response.body.data(), response.body.size());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
GatewayNetworkService::GatewayNetworkService(GatewayController& controller,
|
|
GatewayRuntime& runtime,
|
|
DaliDomainService& dali_domain,
|
|
GatewayNetworkServiceConfig config,
|
|
GatewayBridgeService* bridge_service)
|
|
: controller_(controller), runtime_(runtime), dali_domain_(dali_domain), config_(config),
|
|
bridge_service_(bridge_service), udp_lock_(xSemaphoreCreateMutex()) {}
|
|
|
|
esp_err_t GatewayNetworkService::start() {
|
|
if (started_) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t err = ensureNetworkStack();
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
|
|
if (config_.ethernet_enabled) {
|
|
err = startEthernet();
|
|
if (err != ESP_OK) {
|
|
if (config_.ethernet_ignore_init_failure) {
|
|
ESP_LOGW(kTag, "Ethernet init failed; Ethernet is disabled for this boot: %s",
|
|
esp_err_to_name(err));
|
|
config_.ethernet_enabled = false;
|
|
} else {
|
|
return err;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (config_.espnow_setup_startup_enabled) {
|
|
err = startSetupAp();
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
} else if (config_.smartconfig_startup_enabled) {
|
|
err = startSmartconfig();
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
} else if (config_.wifi_enabled) {
|
|
err = startWifi();
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
err = configureStatusLed();
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
|
|
err = configureBootButton();
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
|
|
controller_.addNotificationSink(
|
|
[this](const std::vector<uint8_t>& frame) { handleGatewayNotification(frame); });
|
|
controller_.addWifiStateSink([this](uint8_t mode) { handleWifiControl(mode); });
|
|
dali_domain_.addRawFrameSink([this](const DaliRawFrame& frame) { handleDaliRawFrame(frame); });
|
|
|
|
if (config_.http_enabled) {
|
|
err = startHttpServer();
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
if (config_.udp_enabled) {
|
|
err = startUdpTask();
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
err = startBootButtonTask();
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
|
|
started_ = true;
|
|
ESP_LOGI(kTag, "network service started eth=%d wifi=%d http=%d udp=%d",
|
|
config_.ethernet_enabled, config_.wifi_enabled, config_.http_enabled,
|
|
config_.udp_enabled);
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::ensureNetworkStack() {
|
|
esp_err_t err = esp_netif_init();
|
|
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
|
ESP_LOGE(kTag, "failed to init esp_netif: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
|
|
err = esp_event_loop_create_default();
|
|
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
|
ESP_LOGE(kTag, "failed to create default event loop: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::startEthernet() {
|
|
if (ethernet_started_) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
#if CONFIG_ETH_SPI_ETHERNET_W5500
|
|
if (eth_netif_ == nullptr) {
|
|
esp_netif_config_t netif_config = ESP_NETIF_DEFAULT_ETH();
|
|
eth_netif_ = esp_netif_new(&netif_config);
|
|
if (eth_netif_ == nullptr) {
|
|
ESP_LOGE(kTag, "failed to create Ethernet netif");
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
}
|
|
|
|
if (!ethernet_event_handlers_registered_) {
|
|
esp_err_t err = esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID,
|
|
&GatewayNetworkService::HandleEthernetEvent, this);
|
|
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
|
ESP_LOGE(kTag, "failed to register Ethernet event handler: %s", esp_err_to_name(err));
|
|
stopEthernet();
|
|
return err;
|
|
}
|
|
err = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP,
|
|
&GatewayNetworkService::HandleEthernetEvent, this);
|
|
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
|
ESP_LOGE(kTag, "failed to register Ethernet IP event handler: %s", esp_err_to_name(err));
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_event_handler_unregister(
|
|
ETH_EVENT, ESP_EVENT_ANY_ID, &GatewayNetworkService::HandleEthernetEvent));
|
|
stopEthernet();
|
|
return err;
|
|
}
|
|
ethernet_event_handlers_registered_ = true;
|
|
}
|
|
|
|
if (config_.ethernet_spi_int_gpio >= 0) {
|
|
esp_err_t err = gpio_install_isr_service(0);
|
|
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
|
ESP_LOGE(kTag, "failed to install GPIO ISR service for Ethernet: %s", esp_err_to_name(err));
|
|
stopEthernet();
|
|
return err;
|
|
}
|
|
}
|
|
|
|
const auto spi_host = static_cast<spi_host_device_t>(config_.ethernet_spi_host);
|
|
spi_bus_config_t bus_config = {};
|
|
bus_config.miso_io_num = config_.ethernet_spi_miso_gpio;
|
|
bus_config.mosi_io_num = config_.ethernet_spi_mosi_gpio;
|
|
bus_config.sclk_io_num = config_.ethernet_spi_sclk_gpio;
|
|
bus_config.quadwp_io_num = -1;
|
|
bus_config.quadhd_io_num = -1;
|
|
|
|
esp_err_t err = spi_bus_initialize(spi_host, &bus_config, SPI_DMA_CH_AUTO);
|
|
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
|
ESP_LOGE(kTag, "failed to initialize Ethernet SPI host %d: %s", config_.ethernet_spi_host,
|
|
esp_err_to_name(err));
|
|
stopEthernet();
|
|
return err;
|
|
}
|
|
|
|
spi_device_interface_config_t spi_device_config = {};
|
|
spi_device_config.mode = 0;
|
|
spi_device_config.clock_speed_hz = static_cast<int>(config_.ethernet_spi_clock_mhz) * 1000 * 1000;
|
|
spi_device_config.spics_io_num = config_.ethernet_spi_cs_gpio;
|
|
spi_device_config.queue_size = 20;
|
|
|
|
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
|
|
mac_config.rx_task_stack_size = config_.ethernet_rx_task_stack_size;
|
|
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
|
|
phy_config.phy_addr = config_.ethernet_phy_addr;
|
|
phy_config.reset_gpio_num = config_.ethernet_phy_reset_gpio;
|
|
|
|
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_host, &spi_device_config);
|
|
w5500_config.int_gpio_num = config_.ethernet_spi_int_gpio;
|
|
w5500_config.poll_period_ms = config_.ethernet_poll_period_ms;
|
|
if (w5500_config.int_gpio_num < 0 && w5500_config.poll_period_ms == 0) {
|
|
w5500_config.poll_period_ms = 100;
|
|
}
|
|
|
|
eth_mac_ = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
|
|
if (eth_mac_ == nullptr) {
|
|
ESP_LOGE(kTag, "failed to create W5500 Ethernet MAC");
|
|
stopEthernet();
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
eth_phy_ = esp_eth_phy_new_w5500(&phy_config);
|
|
if (eth_phy_ == nullptr) {
|
|
ESP_LOGE(kTag, "failed to create W5500 Ethernet PHY");
|
|
stopEthernet();
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(eth_mac_, eth_phy_);
|
|
err = esp_eth_driver_install(ð_config, ð_handle_);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(kTag, "failed to install Ethernet driver: %s", esp_err_to_name(err));
|
|
stopEthernet();
|
|
return err;
|
|
}
|
|
|
|
uint8_t eth_mac[6] = {};
|
|
err = esp_read_mac(eth_mac, ESP_MAC_ETH);
|
|
if (err == ESP_OK) {
|
|
err = esp_eth_ioctl(eth_handle_, ETH_CMD_S_MAC_ADDR, eth_mac);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(kTag, "failed to set Ethernet MAC address: %s", esp_err_to_name(err));
|
|
stopEthernet();
|
|
return err;
|
|
}
|
|
EthernetInfo info;
|
|
info.mac = MacToHex(eth_mac);
|
|
runtime_.setEthernetInfo(std::move(info));
|
|
} else {
|
|
ESP_LOGW(kTag, "failed to read Ethernet MAC address: %s", esp_err_to_name(err));
|
|
}
|
|
|
|
eth_glue_ = esp_eth_new_netif_glue(eth_handle_);
|
|
if (eth_glue_ == nullptr) {
|
|
ESP_LOGE(kTag, "failed to create Ethernet netif glue");
|
|
stopEthernet();
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
err = esp_netif_attach(eth_netif_, eth_glue_);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(kTag, "failed to attach Ethernet netif: %s", esp_err_to_name(err));
|
|
stopEthernet();
|
|
return err;
|
|
}
|
|
|
|
err = esp_eth_start(eth_handle_);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(kTag, "failed to start Ethernet: %s", esp_err_to_name(err));
|
|
stopEthernet();
|
|
return err;
|
|
}
|
|
|
|
ethernet_started_ = true;
|
|
err = probeEthernetStartup();
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(kTag, "Ethernet startup probe failed: %s", esp_err_to_name(err));
|
|
stopEthernet();
|
|
return err;
|
|
}
|
|
|
|
ESP_LOGI(kTag,
|
|
"Ethernet W5500 started spi_host=%d sclk=%d mosi=%d miso=%d cs=%d int=%d reset=%d",
|
|
config_.ethernet_spi_host, config_.ethernet_spi_sclk_gpio,
|
|
config_.ethernet_spi_mosi_gpio, config_.ethernet_spi_miso_gpio,
|
|
config_.ethernet_spi_cs_gpio, config_.ethernet_spi_int_gpio,
|
|
config_.ethernet_phy_reset_gpio);
|
|
return ESP_OK;
|
|
#else
|
|
ESP_LOGW(kTag, "Ethernet requested but W5500 support is not enabled in esp-eth");
|
|
return ESP_ERR_NOT_SUPPORTED;
|
|
#endif
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::probeEthernetStartup() {
|
|
if (eth_handle_ == nullptr || !ethernet_started_) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
|
|
esp_err_t err = esp_eth_stop(eth_handle_);
|
|
ethernet_started_ = false;
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
|
|
err = esp_eth_start(eth_handle_);
|
|
if (err == ESP_OK) {
|
|
ethernet_started_ = true;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
void GatewayNetworkService::stopEthernet() {
|
|
if (ethernet_event_handlers_registered_) {
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_event_handler_unregister(
|
|
IP_EVENT, IP_EVENT_ETH_GOT_IP, &GatewayNetworkService::HandleEthernetEvent));
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_event_handler_unregister(
|
|
ETH_EVENT, ESP_EVENT_ANY_ID, &GatewayNetworkService::HandleEthernetEvent));
|
|
ethernet_event_handlers_registered_ = false;
|
|
}
|
|
|
|
if (eth_handle_ != nullptr) {
|
|
const esp_err_t stop_err = esp_eth_stop(eth_handle_);
|
|
if (stop_err != ESP_OK && stop_err != ESP_ERR_INVALID_STATE) {
|
|
ESP_LOGW(kTag, "failed to stop Ethernet during disable: %s", esp_err_to_name(stop_err));
|
|
}
|
|
ethernet_started_ = false;
|
|
}
|
|
|
|
if (eth_glue_ != nullptr) {
|
|
const esp_err_t glue_err = esp_eth_del_netif_glue(eth_glue_);
|
|
if (glue_err != ESP_OK) {
|
|
ESP_LOGW(kTag, "failed to delete Ethernet netif glue: %s", esp_err_to_name(glue_err));
|
|
} else {
|
|
eth_glue_ = nullptr;
|
|
}
|
|
}
|
|
|
|
if (eth_handle_ != nullptr) {
|
|
const esp_err_t uninstall_err = esp_eth_driver_uninstall(eth_handle_);
|
|
if (uninstall_err != ESP_OK) {
|
|
ESP_LOGW(kTag, "failed to uninstall Ethernet driver: %s", esp_err_to_name(uninstall_err));
|
|
} else {
|
|
eth_handle_ = nullptr;
|
|
}
|
|
}
|
|
|
|
if (eth_phy_ != nullptr && eth_handle_ == nullptr) {
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(eth_phy_->del(eth_phy_));
|
|
eth_phy_ = nullptr;
|
|
}
|
|
if (eth_mac_ != nullptr && eth_handle_ == nullptr) {
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(eth_mac_->del(eth_mac_));
|
|
eth_mac_ = nullptr;
|
|
}
|
|
|
|
if (eth_netif_ != nullptr && eth_glue_ == nullptr) {
|
|
esp_netif_destroy(eth_netif_);
|
|
eth_netif_ = nullptr;
|
|
}
|
|
|
|
runtime_.clearEthernetInfo();
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::startWifi() {
|
|
if (wifi_started_) {
|
|
return ESP_OK;
|
|
}
|
|
stopSmartconfig();
|
|
stopEspNow();
|
|
setup_ap_started_ = false;
|
|
|
|
if (wifi_sta_netif_ == nullptr) {
|
|
wifi_sta_netif_ = esp_netif_create_default_wifi_sta();
|
|
if (wifi_sta_netif_ == nullptr) {
|
|
ESP_LOGE(kTag, "failed to create default Wi-Fi STA netif");
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
}
|
|
|
|
wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT();
|
|
esp_err_t err = esp_wifi_init(&wifi_init_config);
|
|
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
|
ESP_LOGE(kTag, "failed to init Wi-Fi: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
|
|
if (!wifi_event_handlers_registered_) {
|
|
err = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
|
|
&GatewayNetworkService::HandleWifiEvent, this);
|
|
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
|
ESP_LOGE(kTag, "failed to register Wi-Fi event handler: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
|
|
err = esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
|
|
&GatewayNetworkService::HandleWifiEvent, this);
|
|
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
|
ESP_LOGE(kTag, "failed to register IP event handler: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
wifi_event_handlers_registered_ = true;
|
|
}
|
|
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_set_storage(WIFI_STORAGE_RAM));
|
|
err = esp_wifi_set_mode(WIFI_MODE_STA);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(kTag, "failed to set Wi-Fi mode STA: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
|
|
const auto device_info = runtime_.deviceInfo();
|
|
if (device_info.wlan.has_value() && !device_info.wlan->ssid.empty()) {
|
|
wifi_config_t wifi_config = {};
|
|
std::strncpy(reinterpret_cast<char*>(wifi_config.sta.ssid), device_info.wlan->ssid.c_str(),
|
|
sizeof(wifi_config.sta.ssid) - 1);
|
|
std::strncpy(reinterpret_cast<char*>(wifi_config.sta.password),
|
|
device_info.wlan->password.c_str(), sizeof(wifi_config.sta.password) - 1);
|
|
err = esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(kTag, "failed to set Wi-Fi credentials: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
}
|
|
|
|
err = esp_wifi_start();
|
|
if (err != ESP_OK && err != ESP_ERR_WIFI_CONN) {
|
|
ESP_LOGE(kTag, "failed to start Wi-Fi: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
|
|
uint8_t mac[6] = {0};
|
|
if (device_info.wlan.has_value() && !device_info.wlan->ssid.empty() &&
|
|
esp_wifi_get_mac(WIFI_IF_STA, mac) == ESP_OK) {
|
|
WirelessInfo wireless = device_info.wlan.value_or(WirelessInfo{});
|
|
wireless.mac = MacToHex(mac);
|
|
runtime_.setWirelessInfo(std::move(wireless));
|
|
}
|
|
|
|
wifi_started_ = true;
|
|
controller_.setWirelessSetupMode(false);
|
|
ESP_LOGI(kTag, "Wi-Fi STA started has_credentials=%d",
|
|
device_info.wlan.has_value() && !device_info.wlan->ssid.empty());
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::startSetupAp() {
|
|
stopSmartconfig();
|
|
if (wifi_ap_netif_ == nullptr) {
|
|
wifi_ap_netif_ = esp_netif_create_default_wifi_ap();
|
|
if (wifi_ap_netif_ == nullptr) {
|
|
ESP_LOGE(kTag, "failed to create default Wi-Fi AP netif");
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
esp_netif_ip_info_t ip_info = {};
|
|
IP4_ADDR(&ip_info.ip, 192, 168, 3, 1);
|
|
IP4_ADDR(&ip_info.gw, 192, 168, 3, 1);
|
|
IP4_ADDR(&ip_info.netmask, 255, 255, 255, 0);
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_netif_dhcps_stop(wifi_ap_netif_));
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_netif_set_ip_info(wifi_ap_netif_, &ip_info));
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_netif_dhcps_start(wifi_ap_netif_));
|
|
}
|
|
|
|
wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT();
|
|
esp_err_t err = esp_wifi_init(&wifi_init_config);
|
|
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
|
ESP_LOGE(kTag, "failed to init Wi-Fi for setup AP: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
|
|
if (wifi_started_) {
|
|
stopEspNow();
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_disconnect());
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_stop());
|
|
}
|
|
|
|
wifi_config_t ap_config = {};
|
|
std::strncpy(reinterpret_cast<char*>(ap_config.ap.ssid), kSetupApSsid,
|
|
sizeof(ap_config.ap.ssid) - 1);
|
|
ap_config.ap.ssid_len = std::strlen(kSetupApSsid);
|
|
ap_config.ap.channel = 1;
|
|
ap_config.ap.authmode = WIFI_AUTH_OPEN;
|
|
ap_config.ap.max_connection = 4;
|
|
|
|
err = esp_wifi_set_mode(WIFI_MODE_AP);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(kTag, "failed to set Wi-Fi AP mode: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
err = esp_wifi_set_config(WIFI_IF_AP, &ap_config);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(kTag, "failed to configure setup AP: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
err = esp_wifi_start();
|
|
if (err != ESP_OK && err != ESP_ERR_WIFI_CONN) {
|
|
ESP_LOGE(kTag, "failed to start setup AP: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
|
|
if (config_.espnow_setup_enabled) {
|
|
err = startEspNow();
|
|
if (err != ESP_OK) {
|
|
ESP_LOGW(kTag, "setup AP started without ESP-NOW: %s", esp_err_to_name(err));
|
|
}
|
|
}
|
|
|
|
wifi_started_ = true;
|
|
setup_ap_started_ = true;
|
|
controller_.setWirelessSetupMode(true);
|
|
ESP_LOGI(kTag, "setup AP started ssid=%s ip=192.168.3.1", kSetupApSsid);
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::startSmartconfig() {
|
|
if (!config_.smartconfig_enabled) {
|
|
ESP_LOGW(kTag, "smartconfig requested but not supported");
|
|
return ESP_ERR_NOT_SUPPORTED;
|
|
}
|
|
if (smartconfig_started_) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
config_.wifi_enabled = true;
|
|
if (setup_ap_started_) {
|
|
stopEspNow();
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_stop());
|
|
wifi_started_ = false;
|
|
setup_ap_started_ = false;
|
|
}
|
|
|
|
esp_err_t err = startWifi();
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
|
|
if (!smartconfig_event_handler_registered_) {
|
|
err = esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID,
|
|
&GatewayNetworkService::HandleWifiEvent, this);
|
|
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
|
ESP_LOGE(kTag, "failed to register smartconfig event handler: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
smartconfig_event_handler_registered_ = true;
|
|
}
|
|
|
|
err = esp_smartconfig_set_type(SC_TYPE_ESPTOUCH);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(kTag, "failed to set smartconfig type: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_esptouch_set_timeout(config_.smartconfig_timeout_sec));
|
|
|
|
smartconfig_start_config_t smartconfig = SMARTCONFIG_START_CONFIG_DEFAULT();
|
|
err = esp_smartconfig_start(&smartconfig);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(kTag, "failed to start smartconfig: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
|
|
smartconfig_pending_wireless_.reset();
|
|
smartconfig_started_ = true;
|
|
controller_.setWirelessSetupMode(true);
|
|
ESP_LOGI(kTag, "ESP-Touch smartconfig started timeout=%us", config_.smartconfig_timeout_sec);
|
|
return ESP_OK;
|
|
}
|
|
|
|
void GatewayNetworkService::stopSmartconfig() {
|
|
if (!smartconfig_started_) {
|
|
return;
|
|
}
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_smartconfig_stop());
|
|
smartconfig_started_ = false;
|
|
smartconfig_pending_wireless_.reset();
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::startEspNow() {
|
|
if (espnow_started_) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t err = esp_now_init();
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(kTag, "failed to init ESP-NOW: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
|
|
s_espnow_service = this;
|
|
err = esp_now_register_recv_cb(&GatewayNetworkService::HandleEspNowReceive);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(kTag, "failed to register ESP-NOW RX callback: %s", esp_err_to_name(err));
|
|
esp_now_deinit();
|
|
s_espnow_service = nullptr;
|
|
return err;
|
|
}
|
|
|
|
err = addEspNowPeer(kEspNowBroadcastMac, true);
|
|
if (err != ESP_OK) {
|
|
esp_now_unregister_recv_cb();
|
|
esp_now_deinit();
|
|
s_espnow_service = nullptr;
|
|
return err;
|
|
}
|
|
|
|
espnow_connected_ = false;
|
|
espnow_peer_.fill(0);
|
|
espnow_started_ = true;
|
|
ESP_LOGI(kTag, "ESP-NOW setup ingress started local_mac=%s", LocalMacHex(WIFI_IF_AP).c_str());
|
|
return ESP_OK;
|
|
}
|
|
|
|
void GatewayNetworkService::stopEspNow() {
|
|
if (!espnow_started_) {
|
|
return;
|
|
}
|
|
|
|
esp_now_unregister_recv_cb();
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_now_deinit());
|
|
if (s_espnow_service == this) {
|
|
s_espnow_service = nullptr;
|
|
}
|
|
espnow_connected_ = false;
|
|
espnow_started_ = false;
|
|
espnow_peer_.fill(0);
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::addEspNowPeer(const uint8_t* mac, bool broadcast) {
|
|
if (mac == nullptr) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
if (esp_now_is_peer_exist(mac)) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_now_peer_info_t peer = {};
|
|
std::memcpy(peer.peer_addr, mac, sizeof(peer.peer_addr));
|
|
peer.channel = 0;
|
|
peer.ifidx = setup_ap_started_ || broadcast ? WIFI_IF_AP : WIFI_IF_STA;
|
|
peer.encrypt = false;
|
|
esp_err_t err = esp_now_add_peer(&peer);
|
|
if (err == ESP_ERR_ESPNOW_EXIST) {
|
|
return ESP_OK;
|
|
}
|
|
if (err != ESP_OK) {
|
|
ESP_LOGW(kTag, "failed to add ESP-NOW peer %s: %s", MacToHex(mac).c_str(),
|
|
esp_err_to_name(err));
|
|
}
|
|
return err;
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::sendEspNowJson(const uint8_t* mac, const std::string& payload) {
|
|
if (mac == nullptr || payload.empty()) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
esp_err_t err = addEspNowPeer(mac, std::memcmp(mac, kEspNowBroadcastMac, 6) == 0);
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
return esp_now_send(mac, reinterpret_cast<const uint8_t*>(payload.data()), payload.size());
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::configureStatusLed() {
|
|
if (config_.status_led_gpio < 0) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
gpio_config_t io_config = {};
|
|
io_config.pin_bit_mask = 1ULL << static_cast<uint32_t>(config_.status_led_gpio);
|
|
io_config.mode = GPIO_MODE_OUTPUT;
|
|
io_config.pull_up_en = GPIO_PULLUP_DISABLE;
|
|
io_config.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
|
io_config.intr_type = GPIO_INTR_DISABLE;
|
|
const esp_err_t err = gpio_config(&io_config);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(kTag, "failed to configure status LED GPIO%d: %s", config_.status_led_gpio,
|
|
esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
setStatusLed(false);
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::configureBootButton() {
|
|
if (config_.boot_button_gpio < 0) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
gpio_config_t io_config = {};
|
|
io_config.pin_bit_mask = 1ULL << static_cast<uint32_t>(config_.boot_button_gpio);
|
|
io_config.mode = GPIO_MODE_INPUT;
|
|
io_config.pull_up_en = config_.boot_button_active_low ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
|
|
io_config.pull_down_en = config_.boot_button_active_low ? GPIO_PULLDOWN_DISABLE : GPIO_PULLDOWN_ENABLE;
|
|
io_config.intr_type = GPIO_INTR_DISABLE;
|
|
const esp_err_t err = gpio_config(&io_config);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(kTag, "failed to configure boot button GPIO%d: %s", config_.boot_button_gpio,
|
|
esp_err_to_name(err));
|
|
}
|
|
return err;
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::startHttpServer() {
|
|
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
|
config.server_port = config_.http_port;
|
|
config.lru_purge_enable = true;
|
|
|
|
esp_err_t err = httpd_start(&http_server_, &config);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(kTag, "failed to start HTTP server: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
|
|
struct Route {
|
|
const char* uri;
|
|
httpd_method_t method;
|
|
esp_err_t (*handler)(httpd_req_t*);
|
|
};
|
|
|
|
const Route routes[] = {
|
|
{"/info", HTTP_GET, &GatewayNetworkService::HandleInfoGet},
|
|
{"/dali/cmd", HTTP_GET, &GatewayNetworkService::HandleCommandGet},
|
|
{"/dali/cmd", HTTP_POST, &GatewayNetworkService::HandleCommandPost},
|
|
{"/bridge", HTTP_GET, &GatewayNetworkService::HandleBridgeGet},
|
|
{"/bridge", HTTP_POST, &GatewayNetworkService::HandleBridgePost},
|
|
{"/led/1", HTTP_GET, &GatewayNetworkService::HandleLedOnGet},
|
|
{"/led/0", HTTP_GET, &GatewayNetworkService::HandleLedOffGet},
|
|
{"/jq.js", HTTP_GET, &GatewayNetworkService::HandleJqJsGet},
|
|
};
|
|
|
|
for (const auto& route : routes) {
|
|
err = RegisterUri(http_server_, route.uri, route.method, route.handler, this);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(kTag, "failed to register %s handler: %s", route.uri, esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::startUdpTask() {
|
|
if (udp_task_handle_ != nullptr) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
const BaseType_t created =
|
|
xTaskCreate(&GatewayNetworkService::UdpTaskEntry, "gateway_udp",
|
|
config_.udp_task_stack_size, this, config_.udp_task_priority,
|
|
&udp_task_handle_);
|
|
if (created != pdPASS) {
|
|
udp_task_handle_ = nullptr;
|
|
ESP_LOGE(kTag, "failed to create UDP task");
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::startBootButtonTask() {
|
|
if (config_.boot_button_gpio < 0 || boot_button_task_handle_ != nullptr) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
const BaseType_t created =
|
|
xTaskCreate(&GatewayNetworkService::BootButtonTaskEntry, "gateway_boot_btn",
|
|
config_.boot_button_task_stack_size, this, config_.boot_button_task_priority,
|
|
&boot_button_task_handle_);
|
|
if (created != pdPASS) {
|
|
boot_button_task_handle_ = nullptr;
|
|
ESP_LOGE(kTag, "failed to create boot button task");
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
void GatewayNetworkService::UdpTaskEntry(void* arg) {
|
|
static_cast<GatewayNetworkService*>(arg)->udpTaskLoop();
|
|
}
|
|
|
|
void GatewayNetworkService::BootButtonTaskEntry(void* arg) {
|
|
static_cast<GatewayNetworkService*>(arg)->bootButtonTaskLoop();
|
|
}
|
|
|
|
void GatewayNetworkService::HandleWifiEvent(void* arg, esp_event_base_t event_base,
|
|
int32_t event_id, void* event_data) {
|
|
auto* service = static_cast<GatewayNetworkService*>(arg);
|
|
if (service != nullptr) {
|
|
service->handleWifiEvent(event_base, event_id, event_data);
|
|
}
|
|
}
|
|
|
|
void GatewayNetworkService::HandleEspNowReceive(const esp_now_recv_info_t* info,
|
|
const uint8_t* data, int data_len) {
|
|
if (s_espnow_service != nullptr) {
|
|
s_espnow_service->handleEspNowReceive(info, data, data_len);
|
|
}
|
|
}
|
|
|
|
void GatewayNetworkService::HandleEthernetEvent(void* arg, esp_event_base_t event_base,
|
|
int32_t event_id, void* event_data) {
|
|
auto* service = static_cast<GatewayNetworkService*>(arg);
|
|
if (service != nullptr) {
|
|
service->handleEthernetEvent(event_base, event_id, event_data);
|
|
}
|
|
}
|
|
|
|
void GatewayNetworkService::handleEthernetEvent(esp_event_base_t event_base, int32_t event_id,
|
|
void* event_data) {
|
|
if (event_base == ETH_EVENT) {
|
|
esp_eth_handle_t handle = eth_handle_;
|
|
if (event_data != nullptr) {
|
|
handle = *static_cast<esp_eth_handle_t*>(event_data);
|
|
}
|
|
|
|
if (event_id == ETHERNET_EVENT_CONNECTED) {
|
|
uint8_t mac[6] = {};
|
|
if (handle != nullptr && esp_eth_ioctl(handle, ETH_CMD_G_MAC_ADDR, mac) == ESP_OK) {
|
|
const std::string mac_hex = MacToHex(mac);
|
|
EthernetInfo info = runtime_.deviceInfo().eth.value_or(EthernetInfo{});
|
|
info.mac = mac_hex;
|
|
runtime_.setEthernetInfo(std::move(info));
|
|
ESP_LOGI(kTag, "Ethernet link up mac=%s", mac_hex.c_str());
|
|
} else {
|
|
ESP_LOGI(kTag, "Ethernet link up");
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (event_id == ETHERNET_EVENT_DISCONNECTED) {
|
|
runtime_.clearEthernetIp();
|
|
ESP_LOGI(kTag, "Ethernet link down");
|
|
return;
|
|
}
|
|
|
|
if (event_id == ETHERNET_EVENT_START) {
|
|
ESP_LOGI(kTag, "Ethernet driver started");
|
|
return;
|
|
}
|
|
|
|
if (event_id == ETHERNET_EVENT_STOP) {
|
|
runtime_.clearEthernetIp();
|
|
ESP_LOGI(kTag, "Ethernet driver stopped");
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (event_base == IP_EVENT && event_id == IP_EVENT_ETH_GOT_IP && event_data != nullptr) {
|
|
auto* event = static_cast<ip_event_got_ip_t*>(event_data);
|
|
char ip[16] = {0};
|
|
esp_ip4addr_ntoa(&event->ip_info.ip, ip, sizeof(ip));
|
|
EthernetInfo info = runtime_.deviceInfo().eth.value_or(EthernetInfo{});
|
|
uint8_t mac[6] = {};
|
|
if (eth_handle_ != nullptr && esp_eth_ioctl(eth_handle_, ETH_CMD_G_MAC_ADDR, mac) == ESP_OK) {
|
|
info.mac = MacToHex(mac);
|
|
}
|
|
info.ip = ip;
|
|
runtime_.setEthernetInfo(std::move(info));
|
|
ESP_LOGI(kTag, "Ethernet got IP %s", ip);
|
|
}
|
|
}
|
|
|
|
void GatewayNetworkService::handleWifiEvent(esp_event_base_t event_base, int32_t event_id,
|
|
void* event_data) {
|
|
if (!config_.wifi_enabled) {
|
|
return;
|
|
}
|
|
|
|
if (event_base == SC_EVENT) {
|
|
if (event_id == SC_EVENT_SCAN_DONE) {
|
|
ESP_LOGI(kTag, "smartconfig scan done");
|
|
} else if (event_id == SC_EVENT_FOUND_CHANNEL) {
|
|
ESP_LOGI(kTag, "smartconfig found channel");
|
|
} else if (event_id == SC_EVENT_GOT_SSID_PSWD && event_data != nullptr) {
|
|
auto* event = static_cast<smartconfig_event_got_ssid_pswd_t*>(event_data);
|
|
wifi_config_t wifi_config = {};
|
|
std::memcpy(wifi_config.sta.ssid, event->ssid, sizeof(wifi_config.sta.ssid));
|
|
std::memcpy(wifi_config.sta.password, event->password, sizeof(wifi_config.sta.password));
|
|
wifi_config.sta.bssid_set = event->bssid_set;
|
|
if (event->bssid_set) {
|
|
std::memcpy(wifi_config.sta.bssid, event->bssid, sizeof(wifi_config.sta.bssid));
|
|
}
|
|
|
|
WirelessInfo wireless;
|
|
wireless.ssid = BoundedString(event->ssid, sizeof(event->ssid));
|
|
wireless.password = BoundedString(event->password, sizeof(event->password));
|
|
uint8_t mac[6] = {};
|
|
if (esp_wifi_get_mac(WIFI_IF_STA, mac) == ESP_OK) {
|
|
wireless.mac = MacToHex(mac);
|
|
}
|
|
smartconfig_pending_wireless_ = wireless;
|
|
|
|
ESP_LOGI(kTag, "smartconfig got credentials ssid=%s", wireless.ssid.c_str());
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_disconnect());
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_connect());
|
|
} else if (event_id == SC_EVENT_SEND_ACK_DONE) {
|
|
stopSmartconfig();
|
|
controller_.setWirelessSetupMode(false);
|
|
ESP_LOGI(kTag, "smartconfig ACK sent");
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
|
|
const auto info = runtime_.deviceInfo();
|
|
if (info.wlan.has_value() && !info.wlan->ssid.empty()) {
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_connect());
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
|
auto info = runtime_.deviceInfo();
|
|
if (info.wlan.has_value()) {
|
|
const bool has_credentials = !info.wlan->ssid.empty();
|
|
info.wlan->ip.clear();
|
|
runtime_.setWirelessInfo(std::move(*info.wlan));
|
|
if (has_credentials && !smartconfig_started_) {
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_connect());
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP && event_data != nullptr) {
|
|
auto* event = static_cast<ip_event_got_ip_t*>(event_data);
|
|
char ip[16] = {0};
|
|
esp_ip4addr_ntoa(&event->ip_info.ip, ip, sizeof(ip));
|
|
WirelessInfo wireless = smartconfig_pending_wireless_.value_or(
|
|
runtime_.deviceInfo().wlan.value_or(WirelessInfo{}));
|
|
wireless.ip = ip;
|
|
uint8_t mac[6] = {};
|
|
if (esp_wifi_get_mac(WIFI_IF_STA, mac) == ESP_OK) {
|
|
wireless.mac = MacToHex(mac);
|
|
}
|
|
runtime_.setWirelessInfo(std::move(wireless));
|
|
ESP_LOGI(kTag, "Wi-Fi got IP %s", ip);
|
|
}
|
|
}
|
|
|
|
void GatewayNetworkService::handleEspNowReceive(const esp_now_recv_info_t* info,
|
|
const uint8_t* data, int data_len) {
|
|
if (!espnow_started_ || info == nullptr || info->src_addr == nullptr || data == nullptr ||
|
|
data_len <= 0) {
|
|
return;
|
|
}
|
|
|
|
const std::string payload(reinterpret_cast<const char*>(data),
|
|
static_cast<size_t>(data_len));
|
|
cJSON* root = cJSON_Parse(payload.c_str());
|
|
if (root == nullptr) {
|
|
ESP_LOGW(kTag, "ignored non-JSON ESP-NOW setup packet len=%d", data_len);
|
|
return;
|
|
}
|
|
|
|
const char* type = JsonString(root, "type");
|
|
if (type == nullptr) {
|
|
cJSON_Delete(root);
|
|
return;
|
|
}
|
|
|
|
addEspNowPeer(info->src_addr);
|
|
const std::string local_mac = LocalMacHex(WIFI_IF_AP);
|
|
|
|
if (std::strcmp(type, "connReq") == 0) {
|
|
cJSON* response = cJSON_CreateObject();
|
|
if (response != nullptr) {
|
|
cJSON_AddStringToObject(response, "type", "connRsp");
|
|
cJSON_AddStringToObject(response, "data", "");
|
|
cJSON_AddStringToObject(response, "dst", MacToHex(info->src_addr).c_str());
|
|
cJSON_AddStringToObject(response, "src", local_mac.c_str());
|
|
cJSON_AddStringToObject(response, "pmk", "");
|
|
const std::string rendered = PrintJson(response);
|
|
sendEspNowJson(kEspNowBroadcastMac, rendered);
|
|
cJSON_Delete(response);
|
|
}
|
|
cJSON_Delete(root);
|
|
return;
|
|
}
|
|
|
|
if (std::strcmp(type, "connAck") == 0) {
|
|
if (!espnow_connected_) {
|
|
std::memcpy(espnow_peer_.data(), info->src_addr, espnow_peer_.size());
|
|
espnow_connected_ = true;
|
|
ESP_LOGI(kTag, "ESP-NOW setup peer connected mac=%s", MacToHex(info->src_addr).c_str());
|
|
}
|
|
cJSON_Delete(root);
|
|
return;
|
|
}
|
|
|
|
if (std::strcmp(type, "echo") == 0) {
|
|
if (espnow_connected_) {
|
|
cJSON* response = cJSON_CreateObject();
|
|
if (response != nullptr) {
|
|
cJSON_AddStringToObject(response, "type", "echoRsp");
|
|
cJSON_AddStringToObject(response, "data", "");
|
|
const std::string rendered = PrintJson(response);
|
|
sendEspNowJson(info->src_addr, rendered);
|
|
cJSON_Delete(response);
|
|
}
|
|
}
|
|
cJSON_Delete(root);
|
|
return;
|
|
}
|
|
|
|
if (std::strcmp(type, "cmd") == 0 || std::strcmp(type, "data") == 0) {
|
|
const auto frame = BytesFromJsonString(JsonString(root, "data"));
|
|
if (!frame.empty()) {
|
|
controller_.enqueueCommandFrame(frame);
|
|
}
|
|
cJSON_Delete(root);
|
|
return;
|
|
}
|
|
|
|
if (std::strcmp(type, "uart") == 0) {
|
|
cJSON* num = cJSON_GetObjectItem(root, "num");
|
|
const auto frame = BytesFromJsonString(JsonString(root, "data"));
|
|
if (cJSON_IsNumber(num) && !frame.empty()) {
|
|
handleSetupUartFrame(num->valueint, frame);
|
|
}
|
|
cJSON_Delete(root);
|
|
return;
|
|
}
|
|
|
|
cJSON_Delete(root);
|
|
}
|
|
|
|
void GatewayNetworkService::handleSetupUartFrame(int setup_id,
|
|
const std::vector<uint8_t>& frame) {
|
|
if (frame.empty()) {
|
|
return;
|
|
}
|
|
|
|
if (frame.size() >= 7) {
|
|
controller_.enqueueCommandFrame(frame);
|
|
return;
|
|
}
|
|
|
|
const uint8_t channel_index = static_cast<uint8_t>(setup_id - 3);
|
|
for (const auto& channel : dali_domain_.channelInfo()) {
|
|
if (channel.channel_index == channel_index) {
|
|
if (!dali_domain_.writeBridgeFrame(channel.gateway_id, frame.data(), frame.size())) {
|
|
ESP_LOGW(kTag, "failed to forward ESP-NOW setup UART%d frame to gateway=%u", setup_id,
|
|
channel.gateway_id);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
ESP_LOGW(kTag, "ignored setup UART%d frame for unbound DALI channel", setup_id);
|
|
}
|
|
|
|
void GatewayNetworkService::handleDaliRawFrame(const DaliRawFrame& frame) {
|
|
if (!espnow_started_ || !espnow_connected_ || frame.data.empty()) {
|
|
return;
|
|
}
|
|
|
|
cJSON* payload = cJSON_CreateObject();
|
|
if (payload == nullptr) {
|
|
return;
|
|
}
|
|
cJSON_AddStringToObject(payload, "type", "uart");
|
|
cJSON_AddNumberToObject(payload, "num", static_cast<double>(frame.channel_index + 3));
|
|
const std::string data_hex = BytesToHex(frame.data);
|
|
cJSON_AddStringToObject(payload, "data", data_hex.c_str());
|
|
|
|
const std::string rendered = PrintJson(payload);
|
|
cJSON_Delete(payload);
|
|
if (!rendered.empty()) {
|
|
sendEspNowJson(espnow_peer_.data(), rendered);
|
|
}
|
|
}
|
|
|
|
void GatewayNetworkService::udpTaskLoop() {
|
|
udp_socket_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
|
if (udp_socket_ < 0) {
|
|
ESP_LOGE(kTag, "failed to create UDP socket");
|
|
udp_task_handle_ = nullptr;
|
|
vTaskDelete(nullptr);
|
|
return;
|
|
}
|
|
|
|
sockaddr_in local_addr = {};
|
|
local_addr.sin_family = AF_INET;
|
|
local_addr.sin_port = htons(config_.udp_port);
|
|
local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
if (bind(udp_socket_, reinterpret_cast<sockaddr*>(&local_addr), sizeof(local_addr)) != 0) {
|
|
ESP_LOGE(kTag, "failed to bind UDP socket on port %u", config_.udp_port);
|
|
close(udp_socket_);
|
|
udp_socket_ = -1;
|
|
udp_task_handle_ = nullptr;
|
|
vTaskDelete(nullptr);
|
|
return;
|
|
}
|
|
|
|
ESP_LOGI(kTag, "UDP router listening on port %u", config_.udp_port);
|
|
while (true) {
|
|
uint8_t buffer[kUdpBufferSize] = {0};
|
|
sockaddr_storage remote_addr = {};
|
|
socklen_t remote_addr_len = sizeof(remote_addr);
|
|
const int read_len = recvfrom(udp_socket_, buffer, sizeof(buffer), 0,
|
|
reinterpret_cast<sockaddr*>(&remote_addr),
|
|
&remote_addr_len);
|
|
if (read_len <= 0) {
|
|
continue;
|
|
}
|
|
|
|
{
|
|
LockGuard guard(udp_lock_);
|
|
udp_remote_addr_ = remote_addr;
|
|
udp_remote_addr_len_ = remote_addr_len;
|
|
has_udp_remote_ = true;
|
|
}
|
|
|
|
controller_.enqueueCommandFrame(std::vector<uint8_t>(buffer, buffer + read_len));
|
|
}
|
|
}
|
|
|
|
void GatewayNetworkService::bootButtonTaskLoop() {
|
|
vTaskDelay(pdMS_TO_TICKS(2000));
|
|
const TickType_t poll_ticks = pdMS_TO_TICKS(100);
|
|
const uint32_t long_press_ms = std::max<uint32_t>(config_.boot_button_long_press_ms, 100);
|
|
|
|
auto is_pressed = [this]() {
|
|
const int level = gpio_get_level(static_cast<gpio_num_t>(config_.boot_button_gpio));
|
|
return config_.boot_button_active_low ? level == 0 : level != 0;
|
|
};
|
|
|
|
while (true) {
|
|
if (!is_pressed()) {
|
|
vTaskDelay(poll_ticks);
|
|
continue;
|
|
}
|
|
|
|
uint32_t pressed_ms = 0;
|
|
while (is_pressed()) {
|
|
vTaskDelay(poll_ticks);
|
|
pressed_ms += 100;
|
|
}
|
|
|
|
if (pressed_ms >= long_press_ms) {
|
|
ESP_LOGW(kTag, "BOOT long press clears Wi-Fi credentials and restarts");
|
|
runtime_.clearWirelessInfo();
|
|
stopEspNow();
|
|
if (wifi_started_) {
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_disconnect());
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_stop());
|
|
}
|
|
vTaskDelay(pdMS_TO_TICKS(300));
|
|
esp_restart();
|
|
} else {
|
|
ESP_LOGI(kTag, "BOOT short press enters setup AP mode");
|
|
handleWifiControl(101);
|
|
}
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(300));
|
|
}
|
|
}
|
|
|
|
void GatewayNetworkService::handleGatewayNotification(const std::vector<uint8_t>& frame) {
|
|
if (!config_.udp_enabled || udp_socket_ < 0 || frame.empty()) {
|
|
return;
|
|
}
|
|
|
|
sockaddr_storage remote_addr = {};
|
|
socklen_t remote_addr_len = 0;
|
|
{
|
|
LockGuard guard(udp_lock_);
|
|
if (!has_udp_remote_) {
|
|
return;
|
|
}
|
|
remote_addr = udp_remote_addr_;
|
|
remote_addr_len = udp_remote_addr_len_;
|
|
}
|
|
|
|
sendto(udp_socket_, frame.data(), frame.size(), 0,
|
|
reinterpret_cast<const sockaddr*>(&remote_addr), remote_addr_len);
|
|
}
|
|
|
|
void GatewayNetworkService::handleWifiControl(uint8_t mode) {
|
|
if (mode == 0) {
|
|
config_.wifi_enabled = false;
|
|
stopSmartconfig();
|
|
stopEspNow();
|
|
controller_.setWirelessSetupMode(false);
|
|
if (wifi_started_) {
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_disconnect());
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_stop());
|
|
wifi_started_ = false;
|
|
setup_ap_started_ = false;
|
|
}
|
|
auto info = runtime_.deviceInfo();
|
|
if (info.wlan.has_value()) {
|
|
info.wlan->ip.clear();
|
|
runtime_.setWirelessInfo(std::move(*info.wlan));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (mode == 101) {
|
|
config_.wifi_enabled = true;
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(startSetupAp());
|
|
return;
|
|
}
|
|
if (mode == 100) {
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(startSmartconfig());
|
|
return;
|
|
}
|
|
if (mode == 1) {
|
|
config_.wifi_enabled = true;
|
|
stopSmartconfig();
|
|
if (setup_ap_started_) {
|
|
stopEspNow();
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_stop());
|
|
wifi_started_ = false;
|
|
setup_ap_started_ = false;
|
|
}
|
|
ESP_ERROR_CHECK_WITHOUT_ABORT(startWifi());
|
|
}
|
|
}
|
|
|
|
std::string GatewayNetworkService::deviceInfoJson() const {
|
|
const auto info = runtime_.deviceInfo();
|
|
cJSON* root = cJSON_CreateObject();
|
|
if (root == nullptr) {
|
|
return {};
|
|
}
|
|
|
|
cJSON_AddStringToObject(root, "serialId", info.serial_id.c_str());
|
|
cJSON_AddStringToObject(root, "type", info.type.c_str());
|
|
cJSON_AddStringToObject(root, "project", info.project.c_str());
|
|
cJSON_AddStringToObject(root, "version", info.version.c_str());
|
|
|
|
cJSON* gateway_info = cJSON_CreateObject();
|
|
if (gateway_info != nullptr) {
|
|
cJSON_AddNumberToObject(gateway_info, "count",
|
|
static_cast<double>(info.dali_gateway_count));
|
|
cJSON_AddItemToObject(root, "daliGatewayInfo", gateway_info);
|
|
}
|
|
|
|
if (info.wlan.has_value()) {
|
|
cJSON* wlan = cJSON_CreateObject();
|
|
if (wlan != nullptr) {
|
|
cJSON_AddStringToObject(wlan, "mac", info.wlan->mac.c_str());
|
|
cJSON_AddStringToObject(wlan, "IP", info.wlan->ip.c_str());
|
|
cJSON_AddStringToObject(wlan, "ssid", info.wlan->ssid.c_str());
|
|
cJSON_AddStringToObject(wlan, "passwd", info.wlan->password.c_str());
|
|
cJSON_AddItemToObject(root, "wlanInfo", wlan);
|
|
}
|
|
}
|
|
|
|
if (info.eth.has_value()) {
|
|
cJSON* eth = cJSON_CreateObject();
|
|
if (eth != nullptr) {
|
|
cJSON_AddStringToObject(eth, "mac", info.eth->mac.c_str());
|
|
cJSON_AddStringToObject(eth, "IP", info.eth->ip.c_str());
|
|
cJSON_AddItemToObject(root, "ethInfo", eth);
|
|
}
|
|
}
|
|
|
|
const std::string rendered = PrintJson(root);
|
|
cJSON_Delete(root);
|
|
return rendered;
|
|
}
|
|
|
|
std::string GatewayNetworkService::deviceInfoDoubleEncodedJson() const {
|
|
const std::string inner = deviceInfoJson();
|
|
cJSON* outer = cJSON_CreateString(inner.c_str());
|
|
if (outer == nullptr) {
|
|
return {};
|
|
}
|
|
|
|
const std::string rendered = PrintJson(outer);
|
|
cJSON_Delete(outer);
|
|
return rendered;
|
|
}
|
|
|
|
std::string GatewayNetworkService::gatewaySnapshotJson() {
|
|
const auto snapshot = controller_.snapshot();
|
|
cJSON* root = cJSON_CreateObject();
|
|
if (root == nullptr) {
|
|
return {};
|
|
}
|
|
|
|
cJSON_AddNumberToObject(root, "count", static_cast<double>(snapshot.channels.size()));
|
|
|
|
cJSON* gw_info = cJSON_CreateObject();
|
|
if (gw_info != nullptr) {
|
|
cJSON_AddBoolToObject(gw_info, "setupMode", snapshot.setup_mode);
|
|
cJSON_AddBoolToObject(gw_info, "wlSetupMode", snapshot.wireless_setup_mode);
|
|
cJSON_AddBoolToObject(gw_info, "bleEnabled", snapshot.ble_enabled);
|
|
cJSON_AddBoolToObject(gw_info, "wifiEnabled", snapshot.wifi_enabled);
|
|
cJSON_AddBoolToObject(gw_info, "IPRouter", snapshot.ip_router_enabled);
|
|
cJSON_AddBoolToObject(gw_info, "iSceneEnabled", snapshot.internal_scene_supported);
|
|
cJSON_AddBoolToObject(gw_info, "iGroupEnabled", snapshot.internal_group_supported);
|
|
cJSON_AddItemToObject(root, "gwInfo", gw_info);
|
|
}
|
|
|
|
cJSON* channels = cJSON_CreateArray();
|
|
if (channels != nullptr) {
|
|
for (const auto& channel : snapshot.channels) {
|
|
cJSON* item = cJSON_CreateObject();
|
|
if (item == nullptr) {
|
|
continue;
|
|
}
|
|
cJSON_AddNumberToObject(item, "channel", channel.channel_index + 1);
|
|
cJSON_AddNumberToObject(item, "gw", channel.gateway_id);
|
|
cJSON_AddStringToObject(item, "name", channel.name.c_str());
|
|
cJSON_AddStringToObject(item, "phy", channel.phy.c_str());
|
|
cJSON_AddNumberToObject(item, "sceneMaskLow", channel.scene_mask_low);
|
|
cJSON_AddNumberToObject(item, "sceneMaskHigh", channel.scene_mask_high);
|
|
cJSON_AddNumberToObject(item, "groupMaskLow", channel.group_mask_low);
|
|
cJSON_AddNumberToObject(item, "groupMaskHigh", channel.group_mask_high);
|
|
cJSON_AddBoolToObject(item, "isAllocAddr", channel.allocating);
|
|
cJSON_AddNumberToObject(item, "lastAllocAddr", channel.last_alloc_addr);
|
|
cJSON_AddItemToArray(channels, item);
|
|
}
|
|
cJSON_AddItemToObject(root, "channels", channels);
|
|
}
|
|
|
|
const std::string rendered = PrintJson(root);
|
|
cJSON_Delete(root);
|
|
return rendered;
|
|
}
|
|
|
|
void GatewayNetworkService::setStatusLed(bool on) {
|
|
if (config_.status_led_gpio < 0) {
|
|
return;
|
|
}
|
|
const bool level = config_.status_led_active_high ? on : !on;
|
|
gpio_set_level(static_cast<gpio_num_t>(config_.status_led_gpio), level ? 1 : 0);
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::HandleInfoGet(httpd_req_t* req) {
|
|
auto* service = static_cast<GatewayNetworkService*>(req->user_ctx);
|
|
if (service == nullptr) {
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
const std::string payload = service->deviceInfoDoubleEncodedJson();
|
|
httpd_resp_set_type(req, "application/json");
|
|
return httpd_resp_send(req, payload.data(), payload.size());
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::HandleCommandGet(httpd_req_t* req) {
|
|
auto* service = static_cast<GatewayNetworkService*>(req->user_ctx);
|
|
if (service == nullptr) {
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
const std::string payload = service->gatewaySnapshotJson();
|
|
httpd_resp_set_type(req, "application/json");
|
|
return httpd_resp_send(req, payload.data(), payload.size());
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::HandleCommandPost(httpd_req_t* req) {
|
|
auto* service = static_cast<GatewayNetworkService*>(req->user_ctx);
|
|
if (service == nullptr) {
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
std::string body;
|
|
if (ReadRequestBody(req, body) != ESP_OK) {
|
|
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Bad Request");
|
|
}
|
|
|
|
cJSON* root = cJSON_ParseWithLength(body.c_str(), body.size());
|
|
if (root == nullptr) {
|
|
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Bad Request: json decode error");
|
|
}
|
|
|
|
const cJSON* command = cJSON_GetObjectItemCaseSensitive(root, "command");
|
|
if (!cJSON_IsString(command) || command->valuestring == nullptr) {
|
|
cJSON_Delete(root);
|
|
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Bad Request: missing command");
|
|
}
|
|
|
|
const auto frame = DecodeHex(command->valuestring);
|
|
cJSON_Delete(root);
|
|
if (frame.empty()) {
|
|
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Bad Request: invalid command hex");
|
|
}
|
|
|
|
service->controller_.enqueueCommandFrame(frame);
|
|
httpd_resp_set_type(req, "text/plain");
|
|
return httpd_resp_sendstr(req, "ok");
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::HandleBridgeGet(httpd_req_t* req) {
|
|
auto* service = static_cast<GatewayNetworkService*>(req->user_ctx);
|
|
if (service == nullptr) {
|
|
return ESP_FAIL;
|
|
}
|
|
return service->sendBridgeResponse(req, false);
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::HandleBridgePost(httpd_req_t* req) {
|
|
auto* service = static_cast<GatewayNetworkService*>(req->user_ctx);
|
|
if (service == nullptr) {
|
|
return ESP_FAIL;
|
|
}
|
|
return service->sendBridgeResponse(req, true);
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::sendBridgeResponse(httpd_req_t* req, bool post) {
|
|
if (bridge_service_ == nullptr) {
|
|
return httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Bridge service is not enabled");
|
|
}
|
|
|
|
const std::string action = QueryValue(req, "action");
|
|
const auto gateway_id = QueryGatewayId(req);
|
|
const int selected_gateway_id = gateway_id.has_value() ? gateway_id.value() : -1;
|
|
if (!post) {
|
|
return SendJsonResponse(req, bridge_service_->handleGet(action, selected_gateway_id,
|
|
QueryString(req)));
|
|
}
|
|
|
|
std::string body;
|
|
if (ReadRequestBody(req, body) != ESP_OK) {
|
|
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Bad Request");
|
|
}
|
|
return SendJsonResponse(req, bridge_service_->handlePost(action, selected_gateway_id, body));
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::HandleLedOnGet(httpd_req_t* req) {
|
|
auto* service = static_cast<GatewayNetworkService*>(req->user_ctx);
|
|
if (service == nullptr) {
|
|
return ESP_FAIL;
|
|
}
|
|
service->setStatusLed(true);
|
|
httpd_resp_set_type(req, "text/plain");
|
|
return httpd_resp_sendstr(req, "ok");
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::HandleLedOffGet(httpd_req_t* req) {
|
|
auto* service = static_cast<GatewayNetworkService*>(req->user_ctx);
|
|
if (service == nullptr) {
|
|
return ESP_FAIL;
|
|
}
|
|
service->setStatusLed(false);
|
|
httpd_resp_set_type(req, "text/plain");
|
|
return httpd_resp_sendstr(req, "ok");
|
|
}
|
|
|
|
esp_err_t GatewayNetworkService::HandleJqJsGet(httpd_req_t* req) {
|
|
FILE* file = std::fopen("/jq.js", "rb");
|
|
if (file == nullptr) {
|
|
return httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Not Found/jq.js");
|
|
}
|
|
|
|
httpd_resp_set_type(req, "application/javascript");
|
|
char buffer[512] = {0};
|
|
while (true) {
|
|
const size_t read_len = std::fread(buffer, 1, sizeof(buffer), file);
|
|
if (read_len > 0 && httpd_resp_send_chunk(req, buffer, read_len) != ESP_OK) {
|
|
std::fclose(file);
|
|
return ESP_FAIL;
|
|
}
|
|
if (read_len < sizeof(buffer)) {
|
|
break;
|
|
}
|
|
}
|
|
std::fclose(file);
|
|
return httpd_resp_send_chunk(req, nullptr, 0);
|
|
}
|
|
|
|
} // namespace gateway
|