feat(ssdp) new method to be discovered in a network
This commit is contained in:
parent
3208261f97
commit
c30c145a4a
|
@ -0,0 +1,8 @@
|
|||
file(GLOB SOURCES ssdp.c
|
||||
)
|
||||
|
||||
idf_component_register(
|
||||
SRCS ${SOURCES}
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES esp_netif memory_pool wt_storage
|
||||
)
|
|
@ -0,0 +1,372 @@
|
|||
#include "ssdp.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_mac.h"
|
||||
#include "esp_netif.h"
|
||||
#include "lwip/err.h"
|
||||
#include "lwip/sockets.h"
|
||||
#include "memory_pool.h"
|
||||
#include "wt_nvs.h"
|
||||
|
||||
#define TAG "SSDP"
|
||||
|
||||
#define SSDP_PORT 1900
|
||||
#define SSDP_MULTICAST_TTL 4
|
||||
#define SSDP_UUID_BASE "9079d7e9-92c6-45b3-aa28-77cdcc"
|
||||
#define SSDP_MULTICAST_ADDR "239.255.255.250"
|
||||
#define HEADER_SEARCH "M-SEARCH"
|
||||
#define SSDP_DEVICE_TYPE "urn:schemas-upnp-org:device:Basic:1"
|
||||
#define SSDP_MODEL_NAME "wireless-proxy"
|
||||
#define SSDP_DEFAULT_FRIENDLY_NAME "允斯调试器"
|
||||
#define SSDP_MANUFACTURER "允斯工作室"
|
||||
|
||||
#ifndef SSDP_MODEL_URL
|
||||
#define SSDP_MODEL_URL "https://yunsi.studio/"
|
||||
#endif
|
||||
|
||||
static struct ssdp_ctx_t {
|
||||
TaskHandle_t ssdp_service_task;
|
||||
ip4_addr_t ip;
|
||||
ip4_addr_t gw;
|
||||
uint8_t uuid_end[3]; /* actually use MAC[3:5] */
|
||||
char friendly_name[32];
|
||||
} ssdp_ctx;
|
||||
|
||||
static volatile uint8_t ssdp_running = false;
|
||||
static int ssdp_socket = -1;
|
||||
|
||||
static void ssdp_service_task(void *pvParameters);
|
||||
static int ssdp_handle_data(int sock, in_addr_t remote_addr, uint16_t remote_port,
|
||||
char *buf, int len);
|
||||
static int ssdp_get_friendly_name();
|
||||
|
||||
static int create_ssdp_socket(int *sock)
|
||||
{
|
||||
int err;
|
||||
|
||||
*sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (*sock < 0) {
|
||||
ESP_LOGE(TAG, "Failed to create socket. Error %d", errno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int opt = 1;
|
||||
if (setsockopt(*sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) != 0) {
|
||||
ESP_LOGE(TAG, "setsockopt SO_REUSEADDR failed %d\n", errno);
|
||||
goto err;
|
||||
}
|
||||
|
||||
struct sockaddr_in addr = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = htons(SSDP_PORT),
|
||||
.sin_addr.s_addr = htonl(INADDR_ANY)
|
||||
};
|
||||
err = bind(*sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
|
||||
if (err < 0) {
|
||||
ESP_LOGE(TAG, "Failed to bind socket. Error %d", errno);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Multicast TTL (independent to normal interface TTL)
|
||||
* should be set to small value like 2~4 */
|
||||
opt = SSDP_MULTICAST_TTL;
|
||||
if (setsockopt(*sock, IPPROTO_IP, IP_MULTICAST_TTL, &opt, sizeof(opt)) < 0) {
|
||||
ESP_LOGE(TAG, "Failed to set IP_MULTICAST_TTL. Error %d", errno);
|
||||
err = errno;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Receive multicast packet sent from this device,
|
||||
* useful when other service need to be aware of multicast notification */
|
||||
opt = 0;
|
||||
if (setsockopt(*sock, IPPROTO_IP, IP_MULTICAST_LOOP, &opt, sizeof(opt)) < 0) {
|
||||
ESP_LOGE(TAG, "Failed to unset IP_MULTICAST_LOOP. Error %d", errno);
|
||||
err = errno;
|
||||
goto err;
|
||||
}
|
||||
|
||||
struct ip_mreq mreq;
|
||||
mreq.imr_multiaddr.s_addr = inet_addr(SSDP_MULTICAST_ADDR);
|
||||
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
|
||||
if (setsockopt(*sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
|
||||
perror("Adding to multicast group failed");
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
close(*sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int ssdp_send_notify(int sock, char *buf, int buf_len)
|
||||
{
|
||||
int msg_len = snprintf(
|
||||
buf, buf_len,
|
||||
"NOTIFY * HTTP/1.1\r\n"
|
||||
"HOST: 239.255.255.250:1900\r\n"
|
||||
"NTS: ssdp:alive\r\n"
|
||||
"NT: upnp:rootdevice\r\n"
|
||||
"CACHE-CONTROL: max-age=3600\r\n"
|
||||
"SERVER: FreeRTOS/v10 UPnP/1.1 product/version\r\n"
|
||||
"USN: uuid:"SSDP_UUID_BASE"%02x%02x%02x::upnp:rootdevice\r\n"
|
||||
"LOCATION: http://%s:%u/SSDP.xml\r\n",
|
||||
ssdp_ctx.uuid_end[0], ssdp_ctx.uuid_end[1], ssdp_ctx.uuid_end[2],
|
||||
ip4addr_ntoa((const ip4_addr_t *)&ssdp_ctx.ip), 80
|
||||
);
|
||||
buf[msg_len] = '\0';
|
||||
struct sockaddr_in dest_addr;
|
||||
dest_addr.sin_family = AF_INET;
|
||||
dest_addr.sin_port = htons(SSDP_PORT);
|
||||
dest_addr.sin_addr.s_addr = inet_addr(SSDP_MULTICAST_ADDR);
|
||||
|
||||
int err = sendto(sock, buf, msg_len, 0,
|
||||
(struct sockaddr *)&dest_addr, sizeof(dest_addr));
|
||||
if (err < 0) {
|
||||
ESP_LOGE(TAG, "IPV4 sendto failed. errno: %d", errno);
|
||||
return errno;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ssdp_send_response(int sock, in_addr_t remote_addr, uint16_t remote_port, char *buf, int buf_len)
|
||||
{
|
||||
int msg_len = snprintf(
|
||||
buf, buf_len,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"EXT:\r\n"
|
||||
"ST: ""upnp:rootdevice""\r\n"
|
||||
"CACHE-CONTROL: max-age=3600\r\n"
|
||||
"SERVER: FreeRTOS/v10 UPnP/1.1 product/version\r\n"
|
||||
"USN: uuid:"SSDP_UUID_BASE"%02x%02x%02x" /* MAC=UUID */
|
||||
"::""upnp:rootdevice""\r\n"
|
||||
"LOCATION: http://%s:%u/SSDP.xml\r\n" /* ip:port */
|
||||
"\r\n",
|
||||
ssdp_ctx.uuid_end[0], ssdp_ctx.uuid_end[1], ssdp_ctx.uuid_end[2], /* MAC=UUID */
|
||||
ip4addr_ntoa((const ip4_addr_t *)&ssdp_ctx.ip), 80 /* ip:port */
|
||||
);
|
||||
|
||||
buf[msg_len] = '\0';
|
||||
|
||||
struct sockaddr_in dest_addr;
|
||||
dest_addr.sin_family = AF_INET;
|
||||
dest_addr.sin_port = remote_port;
|
||||
dest_addr.sin_addr.s_addr = remote_addr;
|
||||
|
||||
int err = sendto(sock, buf, msg_len, 0,
|
||||
(struct sockaddr *)&dest_addr, sizeof(dest_addr));
|
||||
if (err < 0) {
|
||||
ESP_LOGE(TAG, "IPV4 sendto failed. errno: %d", errno);
|
||||
return errno;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ssdp_handle_data(int sock, in_addr_t remote_addr, uint16_t remote_port,
|
||||
char *buf, int len)
|
||||
{
|
||||
/* on M-SEARCH: send RESPONSE anyway (even if this device doesn't match search type)
|
||||
* on NOTIFY : ignore
|
||||
* on RESPONSE: HTTP/1.1 200 OK : ignore
|
||||
* */
|
||||
if (memcmp(buf, HEADER_SEARCH, strlen(HEADER_SEARCH)) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ssdp_send_response(sock, remote_addr, remote_port, buf, len);
|
||||
}
|
||||
|
||||
static void ssdp_service_handle_socket()
|
||||
{
|
||||
char *buf = memory_pool_get(portMAX_DELAY);
|
||||
|
||||
while (1) {
|
||||
struct sockaddr_storage remote_addr;
|
||||
socklen_t socklen = sizeof(remote_addr);
|
||||
int len = recvfrom(ssdp_socket, buf, memory_pool_get_buf_size(), 0,
|
||||
(struct sockaddr *)&remote_addr, &socklen);
|
||||
if (len < 0) {
|
||||
ESP_LOGE(TAG, "multicast recvfrom failed: errno %d", errno);
|
||||
break;
|
||||
} else if (len == 0) {
|
||||
continue;
|
||||
}
|
||||
buf[len] = '\0';
|
||||
|
||||
uint16_t remote_port = ((struct sockaddr_in *)&remote_addr)->sin_port;
|
||||
in_addr_t remote_ip = ((struct sockaddr_in *)&remote_addr)->sin_addr.s_addr;
|
||||
ssdp_handle_data(ssdp_socket, remote_ip, remote_port,
|
||||
buf, memory_pool_get_buf_size());
|
||||
}
|
||||
|
||||
memory_pool_put(buf);
|
||||
}
|
||||
|
||||
static void ssdp_service_task(void *pvParameters)
|
||||
{
|
||||
ESP_LOGD(TAG, "ssdp_service_task()");
|
||||
ssdp_running = true;
|
||||
|
||||
int err;
|
||||
while (ssdp_running) {
|
||||
err = create_ssdp_socket(&ssdp_socket);
|
||||
if (err) {
|
||||
ssdp_socket = -1;
|
||||
ESP_LOGE(TAG, "Failed to create multicast socket");
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
continue;
|
||||
}
|
||||
|
||||
ssdp_service_handle_socket();
|
||||
|
||||
ESP_LOGE(TAG, "Shutting down socket and restarting...");
|
||||
shutdown(ssdp_socket, 0);
|
||||
close(ssdp_socket);
|
||||
ssdp_socket = -1;
|
||||
}
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Global Functions
|
||||
*/
|
||||
esp_err_t ssdp_init()
|
||||
{
|
||||
ssdp_ctx.ssdp_service_task = NULL;
|
||||
ssdp_socket = -1;
|
||||
ssdp_running = false;
|
||||
ssdp_ctx.ip.addr = 0;
|
||||
ssdp_ctx.gw.addr = 0;
|
||||
|
||||
uint8_t mac[6];
|
||||
esp_base_mac_addr_get(mac);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
/* use MAC last 3 bytes, as the first 3 bytes do not change */
|
||||
ssdp_ctx.uuid_end[i] = mac[i + 3];
|
||||
}
|
||||
|
||||
/* get name from nvs, if failed: use default */
|
||||
if (ssdp_get_friendly_name())
|
||||
ssdp_set_friendly_name(SSDP_DEFAULT_FRIENDLY_NAME);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ssdp_start()
|
||||
{
|
||||
if (ssdp_socket != -1 || ssdp_ctx.ssdp_service_task) {
|
||||
ESP_LOGE(TAG, "SSDP already started");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
/* get a default ip from available netif */
|
||||
esp_err_t err;
|
||||
esp_netif_ip_info_t ip_info = {0};
|
||||
esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
|
||||
if (netif == NULL) {
|
||||
netif = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF");
|
||||
}
|
||||
if (netif == NULL) {
|
||||
netif = esp_netif_get_handle_from_ifkey("ETH_DEF");
|
||||
}
|
||||
|
||||
err = esp_netif_get_ip_info(netif, &ip_info);
|
||||
if (err != ESP_OK) {
|
||||
/* fallback to 0.0.0.0 default IP */
|
||||
ssdp_ctx.ip.addr = 0;
|
||||
ssdp_ctx.gw.addr = 0;
|
||||
} else {
|
||||
ssdp_ctx.ip.addr = ip_info.ip.addr;
|
||||
ssdp_ctx.gw.addr = ip_info.gw.addr;
|
||||
}
|
||||
|
||||
BaseType_t res = xTaskCreatePinnedToCore(
|
||||
ssdp_service_task, "ssdp task", 4096, NULL,
|
||||
tskIDLE_PRIORITY + 1, &ssdp_ctx.ssdp_service_task,
|
||||
0);
|
||||
if (res != pdPASS || ssdp_ctx.ssdp_service_task == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to create task");
|
||||
err = ESP_FAIL;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void ssdp_stop()
|
||||
{
|
||||
ESP_LOGD(TAG, "Stopping SSDP");
|
||||
if (ssdp_socket != -1) {
|
||||
shutdown(ssdp_socket, 0);
|
||||
close(ssdp_socket);
|
||||
ssdp_socket = -1;
|
||||
}
|
||||
ssdp_running = false;
|
||||
ssdp_ctx.ssdp_service_task = NULL;
|
||||
}
|
||||
|
||||
|
||||
#define WT_NVS_KEY_SSDP_FRIENDLY_NAME 0x80
|
||||
#define WT_NVS_NS_NETWORK "net"
|
||||
|
||||
int ssdp_get_friendly_name()
|
||||
{
|
||||
return wt_nvs_get_once(WT_NVS_NS_NETWORK, WT_NVS_KEY_SSDP_FRIENDLY_NAME,
|
||||
ssdp_ctx.friendly_name, sizeof(ssdp_ctx.friendly_name));
|
||||
}
|
||||
|
||||
int ssdp_set_friendly_name(const char *name)
|
||||
{
|
||||
snprintf(ssdp_ctx.friendly_name, sizeof(ssdp_ctx.friendly_name),
|
||||
"%s", name);
|
||||
ssdp_ctx.friendly_name[sizeof(ssdp_ctx.friendly_name) - 1] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ssdp_set_ip_gw(const uint32_t *ip, const uint32_t *gw)
|
||||
{
|
||||
ssdp_ctx.ip.addr = *ip;
|
||||
ssdp_ctx.gw.addr = *gw;
|
||||
|
||||
char *buf = memory_pool_get(portMAX_DELAY);
|
||||
|
||||
/* send a ssdp notification to the new connected network */
|
||||
ssdp_send_notify(ssdp_socket, buf, memory_pool_get_buf_size());
|
||||
memory_pool_put(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ssdp_get_schema_str(char *buf, uint32_t buf_len)
|
||||
{
|
||||
int write_size = snprintf(
|
||||
buf, buf_len - 1,
|
||||
"<?xml version=\"1.0\"?>"
|
||||
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
|
||||
"<specVersion>"
|
||||
"<major>1</major>"
|
||||
"<minor>0</minor>"
|
||||
"</specVersion>"
|
||||
"<URLBase>http://%s:%u</URLBase>" /* ip, port */
|
||||
"<device>"
|
||||
"<friendlyName>%s</friendlyName>" /* friendly_name */
|
||||
"<deviceType>"SSDP_DEVICE_TYPE"</deviceType>"
|
||||
"<presentationURL>/</presentationURL>"
|
||||
"<serialNumber>0000</serialNumber>"
|
||||
"<modelName>"SSDP_MODEL_NAME"</modelName>"
|
||||
"<modelNumber>0000</modelNumber>"
|
||||
"<modelURL>"SSDP_MODEL_URL"</modelURL>"
|
||||
"<manufacturer>"SSDP_MANUFACTURER"</manufacturer>"
|
||||
"<manufacturerURL>https://yunsi.studio</manufacturerURL>"
|
||||
"</device>"
|
||||
"</root>\r\n"
|
||||
"\r\n",
|
||||
ip4addr_ntoa((const ip4_addr_t *)&ssdp_ctx.ip), 80, /* ip, port */
|
||||
ssdp_ctx.friendly_name /* friendly_name */
|
||||
);
|
||||
|
||||
buf[write_size] = '\0';
|
||||
return write_size < buf_len - 1;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
#ifndef SSDP_HEADER_GUARD
|
||||
#define SSDP_HEADER_GUARD
|
||||
#include <esp_err.h>
|
||||
#include <stdint.h>
|
||||
|
||||
esp_err_t ssdp_init();
|
||||
esp_err_t ssdp_start();
|
||||
void ssdp_stop();
|
||||
|
||||
int ssdp_set_friendly_name(const char *name);
|
||||
int ssdp_set_ip_gw(const uint32_t *ip, const uint32_t *gw);
|
||||
int ssdp_get_schema_str(char *buf, uint32_t buf_len);
|
||||
|
||||
|
||||
#endif /* SSDP_HEADER_GUARD */
|
|
@ -8,7 +8,7 @@
|
|||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
|
||||
#define BUFFER_NR 8
|
||||
#define BUFFER_NR 7
|
||||
#define BUFFER_SZ 2048
|
||||
|
||||
static uint8_t buf[BUFFER_NR][BUFFER_SZ];
|
||||
|
|
|
@ -10,7 +10,7 @@ idf_component_register(
|
|||
SRCS ${SOURCES}
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES esp_http_server
|
||||
PRIV_REQUIRES request_runner api_router json memory_pool utils html
|
||||
PRIV_REQUIRES request_runner api_router json memory_pool utils html SSDP
|
||||
)
|
||||
|
||||
idf_component_set_property(${COMPONENT_NAME} WHOLE_ARCHIVE ON)
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
#include "web_uri_module.h"
|
||||
#include "ssdp.h"
|
||||
|
||||
#include <esp_http_server.h>
|
||||
#include <esp_log.h>
|
||||
#include "memory_pool.h"
|
||||
|
||||
#define TAG __FILE_NAME__
|
||||
|
||||
#define URI_ROOT 0x0000002F /* / */
|
||||
#define URI_WS_DOT 0x2E73772F /* /ws. */
|
||||
#define MAKE_U32(b0, b1, b2, b3) ((b0) | (b1) << 8 | (b2) << 16 | (b3) << 24)
|
||||
#define URI_ROOT MAKE_U32 ('/', '\0', '\0', '\0')/* / */
|
||||
#define URI_WS_DOT MAKE_U32('/', 'w', 's', '.') /* /ws. */
|
||||
#define URI_SSDP MAKE_U32 ('/', 'S', 'S', 'D') /* /SSD */
|
||||
|
||||
#define HTML_INDEX "index_html_gz"
|
||||
#define JS_WS_SHARED "ws_sharedworker_js_gz"
|
||||
|
@ -29,6 +33,11 @@ static esp_err_t html_base_get_handler(httpd_req_t *req)
|
|||
httpd_resp_set_type(req, "text/javascript");
|
||||
httpd_resp_set_hdr(req, "Content-encoding", "gzip");
|
||||
SEND_FILE(req, JS_WS_SHARED);
|
||||
} else if (*URI_HASH == URI_SSDP) {
|
||||
httpd_resp_set_type(req, "text/xml");
|
||||
char *buf = memory_pool_get(portMAX_DELAY);
|
||||
ssdp_get_schema_str(buf, memory_pool_get_buf_size());
|
||||
httpd_resp_send(req, buf, strlen(buf));
|
||||
} else {
|
||||
httpd_resp_set_type(req, "text/html");
|
||||
httpd_resp_set_hdr(req, "Content-encoding", "gzip");
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "web_server.h"
|
||||
#include "web_uri_module.h"
|
||||
#include "ssdp.h"
|
||||
|
||||
#include <esp_http_server.h>
|
||||
#include <esp_event.h>
|
||||
|
|
|
@ -11,7 +11,7 @@ file(GLOB SOURCES
|
|||
idf_component_register(
|
||||
SRCS ${SOURCES}
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES mdns esp_wifi esp_event api_router wt_storage driver
|
||||
PRIV_REQUIRES mdns esp_wifi esp_event api_router wt_storage driver SSDP
|
||||
)
|
||||
|
||||
idf_component_set_property(${COMPONENT_NAME} WHOLE_ARCHIVE ON)
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
#include "ssdp.h"
|
||||
#include "wifi_configuration.h"
|
||||
|
||||
#define TAG __FILE_NAME__
|
||||
|
||||
void event_on_connected(ip_event_got_ip_t *event);
|
||||
|
@ -22,11 +25,18 @@ void ip_event_handler(void *handler_arg __attribute__((unused)),
|
|||
printf("STA GOT IP : %s\n",
|
||||
ip4addr_ntoa((const ip4_addr_t *) &event->ip_info.ip));
|
||||
event_on_connected(event);
|
||||
ssdp_set_ip_gw(&event->ip_info.ip.addr, &event->ip_info.gw.addr);
|
||||
break;
|
||||
}
|
||||
case IP_EVENT_STA_LOST_IP:
|
||||
case IP_EVENT_STA_LOST_IP: {
|
||||
printf("sta lost ip\n");
|
||||
ip4_addr_t ip;
|
||||
ip4_addr_t gw;
|
||||
IP4_ADDR_EXPAND(&ip, WIFI_DEFAULT_AP_IP);
|
||||
IP4_ADDR_EXPAND(&gw, WIFI_DEFAULT_AP_GATEWAY);
|
||||
ssdp_set_ip_gw(&ip.addr, &gw.addr);
|
||||
break;
|
||||
}
|
||||
case IP_EVENT_AP_STAIPASSIGNED:
|
||||
printf("event STAIPASSIGNED\n");
|
||||
break;
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
#include <hal/gpio_hal.h>
|
||||
#include <soc/ledc_periph.h>
|
||||
|
||||
#include "ssdp.h"
|
||||
|
||||
#define TAG __FILENAME__
|
||||
|
||||
typedef struct wifi_ctx_t {
|
||||
|
@ -131,6 +133,9 @@ void wifi_manager_init(void)
|
|||
if (do_connect) {
|
||||
disconn_handler();
|
||||
}
|
||||
|
||||
ssdp_init();
|
||||
ssdp_start();
|
||||
}
|
||||
|
||||
void wifi_led_init()
|
||||
|
|
Loading…
Reference in New Issue