Added WiFi kernel drivers and refactored Wifi service (#557)

+ other improvements
This commit is contained in:
Ken Van Hoeylandt
2026-07-09 21:03:38 +02:00
committed by GitHub
parent dbb96a891c
commit 1c2806bddf
104 changed files with 2347 additions and 1428 deletions
@@ -26,6 +26,9 @@ CONFIG_LV_USE_WIN=n
CONFIG_LV_USE_SNAPSHOT=y CONFIG_LV_USE_SNAPSHOT=y
CONFIG_LV_BUILD_EXAMPLES=n CONFIG_LV_BUILD_EXAMPLES=n
CONFIG_LV_BUILD_DEMOS=n CONFIG_LV_BUILD_DEMOS=n
# Slot 0 is reserved by ESP-IDF's pthread API (see esp_wifi/lwip pthread_getspecific usage).
# Tactility's Thread wrappers use slot 1 for their own self-pointer.
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2
CONFIG_FREERTOS_HZ=1000 CONFIG_FREERTOS_HZ=1000
CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2 CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2
CONFIG_FREERTOS_SMP=n CONFIG_FREERTOS_SMP=n
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_usbhost.h> #include <tactility/bindings/esp32_usbhost.h>
@@ -11,6 +12,11 @@
compatible = "root"; compatible = "root";
model = "BigTreeTech Panda Touch"; model = "BigTreeTech Panda Touch";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
+6
View File
@@ -1,6 +1,7 @@
/dts-v1/; /dts-v1/;
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -11,6 +12,11 @@
compatible = "root"; compatible = "root";
model = "CYD 2432S024C"; model = "CYD 2432S024C";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 { gpio0 {
compatible = "espressif,esp32-gpio"; compatible = "espressif,esp32-gpio";
gpio-count = <40>; gpio-count = <40>;
+6
View File
@@ -1,6 +1,7 @@
/dts-v1/; /dts-v1/;
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_sdspi.h> #include <tactility/bindings/esp32_sdspi.h>
@@ -12,6 +13,11 @@
compatible = "root"; compatible = "root";
model = "CYD 2432S024R"; model = "CYD 2432S024R";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 { gpio0 {
compatible = "espressif,esp32-gpio"; compatible = "espressif,esp32-gpio";
gpio-count = <40>; gpio-count = <40>;
+6
View File
@@ -1,6 +1,7 @@
/dts-v1/; /dts-v1/;
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -13,6 +14,11 @@
compatible = "root"; compatible = "root";
model = "CYD 2432S028R"; model = "CYD 2432S028R";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 { gpio0 {
compatible = "espressif,esp32-gpio"; compatible = "espressif,esp32-gpio";
gpio-count = <40>; gpio-count = <40>;
@@ -1,6 +1,7 @@
/dts-v1/; /dts-v1/;
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_uart.h> #include <tactility/bindings/esp32_uart.h>
@@ -13,6 +14,11 @@
compatible = "root"; compatible = "root";
model = "CYD 2432S028R v3"; model = "CYD 2432S028R v3";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 { gpio0 {
compatible = "espressif,esp32-gpio"; compatible = "espressif,esp32-gpio";
gpio-count = <40>; gpio-count = <40>;
@@ -2,7 +2,7 @@
#include <driver/gpio.h> #include <driver/gpio.h>
#include <Tactility/hal/Configuration.h> #include <Tactility/hal/Configuration.h>
#include <Tactility/kernel/SystemEvents.h> #include <Tactility/SystemEvents.h>
#include <Tactility/lvgl/LvglSync.h> #include <Tactility/lvgl/LvglSync.h>
#include <PwmBacklight.h> #include <PwmBacklight.h>
+6
View File
@@ -1,6 +1,7 @@
/dts-v1/; /dts-v1/;
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -11,6 +12,11 @@
compatible = "root"; compatible = "root";
model = "CYD 2432S032C"; model = "CYD 2432S032C";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 { gpio0 {
compatible = "espressif,esp32-gpio"; compatible = "espressif,esp32-gpio";
gpio-count = <40>; gpio-count = <40>;
+6
View File
@@ -1,6 +1,7 @@
/dts-v1/; /dts-v1/;
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -12,6 +13,11 @@
compatible = "root"; compatible = "root";
model = "CYD 3248S035C"; model = "CYD 3248S035C";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 { gpio0 {
compatible = "espressif,esp32-gpio"; compatible = "espressif,esp32-gpio";
gpio-count = <40>; gpio-count = <40>;
+6
View File
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -11,6 +12,11 @@
compatible = "root"; compatible = "root";
model = "CYD 4848S040C"; model = "CYD 4848S040C";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
+6
View File
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_uart.h> #include <tactility/bindings/esp32_uart.h>
@@ -12,6 +13,11 @@
compatible = "root"; compatible = "root";
model = "CYD 8048S043C"; model = "CYD 8048S043C";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
+6
View File
@@ -1,6 +1,7 @@
/dts-v1/; /dts-v1/;
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_sdspi.h> #include <tactility/bindings/esp32_sdspi.h>
@@ -11,6 +12,11 @@
compatible = "root"; compatible = "root";
model = "CYD E32R28T"; model = "CYD E32R28T";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 { gpio0 {
compatible = "espressif,esp32-gpio"; compatible = "espressif,esp32-gpio";
gpio-count = <40>; gpio-count = <40>;
+6
View File
@@ -1,6 +1,7 @@
/dts-v1/; /dts-v1/;
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -12,6 +13,11 @@
compatible = "root"; compatible = "root";
model = "CYD E32R32P"; model = "CYD E32R32P";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 { gpio0 {
compatible = "espressif,esp32-gpio"; compatible = "espressif,esp32-gpio";
gpio-count = <40>; gpio-count = <40>;
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -13,6 +14,11 @@
compatible = "root"; compatible = "root";
model = "Elecrow CrowPanel Advance 2.8"; model = "Elecrow CrowPanel Advance 2.8";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -13,6 +14,11 @@
compatible = "root"; compatible = "root";
model = "Elecrow CrowPanel Advance 3.5"; model = "Elecrow CrowPanel Advance 3.5";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -12,6 +13,11 @@
compatible = "root"; compatible = "root";
model = "Elecrow CrowPanel Advance 5.0"; model = "Elecrow CrowPanel Advance 5.0";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
@@ -1,6 +1,7 @@
/dts-v1/; /dts-v1/;
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -13,6 +14,11 @@
compatible = "root"; compatible = "root";
model = "Elecrow CrowPanel Basic 2.8"; model = "Elecrow CrowPanel Basic 2.8";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 { gpio0 {
compatible = "espressif,esp32-gpio"; compatible = "espressif,esp32-gpio";
gpio-count = <40>; gpio-count = <40>;
@@ -1,6 +1,7 @@
/dts-v1/; /dts-v1/;
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -13,6 +14,11 @@
compatible = "root"; compatible = "root";
model = "Elecrow CrowPanel Basic 3.5"; model = "Elecrow CrowPanel Basic 3.5";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 { gpio0 {
compatible = "espressif,esp32-gpio"; compatible = "espressif,esp32-gpio";
gpio-count = <40>; gpio-count = <40>;
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -12,6 +13,11 @@
compatible = "root"; compatible = "root";
model = "Elecrow CrowPanel Basic 5.0"; model = "Elecrow CrowPanel Basic 5.0";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
+6
View File
@@ -1,6 +1,7 @@
/dts-v1/; /dts-v1/;
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
@@ -8,6 +9,11 @@
compatible = "root"; compatible = "root";
model = "Generic ESP32"; model = "Generic ESP32";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 { gpio0 {
compatible = "espressif,esp32-gpio"; compatible = "espressif,esp32-gpio";
gpio-count = <49>; gpio-count = <49>;
@@ -1,6 +1,7 @@
/dts-v1/; /dts-v1/;
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
@@ -8,6 +9,11 @@
compatible = "root"; compatible = "root";
model = "Generic ESP32 C6"; model = "Generic ESP32 C6";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 { gpio0 {
compatible = "espressif,esp32-gpio"; compatible = "espressif,esp32-gpio";
gpio-count = <31>; gpio-count = <31>;
@@ -1,6 +1,7 @@
/dts-v1/; /dts-v1/;
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
@@ -8,6 +9,11 @@
compatible = "root"; compatible = "root";
model = "Generic ESP32 S3"; model = "Generic ESP32 S3";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 { gpio0 {
compatible = "espressif,esp32-gpio"; compatible = "espressif,esp32-gpio";
gpio-count = <49>; gpio-count = <49>;
@@ -1,6 +1,7 @@
/dts-v1/; /dts-v1/;
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -12,6 +13,11 @@
compatible = "root"; compatible = "root";
model = "Guition JC2432W328C"; model = "Guition JC2432W328C";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 { gpio0 {
compatible = "espressif,esp32-gpio"; compatible = "espressif,esp32-gpio";
gpio-count = <40>; gpio-count = <40>;
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_i2s.h> #include <tactility/bindings/esp32_i2s.h>
@@ -14,6 +15,11 @@
compatible = "root"; compatible = "root";
model = "Guition JC3248W535C"; model = "Guition JC3248W535C";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_i2s.h> #include <tactility/bindings/esp32_i2s.h>
@@ -13,6 +14,11 @@
compatible = "root"; compatible = "root";
model = "Guition JC8048W550C"; model = "Guition JC8048W550C";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
@@ -9,6 +10,11 @@
compatible = "root"; compatible = "root";
model = "Heltec WiFi LoRa 32 V3"; model = "Heltec WiFi LoRa 32 V3";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c_master.h> #include <tactility/bindings/esp32_i2c_master.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -14,6 +15,11 @@
compatible = "root"; compatible = "root";
model = "LilyGO T-Deck Max"; model = "LilyGO T-Deck Max";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
}; };
+2 -2
View File
@@ -2,10 +2,10 @@
#include "devices/KeyboardBacklight.h" #include "devices/KeyboardBacklight.h"
#include "devices/TrackballDevice.h" #include "devices/TrackballDevice.h"
#include <Tactility/SystemEvents.h>
#include <Tactility/LogMessages.h>
#include <Tactility/hal/gps/GpsConfiguration.h> #include <Tactility/hal/gps/GpsConfiguration.h>
#include <Tactility/kernel/Kernel.h> #include <Tactility/kernel/Kernel.h>
#include <Tactility/kernel/SystemEvents.h>
#include <Tactility/LogMessages.h>
#include <Tactility/service/gps/GpsService.h> #include <Tactility/service/gps/GpsService.h>
#include <tactility/log.h> #include <tactility/log.h>
+6
View File
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_grove.h> #include <tactility/bindings/esp32_grove.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
@@ -16,6 +17,11 @@
compatible = "root"; compatible = "root";
model = "LilyGO T-Deck"; model = "LilyGO T-Deck";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
+1 -1
View File
@@ -2,7 +2,7 @@
#include "devices/Display.h" #include "devices/Display.h"
#include "PwmBacklight.h" #include "PwmBacklight.h"
#include <Tactility/kernel/SystemEvents.h> #include <Tactility/SystemEvents.h>
#include <Tactility/TactilityCore.h> #include <Tactility/TactilityCore.h>
#include <tactility/log.h> #include <tactility/log.h>
@@ -2,12 +2,18 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
/ { / {
compatible = "root"; compatible = "root";
model = "LilyGO T-Display S3"; model = "LilyGO T-Display S3";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
@@ -1,6 +1,7 @@
/dts-v1/; /dts-v1/;
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -10,6 +11,11 @@
compatible = "root"; compatible = "root";
model = "LilyGO T-Display"; model = "LilyGO T-Display";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 { gpio0 {
compatible = "espressif,esp32-gpio"; compatible = "espressif,esp32-gpio";
gpio-count = <49>; gpio-count = <49>;
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_sdmmc.h> #include <tactility/bindings/esp32_sdmmc.h>
@@ -13,6 +14,11 @@
compatible = "root"; compatible = "root";
model = "LilyGO T-Dongle S3"; model = "LilyGO T-Dongle S3";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
+1 -1
View File
@@ -2,7 +2,7 @@
#include "devices/Display.h" #include "devices/Display.h"
#include "PwmBacklight.h" #include "PwmBacklight.h"
#include <Tactility/kernel/SystemEvents.h> #include <Tactility/SystemEvents.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <Tactility/TactilityCore.h> #include <Tactility/TactilityCore.h>
+6
View File
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_sdmmc.h> #include <tactility/bindings/esp32_sdmmc.h>
@@ -13,6 +14,11 @@
compatible = "root"; compatible = "root";
model = "LilyGO T-HMI"; model = "LilyGO T-HMI";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
+1 -1
View File
@@ -1,6 +1,6 @@
#include <Bq27220.h> #include <Bq27220.h>
#include <Tactility/LogMessages.h> #include <Tactility/LogMessages.h>
#include <Tactility/kernel/SystemEvents.h> #include <Tactility/SystemEvents.h>
#include <Tactility/service/gps/GpsService.h> #include <Tactility/service/gps/GpsService.h>
#include <Tactility/hal/gps/GpsConfiguration.h> #include <Tactility/hal/gps/GpsConfiguration.h>
@@ -2,6 +2,7 @@
#include <bindings/tlora_pager.h> #include <bindings/tlora_pager.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_i2s.h> #include <tactility/bindings/esp32_i2s.h>
@@ -15,6 +16,11 @@
compatible = "lilygo,tlora-pager"; compatible = "lilygo,tlora-pager";
model = "LilyGO T-Lora Pager"; model = "LilyGO T-Lora Pager";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_grove.h> #include <tactility/bindings/esp32_grove.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
@@ -17,6 +18,11 @@
compatible = "root"; compatible = "root";
model = "M5Stack Cardputer Adv"; model = "M5Stack Cardputer Adv";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_grove.h> #include <tactility/bindings/esp32_grove.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
@@ -16,6 +17,11 @@
compatible = "root"; compatible = "root";
model = "M5Stack Cardputer"; model = "M5Stack Cardputer";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
+6
View File
@@ -1,6 +1,7 @@
/dts-v1/; /dts-v1/;
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_grove.h> #include <tactility/bindings/esp32_grove.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
@@ -17,6 +18,11 @@
compatible = "root"; compatible = "root";
model = "M5Stack Core2"; model = "M5Stack Core2";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 { gpio0 {
compatible = "espressif,esp32-gpio"; compatible = "espressif,esp32-gpio";
gpio-count = <40>; gpio-count = <40>;
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_grove.h> #include <tactility/bindings/esp32_grove.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
@@ -18,6 +19,11 @@
compatible = "root"; compatible = "root";
model = "M5Stack CoreS3"; model = "M5Stack CoreS3";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -13,6 +14,11 @@
compatible = "root"; compatible = "root";
model = "M5Stack PaperS3"; model = "M5Stack PaperS3";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_grove.h> #include <tactility/bindings/esp32_grove.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
@@ -20,6 +21,11 @@
compatible = "root"; compatible = "root";
model = "M5Stack StackChan"; model = "M5Stack StackChan";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
@@ -1,5 +1,6 @@
/dts-v1/; /dts-v1/;
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -12,6 +13,11 @@
compatible = "root"; compatible = "root";
model = "M5Stack StickC Plus2"; model = "M5Stack StickC Plus2";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
gpio0 { gpio0 {
compatible = "espressif,esp32-gpio"; compatible = "espressif,esp32-gpio";
gpio-count = <40>; gpio-count = <40>;
@@ -1,6 +1,7 @@
/dts-v1/; /dts-v1/;
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_i2s.h> #include <tactility/bindings/esp32_i2s.h>
@@ -15,6 +16,11 @@
compatible = "root"; compatible = "root";
model = "M5Stack StickS3"; model = "M5Stack StickS3";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
+6
View File
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -13,6 +14,11 @@
compatible = "root"; compatible = "root";
model = "unPhone"; model = "unPhone";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_sdmmc.h> #include <tactility/bindings/esp32_sdmmc.h>
@@ -13,6 +14,11 @@
compatible = "root"; compatible = "root";
model = "Waveshare ESP32-S3-Geek"; model = "Waveshare ESP32-S3-Geek";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -14,6 +15,11 @@
compatible = "root"; compatible = "root";
model = "Waveshare S3 LCD 1.3"; model = "Waveshare S3 LCD 1.3";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -14,6 +15,11 @@
compatible = "root"; compatible = "root";
model = "Waveshare S3 Touch LCD 1.28"; model = "Waveshare S3 Touch LCD 1.28";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -13,6 +14,11 @@
compatible = "root"; compatible = "root";
model = "Waveshare S3 Touch LCD 1.47"; model = "Waveshare S3 Touch LCD 1.47";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -12,6 +13,11 @@
compatible = "root"; compatible = "root";
model = "Waveshare ESP32-S3-Touch-LCD-4.3"; model = "Waveshare ESP32-S3-Touch-LCD-4.3";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
@@ -2,6 +2,7 @@
#include <tactility/bindings/root.h> #include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_ble.h> #include <tactility/bindings/esp32_ble.h>
#include <tactility/bindings/esp32_wifi_pinned.h>
#include <tactility/bindings/esp32_gpio.h> #include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h> #include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_spi.h> #include <tactility/bindings/esp32_spi.h>
@@ -11,6 +12,11 @@
compatible = "root"; compatible = "root";
model = "Wireless-Tag WT32-SC01 Plus"; model = "Wireless-Tag WT32-SC01 Plus";
wifi0 {
compatible = "espressif,esp32-wifi-pinned";
status = "disabled";
};
ble0 { ble0 {
compatible = "espressif,esp32-ble"; compatible = "espressif,esp32-ble";
status = "disabled"; status = "disabled";
+1 -1
View File
@@ -6,7 +6,7 @@ idf_component_register(
SRCS ${SOURCES} SRCS ${SOURCES}
INCLUDE_DIRS "include/" INCLUDE_DIRS "include/"
PRIV_INCLUDE_DIRS "private/" PRIV_INCLUDE_DIRS "private/"
REQUIRES TactilityKernel driver esp_driver_i2c vfs fatfs REQUIRES TactilityKernel driver esp_driver_i2c vfs fatfs esp_wifi esp_netif esp_event
) )
idf_component_optional_requires(PRIVATE bt usb espressif__usb_host_hid espressif__usb_host_msc) idf_component_optional_requires(PRIVATE bt usb espressif__usb_host_hid espressif__usb_host_msc)
@@ -0,0 +1,8 @@
description: ESP32 WiFi driver wrapper that marshals state-changing calls onto the WiFi task's CPU core
compatible: "espressif,esp32-wifi-pinned"
properties:
_unused:
type: int
default: 0
@@ -0,0 +1,8 @@
description: ESP32 WiFi driver
compatible: "espressif,esp32-wifi"
properties:
_unused:
type: int
default: 0
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <tactility/drivers/esp32_wifi.h>
#ifdef __cplusplus
extern "C" {
#endif
DEFINE_DEVICETREE(esp32_wifi, struct Esp32WifiConfig)
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <tactility/drivers/esp32_wifi_pinned.h>
#ifdef __cplusplus
extern "C" {
#endif
DEFINE_DEVICETREE(esp32_wifi_pinned, struct Esp32WifiPinnedConfig)
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/** No device-tree configuration required for the ESP32 WiFi driver. */
struct Esp32WifiConfig {
int _unused; /**< Placeholder — driver reads all config from Kconfig. */
};
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/** No device-tree configuration required for the ESP32 pinned WiFi driver. */
struct Esp32WifiPinnedConfig {
int _unused; /**< Placeholder — driver has no configurable properties. */
};
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,567 @@
#ifdef ESP_PLATFORM
#include <sdkconfig.h>
#endif
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#include <esp_event.h>
#include <esp_netif.h>
#include <esp_wifi.h>
#include <esp_wifi_default.h>
#include <tactility/concurrent/mutex.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/esp32_wifi.h>
#include <tactility/drivers/wifi.h>
#include <tactility/error_esp32.h>
#include <tactility/log.h>
#include <algorithm>
#include <cstring>
#include <new>
#define TAG "esp32_wifi"
namespace {
constexpr size_t WIFI_MAX_CALLBACKS = 4;
constexpr uint16_t WIFI_SCAN_RECORD_LIMIT = 32;
struct WifiCallbackEntry {
WifiEventCallback fn = nullptr;
void* ctx = nullptr;
};
struct Esp32WifiCtx {
Device* device = nullptr;
Mutex mutex{};
WifiRadioState radioState = WIFI_RADIO_STATE_OFF;
WifiStationState stationState = WIFI_STATION_STATE_DISCONNECTED;
bool scanning = false;
char targetSsid[33] = {};
esp_netif_ip_info_t ipInfo{};
wifi_ap_record_t scanResults[WIFI_SCAN_RECORD_LIMIT] = {};
uint16_t scanResultCount = 0;
esp_netif_t* netif = nullptr;
esp_event_handler_instance_t wifiEventHandler = nullptr;
esp_event_handler_instance_t ipEventHandler = nullptr;
Mutex callbackMutex{};
WifiCallbackEntry callbacks[WIFI_MAX_CALLBACKS] = {};
size_t callbackCount = 0;
};
#define GET_CTX(device) (static_cast<Esp32WifiCtx*>(device_get_driver_data(device)))
WifiAuthenticationType to_wifi_authentication_type(wifi_auth_mode_t mode) {
switch (mode) {
case WIFI_AUTH_OPEN: return WIFI_AUTHENTICATION_TYPE_OPEN;
case WIFI_AUTH_WEP: return WIFI_AUTHENTICATION_TYPE_WEP;
case WIFI_AUTH_WPA_PSK: return WIFI_AUTHENTICATION_TYPE_WPA_PSK;
case WIFI_AUTH_WPA2_PSK: return WIFI_AUTHENTICATION_TYPE_WPA2_PSK;
case WIFI_AUTH_WPA_WPA2_PSK: return WIFI_AUTHENTICATION_TYPE_WPA_WPA2_PSK;
case WIFI_AUTH_WPA2_ENTERPRISE: return WIFI_AUTHENTICATION_TYPE_WPA2_ENTERPRISE;
case WIFI_AUTH_WPA3_PSK: return WIFI_AUTHENTICATION_TYPE_WPA3_PSK;
case WIFI_AUTH_WPA2_WPA3_PSK: return WIFI_AUTHENTICATION_TYPE_WPA2_WPA3_PSK;
case WIFI_AUTH_WAPI_PSK: return WIFI_AUTHENTICATION_TYPE_WAPI_PSK;
case WIFI_AUTH_OWE: return WIFI_AUTHENTICATION_TYPE_OWE;
case WIFI_AUTH_WPA3_ENT_192: return WIFI_AUTHENTICATION_TYPE_WPA3_ENT_192;
case WIFI_AUTH_WPA3_EXT_PSK: return WIFI_AUTHENTICATION_TYPE_WPA3_EXT_PSK;
case WIFI_AUTH_WPA3_EXT_PSK_MIXED_MODE: return WIFI_AUTHENTICATION_TYPE_WPA3_EXT_PSK_MIXED_MODE;
default: return WIFI_AUTHENTICATION_TYPE_OPEN;
}
}
void fire_event(Esp32WifiCtx* ctx, WifiEvent event) {
WifiCallbackEntry local[WIFI_MAX_CALLBACKS];
size_t count;
mutex_lock(&ctx->callbackMutex);
count = ctx->callbackCount;
memcpy(local, ctx->callbacks, count * sizeof(WifiCallbackEntry));
mutex_unlock(&ctx->callbackMutex);
for (size_t i = 0; i < count; i++) {
local[i].fn(ctx->device, local[i].ctx, event);
}
}
// ---- ESP-IDF event handling (runs on the esp_event task) ----
void on_wifi_or_ip_event(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
auto* ctx = static_cast<Esp32WifiCtx*>(arg);
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
mutex_lock(&ctx->mutex);
bool was_pending = ctx->stationState == WIFI_STATION_STATE_CONNECTION_PENDING;
ctx->stationState = WIFI_STATION_STATE_DISCONNECTED;
memset(&ctx->ipInfo, 0, sizeof(ctx->ipInfo));
mutex_unlock(&ctx->mutex);
WifiEvent state_event = {};
state_event.type = WIFI_EVENT_TYPE_STATION_STATE_CHANGED;
state_event.station_state = WIFI_STATION_STATE_DISCONNECTED;
fire_event(ctx, state_event);
if (was_pending) {
WifiEvent result_event = {};
result_event.type = WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT;
result_event.connection_error = WIFI_STATION_CONNECTION_ERROR_TARGET_NOT_FOUND;
fire_event(ctx, result_event);
}
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
auto* got_ip = static_cast<ip_event_got_ip_t*>(event_data);
mutex_lock(&ctx->mutex);
ctx->ipInfo = got_ip->ip_info;
ctx->stationState = WIFI_STATION_STATE_CONNECTED;
mutex_unlock(&ctx->mutex);
WifiEvent state_event = {};
state_event.type = WIFI_EVENT_TYPE_STATION_STATE_CHANGED;
state_event.station_state = WIFI_STATION_STATE_CONNECTED;
fire_event(ctx, state_event);
WifiEvent result_event = {};
result_event.type = WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT;
result_event.connection_error = WIFI_STATION_CONNECTION_ERROR_NONE;
fire_event(ctx, result_event);
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) {
mutex_lock(&ctx->mutex);
ctx->scanning = false;
uint16_t count = WIFI_SCAN_RECORD_LIMIT;
esp_err_t err = esp_wifi_scan_get_ap_records(&count, ctx->scanResults);
ctx->scanResultCount = (err == ESP_OK) ? count : 0;
mutex_unlock(&ctx->mutex);
WifiEvent event = {};
event.type = WIFI_EVENT_TYPE_SCAN_FINISHED;
fire_event(ctx, event);
}
}
// ---- Work, always run inline on the caller (see note at the top) ----
error_t bring_up_wifi(Esp32WifiCtx* ctx) {
ctx->netif = esp_netif_create_default_wifi_sta();
if (ctx->netif == nullptr) {
LOG_E(TAG, "Failed to create default STA netif");
return ERROR_RESOURCE;
}
// Warning: this is the memory-intensive operation. It uses over 100kB of
// RAM with default settings.
wifi_init_config_t init_config = WIFI_INIT_CONFIG_DEFAULT();
esp_err_t err = esp_wifi_init(&init_config);
if (err != ESP_OK) {
LOG_E(TAG, "esp_wifi_init failed: %s", esp_err_to_name(err));
esp_netif_destroy(ctx->netif);
ctx->netif = nullptr;
return esp_err_to_error(err);
}
esp_wifi_set_storage(WIFI_STORAGE_RAM);
err = esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &on_wifi_or_ip_event, ctx, &ctx->wifiEventHandler);
if (err != ESP_OK) {
LOG_E(TAG, "Failed to register WIFI_EVENT handler: %s", esp_err_to_name(err));
esp_wifi_deinit();
esp_netif_destroy(ctx->netif);
ctx->netif = nullptr;
return esp_err_to_error(err);
}
err = esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &on_wifi_or_ip_event, ctx, &ctx->ipEventHandler);
if (err != ESP_OK) {
LOG_E(TAG, "Failed to register IP_EVENT handler: %s", esp_err_to_name(err));
esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, ctx->wifiEventHandler);
ctx->wifiEventHandler = nullptr;
esp_wifi_deinit();
esp_netif_destroy(ctx->netif);
ctx->netif = nullptr;
return esp_err_to_error(err);
}
err = esp_wifi_set_mode(WIFI_MODE_STA);
if (err != ESP_OK) {
LOG_E(TAG, "esp_wifi_set_mode failed: %s", esp_err_to_name(err));
esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, ctx->ipEventHandler);
esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, ctx->wifiEventHandler);
ctx->ipEventHandler = nullptr;
ctx->wifiEventHandler = nullptr;
esp_wifi_deinit();
esp_netif_destroy(ctx->netif);
ctx->netif = nullptr;
return esp_err_to_error(err);
}
err = esp_wifi_start();
if (err != ESP_OK) {
LOG_E(TAG, "esp_wifi_start failed: %s", esp_err_to_name(err));
esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, ctx->ipEventHandler);
esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, ctx->wifiEventHandler);
ctx->ipEventHandler = nullptr;
ctx->wifiEventHandler = nullptr;
esp_wifi_set_mode(WIFI_MODE_NULL);
esp_wifi_deinit();
esp_netif_destroy(ctx->netif);
ctx->netif = nullptr;
return esp_err_to_error(err);
}
mutex_lock(&ctx->mutex);
ctx->radioState = WIFI_RADIO_STATE_ON;
mutex_unlock(&ctx->mutex);
LOG_I(TAG, "WiFi radio on");
return ERROR_NONE;
}
void bring_down_wifi(Esp32WifiCtx* ctx) {
mutex_lock(&ctx->mutex);
bool was_connected = ctx->stationState != WIFI_STATION_STATE_DISCONNECTED;
bool was_scanning = ctx->scanning;
ctx->stationState = WIFI_STATION_STATE_DISCONNECTED;
ctx->scanning = false;
mutex_unlock(&ctx->mutex);
if (was_scanning) {
esp_wifi_scan_stop();
}
if (was_connected) {
esp_wifi_disconnect();
}
// Detach netif from the internal WiFi event handlers before stopping,
// otherwise esp_netif_destroy() can race with esp_wifi_stop()'s own
// netif teardown (see esp32_ble/WifiEsp.cpp for the same issue).
if (ctx->netif != nullptr) {
esp_wifi_clear_default_wifi_driver_and_handlers(ctx->netif);
}
esp_wifi_stop();
esp_wifi_set_mode(WIFI_MODE_NULL);
if (ctx->wifiEventHandler != nullptr) {
esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, ctx->wifiEventHandler);
ctx->wifiEventHandler = nullptr;
}
if (ctx->ipEventHandler != nullptr) {
esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, ctx->ipEventHandler);
ctx->ipEventHandler = nullptr;
}
esp_wifi_deinit();
if (ctx->netif != nullptr) {
esp_netif_destroy(ctx->netif);
ctx->netif = nullptr;
}
mutex_lock(&ctx->mutex);
ctx->radioState = WIFI_RADIO_STATE_OFF;
mutex_unlock(&ctx->mutex);
LOG_I(TAG, "WiFi radio off");
}
// ---- WifiApi ----
error_t api_get_radio_state(Device* device, WifiRadioState* state) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || state == nullptr) return ERROR_INVALID_ARGUMENT;
mutex_lock(&ctx->mutex);
*state = ctx->radioState;
mutex_unlock(&ctx->mutex);
return ERROR_NONE;
}
error_t api_get_station_state(Device* device, WifiStationState* state) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || state == nullptr) return ERROR_INVALID_ARGUMENT;
mutex_lock(&ctx->mutex);
*state = ctx->stationState;
mutex_unlock(&ctx->mutex);
return ERROR_NONE;
}
error_t api_get_access_point_state(Device* device, WifiAccessPointState* state) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || state == nullptr) return ERROR_INVALID_ARGUMENT;
// Access point mode isn't implemented by this driver.
*state = WIFI_ACCESS_POINT_STATE_STOPPED;
return ERROR_NONE;
}
bool api_is_scanning(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr) return false;
mutex_lock(&ctx->mutex);
bool scanning = ctx->scanning;
mutex_unlock(&ctx->mutex);
return scanning;
}
error_t api_scan(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr) return ERROR_INVALID_STATE;
mutex_lock(&ctx->mutex);
if (ctx->radioState != WIFI_RADIO_STATE_ON || ctx->scanning) {
mutex_unlock(&ctx->mutex);
return ERROR_INVALID_STATE;
}
mutex_unlock(&ctx->mutex);
esp_err_t err = esp_wifi_scan_start(nullptr, false);
if (err != ESP_OK) {
return esp_err_to_error(err);
}
mutex_lock(&ctx->mutex);
ctx->scanning = true;
mutex_unlock(&ctx->mutex);
WifiEvent event = {};
event.type = WIFI_EVENT_TYPE_SCAN_STARTED;
fire_event(ctx, event);
return ERROR_NONE;
}
error_t api_get_scan_results(Device* device, WifiApRecord* results, size_t* num_results) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || results == nullptr || num_results == nullptr) return ERROR_INVALID_ARGUMENT;
mutex_lock(&ctx->mutex);
size_t count = std::min<size_t>(*num_results, ctx->scanResultCount);
for (size_t i = 0; i < count; i++) {
const wifi_ap_record_t& src = ctx->scanResults[i];
WifiApRecord& dst = results[i];
memset(dst.ssid, 0, sizeof(dst.ssid));
memcpy(dst.ssid, src.ssid, std::min(sizeof(dst.ssid) - 1, sizeof(src.ssid)));
dst.rssi = src.rssi;
dst.channel = src.primary;
dst.authentication_type = to_wifi_authentication_type(src.authmode);
}
*num_results = count;
mutex_unlock(&ctx->mutex);
return ERROR_NONE;
}
error_t api_station_get_ipv4_address(Device* device, char* ipv4) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ipv4 == nullptr) return ERROR_INVALID_ARGUMENT;
mutex_lock(&ctx->mutex);
esp_ip4addr_ntoa(&ctx->ipInfo.ip, ipv4, 16);
mutex_unlock(&ctx->mutex);
return ERROR_NONE;
}
error_t api_station_get_target_ssid(Device* device, char* ssid) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ssid == nullptr) return ERROR_INVALID_ARGUMENT;
mutex_lock(&ctx->mutex);
constexpr size_t ssid_buffer_size = sizeof(ctx->targetSsid);
strncpy(ssid, ctx->targetSsid, ssid_buffer_size);
mutex_unlock(&ctx->mutex);
return ERROR_NONE;
}
error_t api_station_connect(Device* device, const char* ssid, const char* password, int32_t channel) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ssid == nullptr) return ERROR_INVALID_ARGUMENT;
mutex_lock(&ctx->mutex);
bool radio_on = ctx->radioState == WIFI_RADIO_STATE_ON;
bool was_connected = ctx->stationState != WIFI_STATION_STATE_DISCONNECTED;
mutex_unlock(&ctx->mutex);
if (!radio_on) {
return ERROR_INVALID_STATE;
}
if (was_connected) {
esp_wifi_disconnect();
}
wifi_config_t config {};
config.sta.channel = static_cast<uint8_t>(channel);
config.sta.scan_method = WIFI_FAST_SCAN;
config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL;
config.sta.threshold.rssi = -127;
config.sta.pmf_cfg.capable = true;
strncpy(reinterpret_cast<char*>(config.sta.ssid), ssid, sizeof(config.sta.ssid) - 1);
if (password != nullptr && password[0] != '\0') {
strncpy(reinterpret_cast<char*>(config.sta.password), password, sizeof(config.sta.password) - 1);
config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
}
esp_err_t err = esp_wifi_set_config(WIFI_IF_STA, &config);
if (err != ESP_OK) {
return esp_err_to_error(err);
}
mutex_lock(&ctx->mutex);
strncpy(ctx->targetSsid, ssid, sizeof(ctx->targetSsid) - 1);
ctx->targetSsid[sizeof(ctx->targetSsid) - 1] = '\0';
ctx->stationState = WIFI_STATION_STATE_CONNECTION_PENDING;
mutex_unlock(&ctx->mutex);
err = esp_wifi_connect();
if (err != ESP_OK) {
mutex_lock(&ctx->mutex);
ctx->stationState = WIFI_STATION_STATE_DISCONNECTED;
mutex_unlock(&ctx->mutex);
return esp_err_to_error(err);
}
WifiEvent event = {};
event.type = WIFI_EVENT_TYPE_STATION_STATE_CHANGED;
event.station_state = WIFI_STATION_STATE_CONNECTION_PENDING;
fire_event(ctx, event);
return ERROR_NONE;
}
error_t api_station_disconnect(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr) return ERROR_INVALID_STATE;
mutex_lock(&ctx->mutex);
bool was_connected = ctx->stationState != WIFI_STATION_STATE_DISCONNECTED;
mutex_unlock(&ctx->mutex);
if (!was_connected) {
return ERROR_NONE;
}
// The DISCONNECTED state change is published by the WIFI_EVENT handler
// once ESP-IDF confirms the disconnect, so we don't fire it here.
esp_err_t err = esp_wifi_disconnect();
return err == ESP_OK ? ERROR_NONE : esp_err_to_error(err);
}
error_t api_station_get_rssi(Device* device, int32_t* rssi) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || rssi == nullptr) return ERROR_INVALID_ARGUMENT;
int native_rssi = 0;
esp_err_t err = esp_wifi_sta_get_rssi(&native_rssi);
if (err != ESP_OK) {
return esp_err_to_error(err);
}
*rssi = native_rssi;
return ERROR_NONE;
}
error_t api_add_event_callback(Device* device, void* callback_context, WifiEventCallback callback) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || callback == nullptr) return ERROR_INVALID_ARGUMENT;
mutex_lock(&ctx->callbackMutex);
if (ctx->callbackCount >= WIFI_MAX_CALLBACKS) {
mutex_unlock(&ctx->callbackMutex);
return ERROR_OUT_OF_MEMORY;
}
ctx->callbacks[ctx->callbackCount] = { .fn = callback, .ctx = callback_context };
ctx->callbackCount++;
mutex_unlock(&ctx->callbackMutex);
return ERROR_NONE;
}
error_t api_remove_event_callback(Device* device, WifiEventCallback callback) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || callback == nullptr) return ERROR_INVALID_ARGUMENT;
mutex_lock(&ctx->callbackMutex);
for (size_t i = 0; i < ctx->callbackCount; i++) {
if (ctx->callbacks[i].fn == callback) {
for (size_t j = i; j + 1 < ctx->callbackCount; j++) {
ctx->callbacks[j] = ctx->callbacks[j + 1];
}
ctx->callbackCount--;
mutex_unlock(&ctx->callbackMutex);
return ERROR_NONE;
}
}
mutex_unlock(&ctx->callbackMutex);
return ERROR_NOT_FOUND;
}
const WifiApi esp32_wifi_api = {
.get_radio_state = api_get_radio_state,
.get_station_state = api_get_station_state,
.get_access_point_state = api_get_access_point_state,
.is_scanning = api_is_scanning,
.scan = api_scan,
.get_scan_results = api_get_scan_results,
.station_get_ipv4_address = api_station_get_ipv4_address,
.station_get_target_ssid = api_station_get_target_ssid,
.station_connect = api_station_connect,
.station_disconnect = api_station_disconnect,
.station_get_rssi = api_station_get_rssi,
.add_event_callback = api_add_event_callback,
.remove_event_callback = api_remove_event_callback
};
// ---- Driver lifecycle ----
// The ESP-IDF WiFi stack isn't touched until the device is actually started:
// registering this driver (module start()) only makes it available for
// binding, it doesn't spin up any resources.
error_t start_device(Device* device) {
auto* ctx = new(std::nothrow) Esp32WifiCtx();
if (ctx == nullptr) return ERROR_OUT_OF_MEMORY;
ctx->device = device;
mutex_construct(&ctx->mutex);
mutex_construct(&ctx->callbackMutex);
device_set_driver_data(device, ctx);
error_t result = bring_up_wifi(ctx);
if (result != ERROR_NONE) {
device_set_driver_data(device, nullptr);
mutex_destruct(&ctx->callbackMutex);
mutex_destruct(&ctx->mutex);
delete ctx;
return result;
}
return ERROR_NONE;
}
error_t stop_device(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr) return ERROR_NONE;
bring_down_wifi(ctx);
device_set_driver_data(device, nullptr);
mutex_destruct(&ctx->callbackMutex);
mutex_destruct(&ctx->mutex);
delete ctx;
return ERROR_NONE;
}
} // namespace
extern "C" {
extern Module platform_esp32_module;
Driver esp32_wifi_driver = {
.name = "esp32_wifi",
.compatible = (const char*[]) { "espressif,esp32-wifi", nullptr },
.start_device = start_device,
.stop_device = stop_device,
.api = (const void*)&esp32_wifi_api,
.device_type = &WIFI_TYPE,
.owner = &platform_esp32_module,
.internal = nullptr
};
} // extern "C"
#endif // CONFIG_SOC_WIFI_SUPPORTED or CONFIG_SLAVE_SOC_WIFI_SUPPORTED
@@ -0,0 +1,336 @@
#ifdef ESP_PLATFORM
#include <sdkconfig.h>
#endif
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#include <esp_wifi.h> // for WIFI_TASK_CORE_ID
#include <tactility/concurrent/dispatcher.h>
#include <tactility/concurrent/event_group.h>
#include <tactility/concurrent/thread.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/esp32_wifi_pinned.h>
#include <tactility/drivers/wifi.h>
#include <tactility/log.h>
#include <cstring>
#include <functional>
#include <new>
#define TAG "esp32_wifi_pinned"
namespace {
constexpr uint32_t CALL_DONE_FLAG = 1U;
constexpr configSTACK_DEPTH_TYPE PINNED_THREAD_STACK_SIZE = 4096;
// This driver wraps "espressif,esp32-wifi" (created as a child device) and
// marshals every call that mutates WiFi state onto a dedicated task pinned to
// WIFI_TASK_CORE_ID. ESP-IDF's WiFi driver misbehaves (hangs waiting on its
// internal task semaphore, tripping the watchdog) when those calls originate
// from an arbitrary caller task/core, so this driver guarantees the correct
// core regardless of who calls the WifiApi.
struct Esp32WifiPinnedCtx {
Device* device = nullptr;
Device* child = nullptr;
Thread* thread = nullptr;
DispatcherHandle_t dispatcher = nullptr;
volatile bool running = false;
};
#define GET_CTX(device) (static_cast<Esp32WifiPinnedCtx*>(device_get_driver_data(device)))
struct MarshalledCall {
const std::function<void()>* work;
EventGroupHandle_t done;
};
void marshal_trampoline(void* context) {
auto* call = static_cast<MarshalledCall*>(context);
(*call->work)();
event_group_set(call->done, CALL_DONE_FLAG);
}
/** Runs `work` synchronously on the pinned WiFi task, blocking the caller until it completes. */
error_t run_on_pinned_thread(Esp32WifiPinnedCtx* ctx, const std::function<void()>& work) {
if (ctx == nullptr || ctx->thread == nullptr) {
return ERROR_INVALID_STATE;
}
// Avoid deadlocking when already running on the pinned thread (e.g. during device_start()).
if (thread_get_current() == ctx->thread) {
work();
return ERROR_NONE;
}
MarshalledCall call = { .work = &work, .done = nullptr };
event_group_construct(&call.done);
error_t err = dispatcher_dispatch(ctx->dispatcher, &call, marshal_trampoline);
if (err != ERROR_NONE) {
event_group_destruct(&call.done);
return err;
}
event_group_wait(call.done, CALL_DONE_FLAG, false, true, nullptr, portMAX_DELAY);
event_group_destruct(&call.done);
return ERROR_NONE;
}
void request_stop(void* context) {
static_cast<Esp32WifiPinnedCtx*>(context)->running = false;
}
int32_t pinned_thread_main(void* context) {
auto* ctx = static_cast<Esp32WifiPinnedCtx*>(context);
while (ctx->running) {
dispatcher_consume(ctx->dispatcher);
}
return 0;
}
// ---- Work that must run on the pinned thread (see start_device/stop_device) ----
error_t start_child_work(Esp32WifiPinnedCtx* ctx) {
ctx->child = new(std::nothrow) Device();
if (ctx->child == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
std::memset(ctx->child, 0, sizeof(Device));
ctx->child->parent = ctx->device;
ctx->child->name = ctx->device->name;
error_t err = device_construct_add_start(ctx->child, "espressif,esp32-wifi");
if (err != ERROR_NONE) {
LOG_E(TAG, "%s: failed to start child wifi device: %s", ctx->device->name, error_to_string(err));
delete ctx->child;
ctx->child = nullptr;
}
return err;
}
void stop_child_work(Esp32WifiPinnedCtx* ctx) {
if (ctx->child == nullptr) {
return;
}
if (device_is_ready(ctx->child)) {
device_stop(ctx->child);
}
if (device_is_added(ctx->child)) {
device_remove(ctx->child);
}
device_destruct(ctx->child);
delete ctx->child;
ctx->child = nullptr;
}
// ---- WifiApi: read-only calls forward straight to the child, no core affinity needed ----
error_t api_get_radio_state(Device* device, WifiRadioState* state) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
return wifi_get_radio_state(ctx->child, state);
}
error_t api_get_station_state(Device* device, WifiStationState* state) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
return wifi_get_station_state(ctx->child, state);
}
error_t api_get_access_point_state(Device* device, WifiAccessPointState* state) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
return wifi_get_access_point_state(ctx->child, state);
}
bool api_is_scanning(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return false;
return wifi_is_scanning(ctx->child);
}
error_t api_get_scan_results(Device* device, WifiApRecord* results, size_t* num_results) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
return wifi_get_scan_results(ctx->child, results, num_results);
}
error_t api_station_get_ipv4_address(Device* device, char* ipv4) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
return wifi_station_get_ipv4_address(ctx->child, ipv4);
}
error_t api_station_get_target_ssid(Device* device, char* ssid) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
return wifi_station_get_target_ssid(ctx->child, ssid);
}
error_t api_station_get_rssi(Device* device, int32_t* rssi) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
return wifi_station_get_rssi(ctx->child, rssi);
}
error_t api_add_event_callback(Device* device, void* callback_context, WifiEventCallback callback) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
return wifi_add_event_callback(ctx->child, callback_context, callback);
}
error_t api_remove_event_callback(Device* device, WifiEventCallback callback) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
return wifi_remove_event_callback(ctx->child, callback);
}
// ---- WifiApi: state-changing calls are marshalled onto the pinned WiFi task ----
error_t api_scan(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
error_t result = ERROR_NONE;
Device* child = ctx->child;
error_t err = run_on_pinned_thread(ctx, [child, &result]() {
result = wifi_scan(child);
});
return err != ERROR_NONE ? err : result;
}
error_t api_station_connect(Device* device, const char* ssid, const char* password, int32_t channel) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
error_t result = ERROR_NONE;
Device* child = ctx->child;
error_t err = run_on_pinned_thread(ctx, [child, ssid, password, channel, &result]() {
result = wifi_station_connect(child, ssid, password, channel);
});
return err != ERROR_NONE ? err : result;
}
error_t api_station_disconnect(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ctx->child == nullptr) return ERROR_INVALID_STATE;
error_t result = ERROR_NONE;
Device* child = ctx->child;
error_t err = run_on_pinned_thread(ctx, [child, &result]() {
result = wifi_station_disconnect(child);
});
return err != ERROR_NONE ? err : result;
}
const WifiApi esp32_wifi_pinned_api = {
.get_radio_state = api_get_radio_state,
.get_station_state = api_get_station_state,
.get_access_point_state = api_get_access_point_state,
.is_scanning = api_is_scanning,
.scan = api_scan,
.get_scan_results = api_get_scan_results,
.station_get_ipv4_address = api_station_get_ipv4_address,
.station_get_target_ssid = api_station_get_target_ssid,
.station_connect = api_station_connect,
.station_disconnect = api_station_disconnect,
.station_get_rssi = api_station_get_rssi,
.add_event_callback = api_add_event_callback,
.remove_event_callback = api_remove_event_callback
};
// ---- Driver lifecycle ----
// Starting/stopping the child (which brings the ESP-IDF WiFi stack up/down)
// also happens on the pinned thread, since esp_wifi_init/start/stop/deinit
// are subject to the same core-affinity requirement as the state-changing
// WifiApi calls above.
error_t start_device(Device* device) {
auto* ctx = new(std::nothrow) Esp32WifiPinnedCtx();
if (ctx == nullptr) return ERROR_OUT_OF_MEMORY;
ctx->device = device;
device_set_driver_data(device, ctx);
ctx->dispatcher = dispatcher_alloc();
ctx->running = true;
ctx->thread = thread_alloc_full(
"esp32_wifi_pinned",
PINNED_THREAD_STACK_SIZE,
pinned_thread_main,
ctx,
WIFI_TASK_CORE_ID
);
if (ctx->thread == nullptr || thread_start(ctx->thread) != ERROR_NONE) {
LOG_E(TAG, "%s: failed to start pinned wifi task", device->name);
if (ctx->thread != nullptr) thread_free(ctx->thread);
dispatcher_free(ctx->dispatcher);
device_set_driver_data(device, nullptr);
delete ctx;
return ERROR_RESOURCE;
}
error_t result = ERROR_NONE;
error_t err = run_on_pinned_thread(ctx, [ctx, &result]() {
result = start_child_work(ctx);
});
if (err == ERROR_NONE) {
err = result;
}
if (err != ERROR_NONE) {
dispatcher_dispatch(ctx->dispatcher, ctx, request_stop);
thread_join(ctx->thread, portMAX_DELAY, 10);
thread_free(ctx->thread);
dispatcher_free(ctx->dispatcher);
device_set_driver_data(device, nullptr);
delete ctx;
return err;
}
return ERROR_NONE;
}
error_t stop_device(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr) return ERROR_NONE;
error_t err = run_on_pinned_thread(ctx, [ctx]() {
stop_child_work(ctx);
});
if (err != ERROR_NONE) {
LOG_E(TAG, "%s: failed to stop child wifi device on pinned task: %s", device->name, error_to_string(err));
}
dispatcher_dispatch(ctx->dispatcher, ctx, request_stop);
thread_join(ctx->thread, portMAX_DELAY, 10);
thread_free(ctx->thread);
dispatcher_free(ctx->dispatcher);
device_set_driver_data(device, nullptr);
delete ctx;
return err;
}
} // namespace
extern "C" {
extern Module platform_esp32_module;
Driver esp32_wifi_pinned_driver = {
.name = "esp32_wifi_pinned",
.compatible = (const char*[]) { "espressif,esp32-wifi-pinned", nullptr },
.start_device = start_device,
.stop_device = stop_device,
.api = (const void*)&esp32_wifi_pinned_api,
.device_type = &WIFI_TYPE,
.owner = &platform_esp32_module,
.internal = nullptr
};
} // extern "C"
#endif // CONFIG_SOC_WIFI_SUPPORTED or CONFIG_SLAVE_SOC_WIFI_SUPPORTED
@@ -22,6 +22,10 @@ extern Driver esp32_sdspi_driver;
extern Driver esp32_spi_driver; extern Driver esp32_spi_driver;
extern Driver esp32_uart_driver; extern Driver esp32_uart_driver;
extern Driver esp32_grove_driver; extern Driver esp32_grove_driver;
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
extern Driver esp32_wifi_driver;
extern Driver esp32_wifi_pinned_driver;
#endif
#if defined(CONFIG_BT_NIMBLE_ENABLED) #if defined(CONFIG_BT_NIMBLE_ENABLED)
extern Driver esp32_bluetooth_driver; extern Driver esp32_bluetooth_driver;
extern Driver esp32_ble_serial_driver; extern Driver esp32_ble_serial_driver;
@@ -49,6 +53,10 @@ static error_t start() {
check(driver_construct_add(&esp32_spi_driver) == ERROR_NONE); check(driver_construct_add(&esp32_spi_driver) == ERROR_NONE);
check(driver_construct_add(&esp32_uart_driver) == ERROR_NONE); check(driver_construct_add(&esp32_uart_driver) == ERROR_NONE);
check(driver_construct_add(&esp32_grove_driver) == ERROR_NONE); check(driver_construct_add(&esp32_grove_driver) == ERROR_NONE);
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
check(driver_construct_add(&esp32_wifi_driver) == ERROR_NONE);
check(driver_construct_add(&esp32_wifi_pinned_driver) == ERROR_NONE);
#endif
#if defined(CONFIG_BT_NIMBLE_ENABLED) #if defined(CONFIG_BT_NIMBLE_ENABLED)
check(driver_construct_add(&esp32_bluetooth_driver) == ERROR_NONE); check(driver_construct_add(&esp32_bluetooth_driver) == ERROR_NONE);
check(driver_construct_add(&esp32_ble_serial_driver) == ERROR_NONE); check(driver_construct_add(&esp32_ble_serial_driver) == ERROR_NONE);
@@ -67,6 +75,10 @@ static error_t start() {
static error_t stop() { static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct, /* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */ * there is no guarantee that the previously destroyed drivers can be recovered */
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
check(driver_remove_destruct(&esp32_wifi_pinned_driver) == ERROR_NONE);
check(driver_remove_destruct(&esp32_wifi_driver) == ERROR_NONE);
#endif
#if SOC_USB_OTG_SUPPORTED #if SOC_USB_OTG_SUPPORTED
check(driver_remove_destruct(&esp32_usbhost_msc_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_usbhost_msc_driver) == ERROR_NONE);
check(driver_remove_destruct(&esp32_usbhost_midi_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_usbhost_midi_driver) == ERROR_NONE);
@@ -0,0 +1,349 @@
// SPDX-License-Identifier: Apache-2.0
// Mock WiFi driver for the simulator: fakes a radio with a fixed list of
// access points instead of talking to real hardware. Behaviour is modeled
// after Tactility/Source/service/wifi/WifiMock.cpp (the old HAL's equivalent
// mock) - actions succeed instantly, there's no real scan or connect delay.
#include <tactility/concurrent/mutex.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/wifi.h>
#include <tactility/log.h>
#include <algorithm>
#include <cstring>
#include <new>
#define TAG "mock_wifi"
namespace {
constexpr size_t WIFI_MAX_CALLBACKS = 4;
struct MockApRecord {
const char* ssid;
int8_t rssi;
WifiAuthenticationType authentication_type;
};
// Same fixture data as the old WifiMock.cpp, so simulator UI testing sees
// familiar results.
constexpr MockApRecord MOCK_SCAN_RESULTS[] = {
{ "Home Wifi", -30, WIFI_AUTHENTICATION_TYPE_WPA2_PSK },
{ "No place like 127.0.0.1", -67, WIFI_AUTHENTICATION_TYPE_WPA2_PSK },
{ "Pretty fly for a Wi-Fi", -70, WIFI_AUTHENTICATION_TYPE_WPA2_PSK },
{ "An AP with a really, really long name", -80, WIFI_AUTHENTICATION_TYPE_WPA2_PSK },
{ "Bad Reception", -90, WIFI_AUTHENTICATION_TYPE_OPEN },
};
constexpr size_t MOCK_SCAN_RESULT_COUNT = sizeof(MOCK_SCAN_RESULTS) / sizeof(MOCK_SCAN_RESULTS[0]);
constexpr int8_t MOCK_CONNECTED_RSSI = -30;
constexpr const char* MOCK_IPV4_ADDRESS = "192.168.1.2";
struct WifiCallbackEntry {
WifiEventCallback fn = nullptr;
void* ctx = nullptr;
};
struct PosixWifiCtx {
Device* device = nullptr;
Mutex mutex {};
WifiRadioState radioState = WIFI_RADIO_STATE_OFF;
WifiStationState stationState = WIFI_STATION_STATE_DISCONNECTED;
bool scanning = false;
char targetSsid[33] = {};
Mutex callbackMutex {};
WifiCallbackEntry callbacks[WIFI_MAX_CALLBACKS] = {};
size_t callbackCount = 0;
};
#define GET_CTX(device) (static_cast<PosixWifiCtx*>(device_get_driver_data(device)))
void fireEvent(PosixWifiCtx* ctx, WifiEvent event) {
WifiCallbackEntry local[WIFI_MAX_CALLBACKS];
size_t count;
mutex_lock(&ctx->callbackMutex);
count = ctx->callbackCount;
memcpy(local, ctx->callbacks, count * sizeof(WifiCallbackEntry));
mutex_unlock(&ctx->callbackMutex);
for (size_t i = 0; i < count; i++) {
local[i].fn(ctx->device, local[i].ctx, event);
}
}
// ---- WifiApi ----
error_t apiGetRadioState(Device* device, WifiRadioState* state) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || state == nullptr) return ERROR_INVALID_ARGUMENT;
mutex_lock(&ctx->mutex);
*state = ctx->radioState;
mutex_unlock(&ctx->mutex);
return ERROR_NONE;
}
error_t apiGetStationState(Device* device, WifiStationState* state) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || state == nullptr) return ERROR_INVALID_ARGUMENT;
mutex_lock(&ctx->mutex);
*state = ctx->stationState;
mutex_unlock(&ctx->mutex);
return ERROR_NONE;
}
error_t apiGetAccessPointState(Device* device, WifiAccessPointState* state) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || state == nullptr) return ERROR_INVALID_ARGUMENT;
// Access point mode isn't implemented by this mock.
*state = WIFI_ACCESS_POINT_STATE_STOPPED;
return ERROR_NONE;
}
bool apiIsScanning(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr) return false;
mutex_lock(&ctx->mutex);
bool scanning = ctx->scanning;
mutex_unlock(&ctx->mutex);
return scanning;
}
error_t apiScan(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr) return ERROR_INVALID_STATE;
mutex_lock(&ctx->mutex);
if (ctx->radioState != WIFI_RADIO_STATE_ON || ctx->scanning) {
mutex_unlock(&ctx->mutex);
return ERROR_INVALID_STATE;
}
ctx->scanning = true;
mutex_unlock(&ctx->mutex);
WifiEvent started_event = {};
started_event.type = WIFI_EVENT_TYPE_SCAN_STARTED;
fireEvent(ctx, started_event);
// No real radio, so the "scan" completes instantly.
mutex_lock(&ctx->mutex);
ctx->scanning = false;
mutex_unlock(&ctx->mutex);
WifiEvent finished_event = {};
finished_event.type = WIFI_EVENT_TYPE_SCAN_FINISHED;
fireEvent(ctx, finished_event);
return ERROR_NONE;
}
error_t apiGetScanResults(Device* device, WifiApRecord* results, size_t* num_results) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || results == nullptr || num_results == nullptr) return ERROR_INVALID_ARGUMENT;
size_t count = std::min(*num_results, MOCK_SCAN_RESULT_COUNT);
for (size_t i = 0; i < count; i++) {
const MockApRecord& src = MOCK_SCAN_RESULTS[i];
WifiApRecord& dst = results[i];
memset(dst.ssid, 0, sizeof(dst.ssid));
strncpy(dst.ssid, src.ssid, sizeof(dst.ssid) - 1);
dst.rssi = src.rssi;
dst.channel = 1;
dst.authentication_type = src.authentication_type;
}
*num_results = count;
return ERROR_NONE;
}
error_t apiStationGetIpv4Address(Device* device, char* ipv4) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ipv4 == nullptr) return ERROR_INVALID_ARGUMENT;
mutex_lock(&ctx->mutex);
bool connected = ctx->stationState == WIFI_STATION_STATE_CONNECTED;
mutex_unlock(&ctx->mutex);
strncpy(ipv4, connected ? MOCK_IPV4_ADDRESS : "0.0.0.0", 16);
return ERROR_NONE;
}
error_t apiStationGetTargetSsid(Device* device, char* ssid) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ssid == nullptr) return ERROR_INVALID_ARGUMENT;
mutex_lock(&ctx->mutex);
strncpy(ssid, ctx->targetSsid, sizeof(ctx->targetSsid));
mutex_unlock(&ctx->mutex);
return ERROR_NONE;
}
error_t apiStationConnect(Device* device, const char* ssid, const char* password, int32_t channel) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || ssid == nullptr) return ERROR_INVALID_ARGUMENT;
mutex_lock(&ctx->mutex);
if (ctx->radioState != WIFI_RADIO_STATE_ON) {
mutex_unlock(&ctx->mutex);
return ERROR_INVALID_STATE;
}
strncpy(ctx->targetSsid, ssid, sizeof(ctx->targetSsid) - 1);
ctx->targetSsid[sizeof(ctx->targetSsid) - 1] = '\0';
ctx->stationState = WIFI_STATION_STATE_CONNECTION_PENDING;
mutex_unlock(&ctx->mutex);
WifiEvent pending_event = {};
pending_event.type = WIFI_EVENT_TYPE_STATION_STATE_CHANGED;
pending_event.station_state = WIFI_STATION_STATE_CONNECTION_PENDING;
fireEvent(ctx, pending_event);
// No real radio to negotiate with, so the mock always succeeds instantly.
mutex_lock(&ctx->mutex);
ctx->stationState = WIFI_STATION_STATE_CONNECTED;
mutex_unlock(&ctx->mutex);
WifiEvent connected_event = {};
connected_event.type = WIFI_EVENT_TYPE_STATION_STATE_CHANGED;
connected_event.station_state = WIFI_STATION_STATE_CONNECTED;
fireEvent(ctx, connected_event);
WifiEvent result_event = {};
result_event.type = WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT;
result_event.connection_error = WIFI_STATION_CONNECTION_ERROR_NONE;
fireEvent(ctx, result_event);
return ERROR_NONE;
}
error_t apiStationDisconnect(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr) return ERROR_INVALID_STATE;
mutex_lock(&ctx->mutex);
bool was_connected = ctx->stationState != WIFI_STATION_STATE_DISCONNECTED;
ctx->stationState = WIFI_STATION_STATE_DISCONNECTED;
mutex_unlock(&ctx->mutex);
if (was_connected) {
WifiEvent event = {};
event.type = WIFI_EVENT_TYPE_STATION_STATE_CHANGED;
event.station_state = WIFI_STATION_STATE_DISCONNECTED;
fireEvent(ctx, event);
}
return ERROR_NONE;
}
error_t apiStationGetRssi(Device* device, int32_t* rssi) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || rssi == nullptr) return ERROR_INVALID_ARGUMENT;
mutex_lock(&ctx->mutex);
bool connected = ctx->stationState == WIFI_STATION_STATE_CONNECTED;
mutex_unlock(&ctx->mutex);
if (!connected) return ERROR_INVALID_STATE;
*rssi = MOCK_CONNECTED_RSSI;
return ERROR_NONE;
}
error_t apiAddEventCallback(Device* device, void* callback_context, WifiEventCallback callback) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || callback == nullptr) return ERROR_INVALID_ARGUMENT;
mutex_lock(&ctx->callbackMutex);
if (ctx->callbackCount >= WIFI_MAX_CALLBACKS) {
mutex_unlock(&ctx->callbackMutex);
return ERROR_OUT_OF_MEMORY;
}
ctx->callbacks[ctx->callbackCount] = { .fn = callback, .ctx = callback_context };
ctx->callbackCount++;
mutex_unlock(&ctx->callbackMutex);
return ERROR_NONE;
}
error_t apiRemoveEventCallback(Device* device, WifiEventCallback callback) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr || callback == nullptr) return ERROR_INVALID_ARGUMENT;
mutex_lock(&ctx->callbackMutex);
for (size_t i = 0; i < ctx->callbackCount; i++) {
if (ctx->callbacks[i].fn == callback) {
for (size_t j = i; j + 1 < ctx->callbackCount; j++) {
ctx->callbacks[j] = ctx->callbacks[j + 1];
}
ctx->callbackCount--;
mutex_unlock(&ctx->callbackMutex);
return ERROR_NONE;
}
}
mutex_unlock(&ctx->callbackMutex);
return ERROR_NOT_FOUND;
}
const WifiApi posix_wifi_api = {
.get_radio_state = apiGetRadioState,
.get_station_state = apiGetStationState,
.get_access_point_state = apiGetAccessPointState,
.is_scanning = apiIsScanning,
.scan = apiScan,
.get_scan_results = apiGetScanResults,
.station_get_ipv4_address = apiStationGetIpv4Address,
.station_get_target_ssid = apiStationGetTargetSsid,
.station_connect = apiStationConnect,
.station_disconnect = apiStationDisconnect,
.station_get_rssi = apiStationGetRssi,
.add_event_callback = apiAddEventCallback,
.remove_event_callback = apiRemoveEventCallback
};
// ---- Driver lifecycle ----
// Unlike the real esp32 driver, there's no hardware to bring up: the radio
// simply reports itself as ON as soon as the device is started.
error_t startDevice(Device* device) {
auto* ctx = new(std::nothrow) PosixWifiCtx();
if (ctx == nullptr) return ERROR_OUT_OF_MEMORY;
ctx->device = device;
mutex_construct(&ctx->mutex);
mutex_construct(&ctx->callbackMutex);
ctx->radioState = WIFI_RADIO_STATE_ON;
device_set_driver_data(device, ctx);
LOG_I(TAG, "WiFi radio on (mock)");
return ERROR_NONE;
}
error_t stopDevice(Device* device) {
auto* ctx = GET_CTX(device);
if (ctx == nullptr) return ERROR_NONE;
device_set_driver_data(device, nullptr);
mutex_destruct(&ctx->callbackMutex);
mutex_destruct(&ctx->mutex);
delete ctx;
LOG_I(TAG, "WiFi radio off (mock)");
return ERROR_NONE;
}
} // namespace
extern "C" {
extern Module platform_posix_module;
Driver posix_wifi_driver = {
.name = "mock_wifi",
.compatible = (const char*[]) { "posix,mock-wifi", nullptr },
.start_device = startDevice,
.stop_device = stopDevice,
.api = (const void*)&posix_wifi_api,
.device_type = &WIFI_TYPE,
.owner = &platform_posix_module,
.internal = nullptr
};
} // extern "C"
+6 -2
View File
@@ -1,15 +1,19 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h>
#include <tactility/module.h> #include <tactility/module.h>
extern "C" { extern "C" {
extern Driver posix_wifi_driver;
static error_t start() { static error_t start() {
/* NO-OP for now */ check(driver_construct_add(&posix_wifi_driver) == ERROR_NONE);
return ERROR_NONE; return ERROR_NONE;
} }
static error_t stop() { static error_t stop() {
/* NO-OP for now */ check(driver_remove_destruct(&posix_wifi_driver) == ERROR_NONE);
return ERROR_NONE; return ERROR_NONE;
} }
+29 -2
View File
@@ -1,18 +1,45 @@
#pragma once #pragma once
#include "tactility/concurrent/dispatcher.h"
#include "tactility/device.h" #include "tactility/device.h"
#include "tactility/module.h" #include "tactility/module.h"
#include <Tactility/Dispatcher.h>
#include <Tactility/app/AppManifest.h> #include <Tactility/app/AppManifest.h>
#include <Tactility/hal/Configuration.h> #include <Tactility/hal/Configuration.h>
#include <Tactility/service/ServiceManifest.h> #include <Tactility/service/ServiceManifest.h>
#include <functional>
extern "C" { extern "C" {
struct DtsDevice; struct DtsDevice;
} }
namespace tt { namespace tt {
/**
* A thin C++ wrapper around the TactilityKernel dispatcher, allowing
* std::function (and therefore capturing lambdas) to be dispatched.
*/
class MainDispatcher final {
public:
using Function = std::function<void()>;
explicit MainDispatcher(DispatcherHandle_t handle) : handle(handle) {}
/**
* Queue a function to be consumed on the main task.
* @param[in] function the function to execute elsewhere
* @param[in] timeout lock acquisition timeout
* @return true if dispatching was successful (timeout not reached)
*/
bool dispatch(Function function, TickType_t timeout = portMAX_DELAY) const;
private:
DispatcherHandle_t handle;
};
/** @brief The configuration for the operating system /** @brief The configuration for the operating system
* It contains the hardware configuration, apps and services * It contains the hardware configuration, apps and services
*/ */
@@ -39,7 +66,7 @@ const Configuration* getConfiguration();
* @warning This dispatcher is used for WiFi and might block for some time during WiFi connection. * @warning This dispatcher is used for WiFi and might block for some time during WiFi connection.
* @return the dispatcher * @return the dispatcher
*/ */
Dispatcher& getMainDispatcher(); MainDispatcher getMainDispatcher();
namespace hal { namespace hal {
@@ -1,24 +0,0 @@
#pragma once
#include <tactility/drivers/spi_controller.h>
#include <tactility/device.h>
#include <Tactility/Lock.h>
namespace tt {
class SpiDeviceLock : public Lock {
::Device* device;
public:
explicit SpiDeviceLock(::Device* device) : device(device) { }
bool lock(TickType_t timeout) const override {
return spi_controller_try_lock(device, timeout) == ERROR_NONE;
}
void unlock() const override {
spi_controller_unlock(device);
}
};
}
@@ -1,9 +1,8 @@
#pragma once #pragma once
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
#include <esp_http_server.h>
#include <Tactility/RecursiveMutex.h> #include <Tactility/RecursiveMutex.h>
#include <Tactility/kernel/SystemEvents.h> #include <esp_http_server.h>
namespace tt::network { namespace tt::network {
@@ -6,52 +6,16 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "WifiApSettings.h" #include <tactility/drivers/wifi.h>
#ifdef ESP_PLATFORM #include "WifiApSettings.h"
#include <esp_wifi.h>
#else
#include <cstdint>
// From esp_wifi_types.h in ESP-IDF 5.2
typedef enum {
WIFI_AUTH_OPEN = 0, /**< authenticate mode : open */
WIFI_AUTH_WEP, /**< authenticate mode : WEP */
WIFI_AUTH_WPA_PSK, /**< authenticate mode : WPA_PSK */
WIFI_AUTH_WPA2_PSK, /**< authenticate mode : WPA2_PSK */
WIFI_AUTH_WPA_WPA2_PSK, /**< authenticate mode : WPA_WPA2_PSK */
WIFI_AUTH_ENTERPRISE, /**< authenticate mode : WiFi EAP security */
WIFI_AUTH_WPA2_ENTERPRISE = WIFI_AUTH_ENTERPRISE, /**< authenticate mode : WiFi EAP security */
WIFI_AUTH_WPA3_PSK, /**< authenticate mode : WPA3_PSK */
WIFI_AUTH_WPA2_WPA3_PSK, /**< authenticate mode : WPA2_WPA3_PSK */
WIFI_AUTH_WAPI_PSK, /**< authenticate mode : WAPI_PSK */
WIFI_AUTH_OWE, /**< authenticate mode : OWE */
WIFI_AUTH_WPA3_ENT_192, /**< authenticate mode : WPA3_ENT_SUITE_B_192_BIT */
WIFI_AUTH_WPA3_EXT_PSK, /**< authenticate mode : WPA3_PSK_EXT_KEY */
WIFI_AUTH_WPA3_EXT_PSK_MIXED_MODE, /**< authenticate mode: WPA3_PSK + WPA3_PSK_EXT_KEY */
WIFI_AUTH_MAX
} wifi_auth_mode_t;
#endif
namespace tt::service::wifi { namespace tt::service::wifi {
enum class WifiEvent { /** The kernel wifi driver's event type: a WifiEventType tag plus a union
/** Radio was turned on */ * payload (radio_state/station_state/access_point_state/connection_error
RadioStateOn, * depending on the tag). See tactility/drivers/wifi.h. */
/** Radio is turning on. */ using WifiEvent = ::WifiEvent;
RadioStateOnPending,
/** Radio is turned off */
RadioStateOff,
/** Radio is turning off */
RadioStateOffPending,
/** Started scanning for access points */
ScanStarted,
/** Finished scanning for access points */ // TODO: 1 second validity
ScanFinished,
Disconnected,
ConnectionPending,
ConnectionSuccess,
ConnectionFailed
};
enum class RadioState { enum class RadioState {
OnPending, OnPending,
@@ -62,13 +26,6 @@ enum class RadioState {
Off, Off,
}; };
struct ApRecord {
std::string ssid;
int8_t rssi;
int32_t channel;
wifi_auth_mode_t auth_mode;
};
/** /**
* @brief Get wifi pubsub that broadcasts Event objects * @brief Get wifi pubsub that broadcasts Event objects
* @return PubSub * @return PubSub
@@ -93,7 +50,7 @@ bool isScanning();
std::string getConnectionTarget(); std::string getConnectionTarget();
/** @return the access points from the last scan (if any). It only contains public APs. */ /** @return the access points from the last scan (if any). It only contains public APs. */
std::vector<ApRecord> getScanResults(); std::vector<WifiApRecord> getScanResults();
/** /**
* @brief Overrides the default scan result size of 16. * @brief Overrides the default scan result size of 16.
@@ -14,7 +14,7 @@ class State final {
bool scanning = false; bool scanning = false;
bool scannedAfterRadioOn = false; bool scannedAfterRadioOn = false;
service::wifi::RadioState radioState; service::wifi::RadioState radioState;
std::vector<service::wifi::ApRecord> apRecords; std::vector<WifiApRecord> apRecords;
std::string connectSsid; std::string connectSsid;
public: public:
@@ -30,14 +30,14 @@ public:
void updateApRecords(); void updateApRecords();
template <std::invocable<const std::vector<service::wifi::ApRecord>&> Func> template <std::invocable<const std::vector<WifiApRecord>&> Func>
void withApRecords(Func&& onApRecords) const { void withApRecords(Func&& onApRecords) const {
mutex.withLock([&] { mutex.withLock([&] {
std::invoke(std::forward<Func>(onApRecords), apRecords); std::invoke(std::forward<Func>(onApRecords), apRecords);
}); });
} }
std::vector<service::wifi::ApRecord> getApRecords() const { std::vector<WifiApRecord> getApRecords() const {
auto lock = mutex.asScopedLock(); auto lock = mutex.asScopedLock();
lock.lock(); lock.lock();
return apRecords; return apRecords;
@@ -27,7 +27,7 @@ class View final {
void updateScanning(); void updateScanning();
void updateNetworkList(); void updateNetworkList();
void updateConnectToHidden(); void updateConnectToHidden();
void createSsidListItem(const service::wifi::ApRecord& record, bool isConnecting, size_t index); void createSsidListItem(const WifiApRecord& record, bool isConnecting, size_t index);
static void showDetails(lv_event_t* event); static void showDetails(lv_event_t* event);
static void connect(lv_event_t* event); static void connect(lv_event_t* event);
@@ -1,4 +1,4 @@
#include <Tactility/kernel/critical/Critical.h> #include <Tactility/Critical.h>
#include <Tactility/freertoscompat/Task.h> #include <Tactility/freertoscompat/Task.h>
#include <Tactility/kernel/Kernel.h> #include <Tactility/kernel/Kernel.h>
@@ -86,7 +86,7 @@ const CrashData& getRtcCrashData() { return crashData; }
// Stub implementation for RISC-V and other architectures // Stub implementation for RISC-V and other architectures
// TODO: Implement crash data collection for RISC-V using frame pointer or EH frame // TODO: Implement crash data collection for RISC-V using frame pointer or EH frame
#include <Tactility/kernel/PanicHandler.h> #include <Tactility/PanicHandler.h>
static CrashData emptyCrashData = {}; static CrashData emptyCrashData = {};
@@ -1,4 +1,4 @@
#include "Tactility/kernel/Platform.h" #include "../Include/Tactility/Platform.h"
namespace tt::kernel { namespace tt::kernel {
@@ -1,4 +1,4 @@
#include <Tactility/kernel/SystemEvents.h> #include <Tactility/SystemEvents.h>
#include <Tactility/Mutex.h> #include <Tactility/Mutex.h>
#include <tactility/check.h> #include <tactility/check.h>
+23 -5
View File
@@ -9,7 +9,6 @@
#include <Tactility/LogMessages.h> #include <Tactility/LogMessages.h>
#include <Tactility/CpuAffinity.h> #include <Tactility/CpuAffinity.h>
#include <Tactility/DispatcherThread.h>
#include <Tactility/MountPoints.h> #include <Tactility/MountPoints.h>
#include <Tactility/app/AppManifestParsing.h> #include <Tactility/app/AppManifestParsing.h>
#include <Tactility/app/AppRegistration.h> #include <Tactility/app/AppRegistration.h>
@@ -46,7 +45,26 @@ namespace tt {
constexpr auto* TAG = "Tactility"; constexpr auto* TAG = "Tactility";
static const Configuration* config_instance = nullptr; static const Configuration* config_instance = nullptr;
static Dispatcher mainDispatcher; static DispatcherHandle_t mainDispatcherHandle = dispatcher_alloc();
namespace {
void mainDispatcherTrampoline(void* context) {
auto* function = static_cast<MainDispatcher::Function*>(context);
(*function)();
delete function;
}
} // namespace
bool MainDispatcher::dispatch(Function function, TickType_t timeout) const {
auto* boxed = new Function(std::move(function));
if (dispatcher_dispatch_timed(handle, boxed, mainDispatcherTrampoline, timeout) != ERROR_NONE) {
delete boxed;
return false;
}
return true;
}
// region Default services // region Default services
namespace service { namespace service {
@@ -376,7 +394,7 @@ void run(const Configuration& config, Module* dtsModules[], DtsDevice dtsDevices
LOG_I(TAG, "Main dispatcher ready"); LOG_I(TAG, "Main dispatcher ready");
while (true) { while (true) {
mainDispatcher.consume(); dispatcher_consume(mainDispatcherHandle);
} }
} }
@@ -385,8 +403,8 @@ const Configuration* getConfiguration() {
return config_instance; return config_instance;
} }
Dispatcher& getMainDispatcher() { MainDispatcher getMainDispatcher() {
return mainDispatcher; return MainDispatcher(mainDispatcherHandle);
} }
} // namespace } // namespace
+6 -7
View File
@@ -1,16 +1,15 @@
#include "Tactility/lvgl/Lvgl.h" #include "Tactility/lvgl/Lvgl.h"
#include <Tactility/TactilityCore.h> #include <Tactility/SystemEvents.h>
#include <Tactility/CpuAffinity.h>
#include <Tactility/Paths.h>
#include <Tactility/TactilityPrivate.h> #include <Tactility/TactilityPrivate.h>
#include <Tactility/app/alertdialog/AlertDialog.h>
#include <Tactility/app/AppContext.h> #include <Tactility/app/AppContext.h>
#include <Tactility/app/AppPaths.h> #include <Tactility/app/AppPaths.h>
#include <Tactility/CpuAffinity.h> #include <Tactility/app/alertdialog/AlertDialog.h>
#include <Tactility/hal/display/DisplayDevice.h> #include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/hal/usb/Usb.h> #include <Tactility/hal/usb/Usb.h>
#include <Tactility/kernel/SystemEvents.h>
#include <Tactility/lvgl/Style.h> #include <Tactility/lvgl/Style.h>
#include <Tactility/Paths.h>
#include <Tactility/service/loader/Loader.h> #include <Tactility/service/loader/Loader.h>
#include <Tactility/settings/BootSettings.h> #include <Tactility/settings/BootSettings.h>
#include <Tactility/settings/DisplaySettings.h> #include <Tactility/settings/DisplaySettings.h>
@@ -21,8 +20,8 @@
#include <atomic> #include <atomic>
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
#include "Tactility/app/crashdiagnostics/CrashDiagnostics.h" #include <Tactility/app/crashdiagnostics/CrashDiagnostics.h>
#include <Tactility/kernel/PanicHandler.h> #include <Tactility/PanicHandler.h>
#include <esp_system.h> #include <esp_system.h>
#include <sdkconfig.h> #include <sdkconfig.h>
#else #else
@@ -1,7 +1,7 @@
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
#include <Tactility/app/crashdiagnostics/QrUrl.h> #include <Tactility/app/crashdiagnostics/QrUrl.h>
#include <Tactility/kernel/PanicHandler.h> #include <Tactility/PanicHandler.h>
#include <sstream> #include <sstream>
#include <vector> #include <vector>
+1 -2
View File
@@ -1,10 +1,9 @@
#include <Tactility/app/files/State.h> #include <Tactility/app/files/State.h>
#include <Tactility/LogMessages.h> #include <Tactility/Platform.h>
#include <Tactility/MountPoints.h> #include <Tactility/MountPoints.h>
#include <Tactility/file/File.h> #include <Tactility/file/File.h>
#include <Tactility/file/FileLock.h> #include <Tactility/file/FileLock.h>
#include <Tactility/kernel/Platform.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <cstring> #include <cstring>
+1 -3
View File
@@ -1,16 +1,14 @@
#include <Tactility/app/files/SupportedFiles.h> #include <Tactility/app/files/SupportedFiles.h>
#include <Tactility/app/files/View.h> #include <Tactility/app/files/View.h>
#include <Tactility/LogMessages.h> #include <Tactility/Platform.h>
#include <Tactility/StringUtils.h> #include <Tactility/StringUtils.h>
#include <Tactility/Tactility.h> #include <Tactility/Tactility.h>
#include <Tactility/app/ElfApp.h>
#include <Tactility/app/alertdialog/AlertDialog.h> #include <Tactility/app/alertdialog/AlertDialog.h>
#include <Tactility/app/imageviewer/ImageViewer.h> #include <Tactility/app/imageviewer/ImageViewer.h>
#include <Tactility/app/inputdialog/InputDialog.h> #include <Tactility/app/inputdialog/InputDialog.h>
#include <Tactility/app/notes/Notes.h> #include <Tactility/app/notes/Notes.h>
#include <Tactility/file/File.h> #include <Tactility/file/File.h>
#include <Tactility/kernel/Platform.h>
#include <Tactility/lvgl/LvglSync.h> #include <Tactility/lvgl/LvglSync.h>
#include <Tactility/lvgl/Toolbar.h> #include <Tactility/lvgl/Toolbar.h>
#include <tactility/check.h> #include <tactility/check.h>
+3 -4
View File
@@ -2,12 +2,11 @@
#include <Tactility/file/File.h> #include <Tactility/file/File.h>
#include <Tactility/MountPoints.h> #include <Tactility/MountPoints.h>
#include <Tactility/kernel/Platform.h> #include <Tactility/Platform.h>
#include <Tactility/LogMessages.h>
#include <cstring>
#include <dirent.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <dirent.h>
#include <unistd.h> #include <unistd.h>
#include <vector> #include <vector>
+1 -1
View File
@@ -5,7 +5,7 @@
#include <Tactility/Tactility.h> #include <Tactility/Tactility.h>
#include <Tactility/app/alertdialog/AlertDialog.h> #include <Tactility/app/alertdialog/AlertDialog.h>
#include <Tactility/file/File.h> #include <Tactility/file/File.h>
#include <Tactility/kernel/Platform.h> #include <Tactility/Platform.h>
#include <Tactility/lvgl/LvglSync.h> #include <Tactility/lvgl/LvglSync.h>
#include <Tactility/lvgl/Toolbar.h> #include <Tactility/lvgl/Toolbar.h>
#include <tactility/check.h> #include <tactility/check.h>
@@ -3,9 +3,9 @@
#if TT_FEATURE_SCREENSHOT_ENABLED #if TT_FEATURE_SCREENSHOT_ENABLED
#include <Tactility/Platform.h>
#include <Tactility/app/App.h> #include <Tactility/app/App.h>
#include <Tactility/app/AppManifest.h> #include <Tactility/app/AppManifest.h>
#include <Tactility/kernel/Platform.h>
#include <Tactility/lvgl/Lvgl.h> #include <Tactility/lvgl/Lvgl.h>
#include <Tactility/lvgl/LvglSync.h> #include <Tactility/lvgl/LvglSync.h>
#include <Tactility/lvgl/Toolbar.h> #include <Tactility/lvgl/Toolbar.h>
@@ -25,22 +25,19 @@ static void onConnect(const service::wifi::settings::WifiApSettings& ap_settings
void WifiConnect::onWifiEvent(service::wifi::WifiEvent event) { void WifiConnect::onWifiEvent(service::wifi::WifiEvent event) {
State& state = getState(); State& state = getState();
switch (event) { if (event.type == WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT) {
case service::wifi::WifiEvent::ConnectionFailed: if (event.connection_error == WIFI_STATION_CONNECTION_ERROR_NONE) {
if (state.isConnecting()) {
state.setConnecting(false);
stop(manifest.appId);
}
} else {
if (state.isConnecting()) { if (state.isConnecting()) {
state.setConnecting(false); state.setConnecting(false);
state.setConnectionError(true); state.setConnectionError(true);
requestViewUpdate(); requestViewUpdate();
} }
break;
case service::wifi::WifiEvent::ConnectionSuccess:
if (getState().isConnecting()) {
state.setConnecting(false);
stop(manifest.appId);
} }
break;
default:
break;
} }
requestViewUpdate(); requestViewUpdate();
} }
+6 -6
View File
@@ -76,7 +76,7 @@ void View::connect(lv_event_t* event) {
if (index < ap_records.size()) { if (index < ap_records.size()) {
LOG_I(TAG, "Clicked %zu/%zu", index, ap_records.size() - 1); LOG_I(TAG, "Clicked %zu/%zu", index, ap_records.size() - 1);
auto& ssid = ap_records[index].ssid; std::string ssid = ap_records[index].ssid;
LOG_I(TAG, "Clicked AP: %s", ssid.c_str()); LOG_I(TAG, "Clicked AP: %s", ssid.c_str());
std::string connection_target = service::wifi::getConnectionTarget(); std::string connection_target = service::wifi::getConnectionTarget();
if (connection_target == ssid) { if (connection_target == ssid) {
@@ -97,7 +97,7 @@ void View::showDetails(lv_event_t* event) {
auto ap_records = self->state->getApRecords(); auto ap_records = self->state->getApRecords();
if (index < ap_records.size()) { if (index < ap_records.size()) {
auto& ssid = ap_records[index].ssid; std::string ssid = ap_records[index].ssid;
LOG_I(TAG, "Clicked AP: %s", ssid.c_str()); LOG_I(TAG, "Clicked AP: %s", ssid.c_str());
self->bindings->onShowApSettings(ssid); self->bindings->onShowApSettings(ssid);
} else { } else {
@@ -105,14 +105,14 @@ void View::showDetails(lv_event_t* event) {
} }
} }
void View::createSsidListItem(const service::wifi::ApRecord& record, bool isConnecting, size_t index) { void View::createSsidListItem(const WifiApRecord& record, bool isConnecting, size_t index) {
if (isConnecting) { if (isConnecting) {
auto* button = lv_list_add_button(networks_list, LV_SYMBOL_WIFI, record.ssid.c_str()); auto* button = lv_list_add_button(networks_list, LV_SYMBOL_WIFI, record.ssid);
lv_obj_add_event_cb(button, showDetails, LV_EVENT_SHORT_CLICKED, this); lv_obj_add_event_cb(button, showDetails, LV_EVENT_SHORT_CLICKED, this);
} else { } else {
const std::string auth_info = (record.auth_mode == WIFI_AUTH_OPEN) ? "(open) " : " "; const std::string auth_info = (record.authentication_type == WIFI_AUTHENTICATION_TYPE_OPEN) ? "(open) " : " ";
const auto percentage = mapRssiToPercentage(record.rssi); const auto percentage = mapRssiToPercentage(record.rssi);
const auto label = std::format("{} {}{}%", record.ssid, auth_info, percentage); const auto label = std::format("{} {}{}%", std::string(record.ssid), auth_info, percentage);
auto* button = lv_list_add_button(networks_list, nullptr, label.c_str()); auto* button = lv_list_add_button(networks_list, nullptr, label.c_str());
lv_obj_set_user_data(button, reinterpret_cast<void*>(index)); lv_obj_set_user_data(button, reinterpret_cast<void*>(index));
if (service::wifi::settings::contains(record.ssid)) { if (service::wifi::settings::contains(record.ssid)) {
@@ -79,17 +79,16 @@ void WifiManage::onWifiEvent(service::wifi::WifiEvent event) {
auto radio_state = service::wifi::getRadioState(); auto radio_state = service::wifi::getRadioState();
LOG_I(TAG, "Update with state %s", service::wifi::radioStateToString(radio_state)); LOG_I(TAG, "Update with state %s", service::wifi::radioStateToString(radio_state));
getState().setRadioState(radio_state); getState().setRadioState(radio_state);
switch (event) { switch (event.type) {
using enum service::wifi::WifiEvent; case WIFI_EVENT_TYPE_SCAN_STARTED:
case ScanStarted:
getState().setScanning(true); getState().setScanning(true);
break; break;
case ScanFinished: case WIFI_EVENT_TYPE_SCAN_FINISHED:
getState().setScanning(false); getState().setScanning(false);
getState().updateApRecords(); getState().updateApRecords();
break; break;
case RadioStateOn: case WIFI_EVENT_TYPE_RADIO_STATE_CHANGED:
if (!service::wifi::isScanning()) { if (event.radio_state == WIFI_RADIO_STATE_ON && !service::wifi::isScanning()) {
service::wifi::scan(); service::wifi::scan();
} }
break; break;
+2 -2
View File
@@ -1,9 +1,9 @@
#include <Tactility/SystemEvents.h>
#include <Tactility/Tactility.h> #include <Tactility/Tactility.h>
#include <Tactility/hal/Configuration.h> #include <Tactility/hal/Configuration.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/hal/SdCard.h> #include <Tactility/hal/SdCard.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/hal/touch/TouchDevice.h> #include <Tactility/hal/touch/TouchDevice.h>
#include <Tactility/kernel/SystemEvents.h>
#include <tactility/check.h> #include <tactility/check.h>
#include <tactility/hal/Device.h> #include <tactility/hal/Device.h>
+2 -2
View File
@@ -1,11 +1,10 @@
#define LV_USE_PRIVATE_API 1 // For actual lv_obj_t declaration #define LV_USE_PRIVATE_API 1 // For actual lv_obj_t declaration
#include <Tactility/LogMessages.h> #include <Tactility/SystemEvents.h>
#include <Tactility/PubSub.h> #include <Tactility/PubSub.h>
#include <Tactility/RecursiveMutex.h> #include <Tactility/RecursiveMutex.h>
#include <Tactility/Tactility.h> #include <Tactility/Tactility.h>
#include <Tactility/Timer.h> #include <Tactility/Timer.h>
#include <Tactility/kernel/SystemEvents.h>
#include <Tactility/lvgl/LvglSync.h> #include <Tactility/lvgl/LvglSync.h>
#include <Tactility/lvgl/Statusbar.h> #include <Tactility/lvgl/Statusbar.h>
#include <Tactility/lvgl/Style.h> #include <Tactility/lvgl/Style.h>
@@ -17,6 +16,7 @@
#include <tactility/lvgl_module.h> #include <tactility/lvgl_module.h>
#include <lvgl.h> #include <lvgl.h>
#include <memory>
namespace tt::lvgl { namespace tt::lvgl {
+1 -1
View File
@@ -6,7 +6,7 @@
#include <memory> #include <memory>
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
#include <Tactility/kernel/SystemEvents.h> #include <Tactility/SystemEvents.h>
#include <Tactility/TactilityCore.h> #include <Tactility/TactilityCore.h>
#include <esp_netif_sntp.h> #include <esp_netif_sntp.h>
#include <esp_sntp.h> #include <esp_sntp.h>
+494 -1
View File
@@ -1,12 +1,30 @@
#include <Tactility/service/wifi/Wifi.h> #include <Tactility/service/wifi/Wifi.h>
#include <Tactility/CoreDefines.h> #include <Tactility/CoreDefines.h>
#include <tactility/check.h> #include <Tactility/LogMessages.h>
#include <Tactility/RecursiveMutex.h>
#include <Tactility/SystemEvents.h>
#include <Tactility/Tactility.h>
#include <Tactility/Timer.h>
#include <Tactility/service/Service.h>
#include <Tactility/service/ServiceManifest.h> #include <Tactility/service/ServiceManifest.h>
#include <Tactility/service/ServiceRegistration.h> #include <Tactility/service/ServiceRegistration.h>
#include <Tactility/service/wifi/WifiBootSplashInit.h>
#include <Tactility/service/wifi/WifiGlobals.h>
#include <Tactility/service/wifi/WifiSettings.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/drivers/wifi.h>
#include <tactility/log.h>
#include <algorithm>
namespace tt::service::wifi { namespace tt::service::wifi {
constexpr auto* TAG = "WifiService";
constexpr auto AUTO_SCAN_INTERVAL = 10000; // ms
const char* radioStateToString(RadioState state) { const char* radioStateToString(RadioState state) {
switch (state) { switch (state) {
using enum RadioState; using enum RadioState;
@@ -32,4 +50,479 @@ std::shared_ptr<ServiceContext> findServiceContext() {
return findServiceContextById(manifest.id); return findServiceContextById(manifest.id);
} }
namespace {
// Everything below wraps a TactilityKernel WIFI_TYPE device: the driver owns
// the radio state, station state and scan results, this file only tracks the
// bits the kernel driver doesn't (in-flight connection target/credentials,
// auto-connect bookkeeping).
/** State lives for the entire process; only ever (re)initialized by onStart(). */
struct WifiServiceState {
Device* device = nullptr;
std::shared_ptr<PubSub<WifiEvent>> pubsub = std::make_shared<PubSub<WifiEvent>>();
RecursiveMutex mutex;
bool secureConnection = false;
bool pauseAutoConnect = false;
bool connectionTargetRemember = false;
settings::WifiApSettings connectionTarget;
uint16_t scanRecordLimit = TT_WIFI_SCAN_RECORD_LIMIT;
TickType_t lastScanTime = kernel::MAX_TICKS;
std::unique_ptr<Timer> autoConnectTimer;
kernel::SystemEventSubscription bootEventSubscription = kernel::NoSystemEventSubscription;
};
WifiServiceState state;
bool started = false;
void onWifiDeviceEvent(Device* device, void* context, ::WifiEvent event);
// ---- Helpers ----
void publish(WifiEvent event) {
state.pubsub->publish(event);
} }
void publishRadioState(WifiRadioState radio_state) {
WifiEvent event = {};
event.type = WIFI_EVENT_TYPE_RADIO_STATE_CHANGED;
event.radio_state = radio_state;
publish(event);
}
RadioState combineRadioState(WifiRadioState radio, WifiStationState station) {
switch (radio) {
case WIFI_RADIO_STATE_OFF: return RadioState::Off;
case WIFI_RADIO_STATE_ON_PENDING: return RadioState::OnPending;
case WIFI_RADIO_STATE_OFF_PENDING: return RadioState::OffPending;
case WIFI_RADIO_STATE_ON:
switch (station) {
case WIFI_STATION_STATE_CONNECTION_PENDING: return RadioState::ConnectionPending;
case WIFI_STATION_STATE_CONNECTED: return RadioState::ConnectionActive;
case WIFI_STATION_STATE_DISCONNECTED: default: return RadioState::On;
}
}
return RadioState::Off;
}
// ---- Dispatched work (runs on the main task) ----
void dispatchSetEnabled(bool enabled) {
LOG_I(TAG, "dispatchSetEnabled(%d)", (int)enabled);
if (!started || state.device == nullptr) return;
bool ready = device_is_ready(state.device);
if (enabled == ready) {
LOG_W(TAG, "Can't enable/disable from current state");
return;
}
if (enabled) {
publishRadioState(WIFI_RADIO_STATE_ON_PENDING);
if (device_start(state.device) != ERROR_NONE) {
LOG_E(TAG, "Failed to start WiFi device");
publishRadioState(WIFI_RADIO_STATE_OFF);
return;
}
if (wifi_add_event_callback(state.device, nullptr, onWifiDeviceEvent) != ERROR_NONE) {
LOG_E(TAG, "Failed to register WiFi event callback");
device_stop(state.device);
publishRadioState(WIFI_RADIO_STATE_OFF);
return;
}
state.pauseAutoConnect = false;
state.lastScanTime = 0;
publishRadioState(WIFI_RADIO_STATE_ON);
} else {
publishRadioState(WIFI_RADIO_STATE_OFF_PENDING);
if (device_stop(state.device) != ERROR_NONE) {
LOG_E(TAG, "Failed to stop WiFi device");
publishRadioState(WIFI_RADIO_STATE_ON);
return;
}
wifi_remove_event_callback(state.device, onWifiDeviceEvent);
state.secureConnection = false;
publishRadioState(WIFI_RADIO_STATE_OFF);
}
}
void dispatchScan() {
LOG_I(TAG, "dispatchScan()");
if (!started || state.device == nullptr || !device_is_ready(state.device)) return;
state.lastScanTime = kernel::getTicks();
error_t result = wifi_scan(state.device);
if (result != ERROR_NONE) {
LOG_I(TAG, "Can't start scan (%s)", error_to_string(result));
}
}
void dispatchConnect() {
LOG_I(TAG, "dispatchConnect()");
if (!started || state.device == nullptr) return;
settings::WifiApSettings target;
{
auto lock = state.mutex.asScopedLock();
if (!lock.lock(50 / portTICK_PERIOD_MS)) {
LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "dispatchConnect()");
return;
}
target = state.connectionTarget;
}
LOG_I(TAG, "Connecting to %s", target.ssid.c_str());
error_t result = wifi_station_connect(state.device, target.ssid.c_str(), target.password.c_str(), target.channel);
if (result != ERROR_NONE) {
LOG_E(TAG, "Failed to connect to %s (%s)", target.ssid.c_str(), error_to_string(result));
WifiEvent event = {};
event.type = WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT;
// The driver couldn't even initiate the connection attempt; there's no
// more specific WifiStationConnectionError for that.
event.connection_error = WIFI_STATION_CONNECTION_ERROR_TIMEOUT;
publish(event);
}
// On success, WIFI_EVENT_TYPE_STATION_STATE_CHANGED / _CONNECTION_RESULT arrive
// asynchronously via onWifiDeviceEvent().
}
void dispatchDisconnect() {
LOG_I(TAG, "dispatchDisconnect()");
if (!started || state.device == nullptr) return;
error_t result = wifi_station_disconnect(state.device);
if (result != ERROR_NONE) {
LOG_E(TAG, "Failed to disconnect (%s)", error_to_string(result));
}
// The Disconnected event arrives asynchronously via onWifiDeviceEvent().
}
bool findAutoConnectAp(settings::WifiApSettings& out) {
for (const auto& record : getScanResults()) {
if (settings::contains(record.ssid)) {
settings::WifiApSettings loaded;
if (settings::load(record.ssid, loaded)) {
if (loaded.autoConnect) {
out = loaded;
return true;
}
} else {
LOG_E(TAG, "Failed to load credentials for ssid %s", record.ssid);
}
}
}
return false;
}
void dispatchAutoConnect() {
LOG_I(TAG, "dispatchAutoConnect()");
if (state.pauseAutoConnect) {
// A manual disconnect() or an in-progress manual connect() has paused
// auto-connect. This is called on every SCAN_FINISHED, not just the
// auto-connect timer's own scans (e.g. WifiManage re-scans on show),
// so it must honor the pause instead of reconnecting unconditionally.
return;
}
RadioState radio_state = getRadioState();
if (radio_state == RadioState::ConnectionActive || radio_state == RadioState::ConnectionPending) {
// Already connected (or connecting): reconnecting to the same AP would just
// force a pointless disconnect/reconnect blip, e.g. when WifiManage's
// on-show scan finishes while we're already on the saved auto-connect AP.
return;
}
settings::WifiApSettings target;
if (findAutoConnectAp(target)) {
LOG_I(TAG, "Auto-connecting to %s", target.ssid.c_str());
connect(target, false);
// connect() pauses auto-connect (it assumes a manual/user call); undo that
// since this call was automatic.
state.pauseAutoConnect = false;
}
}
bool shouldScanForAutoConnect() {
bool radio_scannable = getRadioState() == RadioState::On && !isScanning() && !state.pauseAutoConnect;
if (!radio_scannable) return false;
TickType_t current_time = kernel::getTicks();
bool scan_time_has_looped = current_time < state.lastScanTime;
bool no_recent_scan = (current_time - state.lastScanTime) > (AUTO_SCAN_INTERVAL / portTICK_PERIOD_MS);
return scan_time_has_looped || no_recent_scan;
}
void onAutoConnectTimer() {
if (!started || state.device == nullptr) return;
if (shouldScanForAutoConnect()) {
getMainDispatcher().dispatch([] { dispatchScan(); });
}
}
// ---- Kernel driver event bridge ----
void onWifiDeviceEvent(Device* /*device*/, void* /*context*/, ::WifiEvent event) {
switch (event.type) {
case WIFI_EVENT_TYPE_SCAN_FINISHED:
getMainDispatcher().dispatch([] { dispatchAutoConnect(); });
break;
case WIFI_EVENT_TYPE_STATION_STATE_CHANGED:
if (event.station_state == WIFI_STATION_STATE_DISCONNECTED) {
// Don't touch pauseAutoConnect here: a deliberate disconnect() sets it
// and relies on it staying set until a new connection is established.
// Resetting it on every disconnect (including deliberate ones) would
// let auto-connect immediately reconnect the user. Attempts that fail
// while pending are unpaused via WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT below.
kernel::publishSystemEvent(kernel::SystemEvent::NetworkDisconnected);
}
break;
case WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT:
if (event.connection_error == WIFI_STATION_CONNECTION_ERROR_NONE) {
settings::WifiApSettings target;
bool remember;
{
auto lock = state.mutex.asScopedLock();
if (lock.lock(50 / portTICK_PERIOD_MS)) {
target = state.connectionTarget;
remember = state.connectionTargetRemember;
state.secureConnection = !target.password.empty();
} else {
remember = false;
}
}
{
auto lock = state.mutex.asScopedLock();
if (lock.lock(50 / portTICK_PERIOD_MS)) {
state.pauseAutoConnect = false;
}
}
LOG_I(TAG, "Connected to %s", target.ssid.c_str());
if (remember && !settings::save(target)) {
LOG_E(TAG, "Failed to store credentials");
}
kernel::publishSystemEvent(kernel::SystemEvent::NetworkConnected);
} else {
// The pending connection attempt (which paused auto-connect via connect())
// failed; unpause so auto-connect can try other saved APs.
auto lock = state.mutex.asScopedLock();
if (lock.lock(50 / portTICK_PERIOD_MS)) {
state.pauseAutoConnect = false;
}
}
break;
default:
break;
}
// Forward the event as-is: subscribers inspect event.type and the
// relevant union field directly, same as this function does.
publish(event);
}
} // namespace
// region Public functions
std::shared_ptr<PubSub<WifiEvent>> getPubsub() {
return state.pubsub;
}
RadioState getRadioState() {
if (!started || state.device == nullptr || !device_is_ready(state.device)) {
return RadioState::Off;
}
WifiRadioState radio = WIFI_RADIO_STATE_OFF;
WifiStationState station = WIFI_STATION_STATE_DISCONNECTED;
wifi_get_radio_state(state.device, &radio);
wifi_get_station_state(state.device, &station);
return combineRadioState(radio, station);
}
std::string getConnectionTarget() {
RadioState radio_state = getRadioState();
if (radio_state != RadioState::ConnectionPending && radio_state != RadioState::ConnectionActive) {
return "";
}
char ssid[33] = {};
if (wifi_station_get_target_ssid(state.device, ssid) != ERROR_NONE) {
return "";
}
return { ssid };
}
void scan() {
LOG_I(TAG, "scan()");
if (!started || state.device == nullptr) return;
getMainDispatcher().dispatch([] { dispatchScan(); });
}
bool isScanning() {
if (!started || state.device == nullptr) return false;
return wifi_is_scanning(state.device);
}
void connect(const settings::WifiApSettings& ap, bool remember) {
LOG_I(TAG, "connect(%s, %d)", ap.ssid.c_str(), (int)remember);
if (!started || state.device == nullptr) return;
bool radio_off;
{
auto lock = state.mutex.asScopedLock();
if (!lock.lock(10 / portTICK_PERIOD_MS)) {
LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED);
return;
}
// Stop auto-connecting until the connection is established.
state.pauseAutoConnect = true;
state.connectionTarget = ap;
state.connectionTargetRemember = remember;
radio_off = !device_is_ready(state.device);
}
getMainDispatcher().dispatch([radio_off] {
if (radio_off) {
dispatchSetEnabled(true);
}
dispatchConnect();
});
}
void disconnect() {
LOG_I(TAG, "disconnect()");
if (!started || state.device == nullptr) return;
{
auto lock = state.mutex.asScopedLock();
if (!lock.lock(10 / portTICK_PERIOD_MS)) {
LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED);
return;
}
state.connectionTarget = settings::WifiApSettings("", "");
// Manual disconnect (e.g. via app) should stop auto-connecting until a new connection is established.
state.pauseAutoConnect = true;
}
getMainDispatcher().dispatch([] { dispatchDisconnect(); });
}
void setScanRecords(uint16_t records) {
LOG_I(TAG, "setScanRecords(%u)", records);
if (!started) return;
auto lock = state.mutex.asScopedLock();
if (lock.lock(10 / portTICK_PERIOD_MS)) {
state.scanRecordLimit = records;
}
}
std::vector<WifiApRecord> getScanResults() {
std::vector<WifiApRecord> records;
if (!started || state.device == nullptr) return records;
records.resize(state.scanRecordLimit);
size_t count = records.size();
if (wifi_get_scan_results(state.device, records.data(), &count) != ERROR_NONE) {
records.clear();
return records;
}
records.resize(count);
return records;
}
void setEnabled(bool enabled) {
LOG_I(TAG, "setEnabled(%d)", (int)enabled);
if (!started || state.device == nullptr) return;
getMainDispatcher().dispatch([enabled] { dispatchSetEnabled(enabled); });
}
bool isConnectionSecure() {
return state.secureConnection;
}
int getRssi() {
if (!started || state.device == nullptr) return 1;
int32_t rssi = 0;
if (wifi_station_get_rssi(state.device, &rssi) == ERROR_NONE) {
return rssi;
}
return 1;
}
std::string getIp() {
if (!started || state.device == nullptr) return "";
char ipv4[16] = {};
if (wifi_station_get_ipv4_address(state.device, ipv4) != ERROR_NONE) {
return "";
}
return { ipv4 };
}
// endregion Public functions
namespace {
class WifiService final : public Service {
public:
bool onStart(ServiceContext& /*service*/) override {
check(!started);
state.device = wifi_find_first_registered_device();
if (state.device == nullptr) {
LOG_W(TAG, "No WiFi device found");
}
state.bootEventSubscription = kernel::subscribeSystemEvent(kernel::SystemEvent::BootSplash, [](auto) {
bootSplashInit();
});
auto timer_interval = std::min(2000, AUTO_SCAN_INTERVAL);
state.autoConnectTimer = std::make_unique<Timer>(Timer::Type::Periodic, timer_interval, [] { onAutoConnectTimer(); });
// We want to try and scan more often in case of startup or scan lock failure.
state.autoConnectTimer->start();
started = true;
return true;
}
void onStop(ServiceContext& /*service*/) override {
check(started);
started = false;
state.autoConnectTimer->stop();
state.autoConnectTimer = nullptr; // Must release as it holds a reference via its callback.
kernel::unsubscribeSystemEvent(state.bootEventSubscription);
state.bootEventSubscription = kernel::NoSystemEventSubscription;
if (state.device != nullptr && device_is_ready(state.device)) {
wifi_remove_event_callback(state.device, onWifiDeviceEvent);
device_stop(state.device);
}
state.secureConnection = false;
state.pauseAutoConnect = false;
state.device = nullptr;
}
};
} // namespace
extern const ServiceManifest manifest = {
.id = "wifi",
.createService = create<WifiService>
};
} // namespace tt::service::wifi
-967
View File
@@ -1,967 +0,0 @@
#ifdef ESP_PLATFORM
#include <sdkconfig.h>
#endif
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#include <Tactility/service/wifi/Wifi.h>
#include <Tactility/LogMessages.h>
#include <Tactility/EventGroup.h>
#include <Tactility/RecursiveMutex.h>
#include <Tactility/Tactility.h>
#include <Tactility/Timer.h>
#include <Tactility/kernel/SystemEvents.h>
#include <Tactility/service/ServiceContext.h>
#include <Tactility/service/wifi/WifiBootSplashInit.h>
#include <Tactility/service/wifi/WifiGlobals.h>
#include <Tactility/service/wifi/WifiSettings.h>
#include <tactility/check.h>
#include <tactility/log.h>
#include <esp_wifi_default.h>
#include <lwip/esp_netif_net_stack.h>
#include <freertos/FreeRTOS.h>
#include <atomic>
#include <cstring>
#include <sys/cdefs.h>
namespace tt::service::wifi {
constexpr auto* TAG = "WifiService";
constexpr auto WIFI_CONNECTED_BIT = BIT0;
constexpr auto WIFI_FAIL_BIT = BIT1;
constexpr auto AUTO_SCAN_INTERVAL = 10000; // ms
// Forward declarations
class Wifi;
static void scan_list_free_safely(std::shared_ptr<Wifi> wifi);
// Methods for main thread dispatcher
static void dispatchAutoConnect(std::shared_ptr<Wifi> wifi);
static void dispatchEnable(std::shared_ptr<Wifi> wifi);
static void dispatchDisable(std::shared_ptr<Wifi> wifi);
static void dispatchScan(std::shared_ptr<Wifi> wifi);
static void dispatchConnect(std::shared_ptr<Wifi> wifi);
static void dispatchDisconnectButKeepActive(std::shared_ptr<Wifi> wifi);
class Wifi {
std::atomic<RadioState> radio_state = RadioState::Off;
std::atomic<bool> scan_active = false;
std::atomic<bool> secure_connection = false;
public:
/** @brief Locking mechanism for modifying the Wifi instance */
RecursiveMutex radioMutex;
RecursiveMutex dataMutex;
std::unique_ptr<Timer> autoConnectTimer;
/** @brief The public event bus */
std::shared_ptr<PubSub<WifiEvent>> pubsub = std::make_shared<PubSub<WifiEvent>>();
// TODO: Deal with messages that come in while an action is ongoing
// for example: when scanning and you turn off the radio, the scan should probably stop or turning off
// the radio should disable the on/off button in the app as it is pending.
/** @brief The network interface when wifi is started */
esp_netif_t* netif = nullptr;
/** @brief Scanning results */
wifi_ap_record_t* scan_list = nullptr;
/** @brief The current item count in scan_list (-1 when scan_list is NULL) */
uint16_t scan_list_count = 0;
/** @brief Maximum amount of records to scan (value > 0) */
uint16_t scan_list_limit = TT_WIFI_SCAN_RECORD_LIMIT;
/** @brief when we last requested a scan. Loops around every 50 days. */
TickType_t last_scan_time = kernel::MAX_TICKS;
esp_event_handler_instance_t event_handler_any_id = nullptr;
esp_event_handler_instance_t event_handler_got_ip = nullptr;
EventGroup connection_wait_flags;
settings::WifiApSettings connection_target;
bool pause_auto_connect = false; // Pause when manually disconnecting until manually connecting again
bool connection_target_remember = false; // Whether to store the connection_target on successful connection or not
esp_netif_ip_info_t ip_info;
kernel::SystemEventSubscription bootEventSubscription = kernel::NoSystemEventSubscription;
RadioState getRadioState() const {
return radio_state;
}
void setRadioState(RadioState newState) {
radio_state = newState;
}
bool isScanning() const {
return scan_active;
}
void setScanning(bool newState) {
scan_active = newState;
}
bool isScanActive() const {
return scan_active;
}
void setScanActive(bool newState) {
scan_active = newState;
}
bool isSecureConnection() const {
return secure_connection;
}
void setSecureConnection(bool newState) {
secure_connection = newState;
}
};
static std::shared_ptr<Wifi> wifi_singleton;
// region Public functions
std::shared_ptr<PubSub<WifiEvent>> getPubsub() {
auto wifi = wifi_singleton;
if (wifi == nullptr) {
check(false, "Service not running");
}
return wifi->pubsub;
}
RadioState getRadioState() {
auto wifi = wifi_singleton;
if (wifi != nullptr) {
return wifi->getRadioState();
} else {
return RadioState::Off;
}
}
std::string getConnectionTarget() {
auto wifi = wifi_singleton;
if (wifi == nullptr) {
return "";
}
RadioState state = wifi->getRadioState();
if (
state != RadioState::ConnectionPending &&
state != RadioState::ConnectionActive
) {
return "";
}
return wifi->connection_target.ssid;
}
void scan() {
LOG_I(TAG, "scan()");
auto wifi = wifi_singleton;
if (wifi == nullptr) {
return;
}
getMainDispatcher().dispatch([wifi]() { dispatchScan(wifi); });
}
bool isScanning() {
auto wifi = wifi_singleton;
if (wifi == nullptr) {
return false;
} else {
return wifi->isScanActive();
}
}
void connect(const settings::WifiApSettings& ap, bool remember) {
LOG_I(TAG, "connect(%s, %d)", ap.ssid.c_str(), (int)remember);
auto wifi = wifi_singleton;
if (wifi == nullptr) {
return;
}
auto lock = wifi->dataMutex.asScopedLock();
if (!lock.lock(10 / portTICK_PERIOD_MS)) {
return;
}
// Stop auto-connecting until the connection is established
wifi->pause_auto_connect = true;
wifi->connection_target = ap;
wifi->connection_target_remember = remember;
if (wifi->getRadioState() == RadioState::Off) {
getMainDispatcher().dispatch([wifi] { dispatchEnable(wifi); });
}
getMainDispatcher().dispatch([wifi] { dispatchConnect(wifi); });
}
void disconnect() {
LOG_I(TAG, "disconnect()");
auto wifi = wifi_singleton;
if (wifi == nullptr) {
return;
}
auto lock = wifi->dataMutex.asScopedLock();
if (!lock.lock(10 / portTICK_PERIOD_MS)) {
return;
}
wifi->connection_target = settings::WifiApSettings("", "");
// Manual disconnect (e.g. via app) should stop auto-connecting until a new connection is established
wifi->pause_auto_connect = true;
getMainDispatcher().dispatch([wifi]() { dispatchDisconnectButKeepActive(wifi); });
}
void clearIp() {
auto wifi = wifi_singleton;
if (wifi == nullptr) {
return;
}
auto lock = wifi->dataMutex.asScopedLock();
if (!lock.lock(10 / portTICK_PERIOD_MS)) {
return;
}
memset(&wifi->ip_info, 0, sizeof(esp_netif_ip_info_t));
}
void setScanRecords(uint16_t records) {
LOG_I(TAG, "setScanRecords(%u)", records);
auto wifi = wifi_singleton;
if (wifi == nullptr) {
return;
}
auto lock = wifi->dataMutex.asScopedLock();
if (!lock.lock(10 / portTICK_PERIOD_MS)) {
return;
}
if (records != wifi->scan_list_limit) {
scan_list_free_safely(wifi);
wifi->scan_list_limit = records;
}
}
std::vector<ApRecord> getScanResults() {
LOG_I(TAG, "getScanResults()");
auto wifi = wifi_singleton;
std::vector<ApRecord> records;
if (wifi == nullptr) {
return records;
}
auto lock = wifi->dataMutex.asScopedLock();
if (!lock.lock(10 / portTICK_PERIOD_MS)) {
return records;
}
if (wifi->scan_list_count > 0) {
uint16_t i = 0;
for (; i < wifi->scan_list_count; ++i) {
const auto& scanned_item = wifi->scan_list[i];
records.push_back((ApRecord) {
.ssid = reinterpret_cast<const char*>(scanned_item.ssid),
.rssi = scanned_item.rssi,
.channel = scanned_item.primary,
.auth_mode = scanned_item.authmode
});
}
}
return records;
}
void setEnabled(bool enabled) {
LOG_I(TAG, "setEnabled(%d)", (int)enabled);
auto wifi = wifi_singleton;
if (wifi == nullptr) {
return;
}
auto lock = wifi->dataMutex.asScopedLock();
if (!lock.lock(10 / portTICK_PERIOD_MS)) {
return;
}
if (enabled) {
getMainDispatcher().dispatch([wifi] { dispatchEnable(wifi); });
} else {
getMainDispatcher().dispatch([wifi] { dispatchDisable(wifi); });
}
wifi->pause_auto_connect = false;
wifi->last_scan_time = 0;
}
bool isConnectionSecure() {
auto wifi = wifi_singleton;
if (wifi == nullptr) {
return false;
}
return wifi->isSecureConnection();
}
int getRssi() {
assert(wifi_singleton);
static int rssi = 0;
if (esp_wifi_sta_get_rssi(&rssi) == ESP_OK) {
return rssi;
} else {
return 1;
}
}
// endregion Public functions
static void scan_list_alloc(std::shared_ptr<Wifi> wifi) {
auto lock = wifi->dataMutex.asScopedLock();
if (lock.lock()) {
assert(wifi->scan_list == nullptr);
wifi->scan_list = static_cast<wifi_ap_record_t*>(malloc(sizeof(wifi_ap_record_t) * wifi->scan_list_limit));
wifi->scan_list_count = 0;
}
}
static void scan_list_alloc_safely(std::shared_ptr<Wifi> wifi) {
auto lock = wifi->dataMutex.asScopedLock();
if (lock.lock()) {
if (wifi->scan_list == nullptr) {
scan_list_alloc(wifi);
}
}
}
static void scan_list_free(std::shared_ptr<Wifi> wifi) {
auto lock = wifi->dataMutex.asScopedLock();
if (lock.lock()) {
assert(wifi->scan_list != nullptr);
free(wifi->scan_list);
wifi->scan_list = nullptr;
wifi->scan_list_count = 0;
}
}
static void scan_list_free_safely(std::shared_ptr<Wifi> wifi) {
auto lock = wifi->dataMutex.asScopedLock();
if (lock.lock()) {
if (wifi->scan_list != nullptr) {
scan_list_free(wifi);
}
}
}
static void publish_event(std::shared_ptr<Wifi> wifi, WifiEvent event) {
auto lock = wifi->dataMutex.asScopedLock();
if (lock.lock()) {
wifi->pubsub->publish(event);
}
}
static bool copy_scan_list(std::shared_ptr<Wifi> wifi) {
auto state = wifi->getRadioState();
bool can_fetch_results = (state == RadioState::On || state == RadioState::ConnectionActive) &&
wifi->isScanActive();
if (!can_fetch_results) {
LOG_I(TAG, "Skip scan result fetching");
return false;
}
auto lock = wifi->dataMutex.asScopedLock();
if (!lock.lock()) {
return false;
}
// Create scan list if it does not exist
scan_list_alloc_safely(wifi);
wifi->scan_list_count = 0;
uint16_t record_count = wifi->scan_list_limit;
esp_err_t scan_result = esp_wifi_scan_get_ap_records(&record_count, wifi->scan_list);
if (scan_result == ESP_OK) {
uint16_t safe_record_count = std::min(wifi->scan_list_limit, record_count);
wifi->scan_list_count = safe_record_count;
LOG_I(TAG, "Scanned %u APs. Showing %u:", record_count, safe_record_count);
for (uint16_t i = 0; i < safe_record_count; i++) {
wifi_ap_record_t* record = &wifi->scan_list[i];
if (record->ssid[0] != 0 && record->primary != 0) {
LOG_I(TAG, " - SSID %s, RSSI %d, channel %u, BSSID %02x:%02x:%02x:%02x:%02x:%02x",
reinterpret_cast<const char*>(record->ssid),
record->rssi,
record->primary,
record->bssid[0],
record->bssid[1],
record->bssid[2],
record->bssid[3],
record->bssid[4],
record->bssid[5]
);
} else {
LOG_I(TAG, " - (missing channel info)"); // Behaviour on on P4 with C6
}
}
return true;
} else {
LOG_I(TAG, "Failed to get scanned records: %s", esp_err_to_name(scan_result));
return false;
}
}
static bool find_auto_connect_ap(std::shared_ptr<Wifi> wifi, settings::WifiApSettings& settings) {
LOG_I(TAG, "find_auto_connect_ap()");
auto lock = wifi->dataMutex.asScopedLock();
if (lock.lock(10 / portTICK_PERIOD_MS)) {
for (int i = 0; i < wifi->scan_list_count; ++i) {
auto ssid = reinterpret_cast<const char*>(wifi->scan_list[i].ssid);
if (settings::contains(ssid)) {
static_assert(sizeof(wifi->scan_list[i].ssid) == (TT_WIFI_SSID_LIMIT + 1), "SSID size mismatch");
if (settings::load(ssid, settings)) {
if (settings.autoConnect) {
return true;
}
} else {
LOG_E(TAG, "Failed to load credentials for ssid %s", ssid);
}
break;
}
}
}
return false;
}
static void dispatchAutoConnect(std::shared_ptr<Wifi> wifi) {
LOG_I(TAG, "dispatchAutoConnect()");
settings::WifiApSettings settings;
if (find_auto_connect_ap(wifi, settings)) {
LOG_I(TAG, "Auto-connecting to %s", settings.ssid.c_str());
connect(settings, false);
// TODO: We currently have to manually reset it because connect() sets it.
// connect() assumes it's only being called by the user and not internally, so it disables auto-connect
wifi->pause_auto_connect = false;
}
}
static void eventHandler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
auto wifi = wifi_singleton;
if (wifi == nullptr) {
LOG_E(TAG, "eventHandler: no wifi instance");
return;
}
if (event_base == WIFI_EVENT) {
LOG_I(TAG, "eventHandler: WIFI_EVENT %d", (int)event_id);
} else if (event_base == IP_EVENT) {
LOG_I(TAG, "eventHandler: IP_EVENT %d", (int)event_id);
}
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
LOG_I(TAG, "eventHandler: STA_START");
if (wifi->getRadioState() == RadioState::ConnectionPending) {
esp_wifi_connect();
}
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
LOG_I(TAG, "eventHandler: STA_DISCONNECTED");
clearIp();
switch (wifi->getRadioState()) {
case RadioState::ConnectionPending:
wifi->connection_wait_flags.set(WIFI_FAIL_BIT);
break;
case RadioState::On:
// Ensure we can reconnect again
wifi->pause_auto_connect = false;
break;
default:
break;
}
wifi->setRadioState(RadioState::On);
publish_event(wifi, WifiEvent::Disconnected);
kernel::publishSystemEvent(kernel::SystemEvent::NetworkDisconnected);
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
auto* event = static_cast<ip_event_got_ip_t*>(event_data);
memcpy(&wifi->ip_info, &event->ip_info, sizeof(esp_netif_ip_info_t));
LOG_I(TAG, "eventHandler: got ip: %d.%d.%d.%d", IP2STR(&event->ip_info.ip));
if (wifi->getRadioState() == RadioState::ConnectionPending) {
wifi->connection_wait_flags.set(WIFI_CONNECTED_BIT);
// We resume auto-connecting only when there was an explicit request by the user for the connection
// TODO: Make thread-safe
wifi->pause_auto_connect = false; // Resume auto-connection
}
kernel::publishSystemEvent(kernel::SystemEvent::NetworkConnected);
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) {
auto* event = static_cast<wifi_event_sta_scan_done_t*>(event_data);
LOG_I(TAG, "eventHandler: wifi scanning done (scan id %u)", event->scan_id);
bool copied_list = copy_scan_list(wifi);
auto state = wifi->getRadioState();
if (
state != RadioState::Off &&
state != RadioState::OffPending
) {
wifi->setScanActive(false);
esp_wifi_scan_stop();
}
publish_event(wifi_singleton, WifiEvent::ScanFinished);
LOG_I(TAG, "eventHandler: Finished scan");
if (copied_list && wifi_singleton->getRadioState() == RadioState::On && !wifi->pause_auto_connect) {
getMainDispatcher().dispatch([wifi]() { dispatchAutoConnect(wifi); });
}
}
}
static void dispatchEnable(std::shared_ptr<Wifi> wifi) {
LOG_I(TAG, "dispatchEnable()");
RadioState state = wifi->getRadioState();
if (
state == RadioState::On ||
state == RadioState::OnPending ||
state == RadioState::OffPending
) {
LOG_W(TAG, "Can't enable from current state");
return;
}
auto lock = wifi->radioMutex.asScopedLock();
if (lock.lock(50 / portTICK_PERIOD_MS)) {
LOG_I(TAG, "Enabling");
wifi->setRadioState(RadioState::OnPending);
publish_event(wifi, WifiEvent::RadioStateOnPending);
if (wifi->netif != nullptr) {
esp_wifi_clear_default_wifi_driver_and_handlers(wifi->netif);
esp_netif_destroy(wifi->netif);
}
wifi->netif = esp_netif_create_default_wifi_sta();
// Warning: this is the memory-intensive operation
// It uses over 117kB of RAM with default settings for S3 on IDF v5.1.2
wifi_init_config_t config = WIFI_INIT_CONFIG_DEFAULT();
esp_err_t init_result = esp_wifi_init(&config);
if (init_result != ESP_OK) {
LOG_E(TAG, "Wifi init failed");
if (init_result == ESP_ERR_NO_MEM) {
LOG_E(TAG, "Insufficient memory");
}
wifi->setRadioState(RadioState::Off);
publish_event(wifi, WifiEvent::RadioStateOff);
return;
}
esp_wifi_set_storage(WIFI_STORAGE_RAM);
// TODO: don't crash on check failure
ESP_ERROR_CHECK(esp_event_handler_instance_register(
WIFI_EVENT,
ESP_EVENT_ANY_ID,
&eventHandler,
nullptr,
&wifi->event_handler_any_id
));
// TODO: don't crash on check failure
ESP_ERROR_CHECK(esp_event_handler_instance_register(
IP_EVENT,
IP_EVENT_STA_GOT_IP,
&eventHandler,
nullptr,
&wifi->event_handler_got_ip
));
if (esp_wifi_set_mode(WIFI_MODE_STA) != ESP_OK) {
LOG_E(TAG, "Wifi mode setting failed");
wifi->setRadioState(RadioState::Off);
esp_wifi_deinit();
publish_event(wifi, WifiEvent::RadioStateOff);
return;
}
esp_err_t start_result = esp_wifi_start();
if (start_result != ESP_OK) {
LOG_E(TAG, "Wifi start failed");
if (start_result == ESP_ERR_NO_MEM) {
LOG_E(TAG, "Insufficient memory");
}
wifi->setRadioState(RadioState::Off);
esp_wifi_set_mode(WIFI_MODE_NULL);
esp_wifi_deinit();
publish_event(wifi, WifiEvent::RadioStateOff);
return;
}
wifi->setRadioState(RadioState::On);
publish_event(wifi, WifiEvent::RadioStateOn);
wifi->pause_auto_connect = false;
LOG_I(TAG, "Enabled");
} else {
LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED);
}
}
static void dispatchDisable(std::shared_ptr<Wifi> wifi) {
LOG_I(TAG, "dispatchDisable()");
auto lock = wifi->radioMutex.asScopedLock();
if (!lock.lock(50 / portTICK_PERIOD_MS)) {
LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "disable()");
return;
}
RadioState state = wifi->getRadioState();
if (
state == RadioState::Off ||
state == RadioState::OffPending ||
state == RadioState::OnPending
) {
LOG_W(TAG, "Can't disable from current state");
return;
}
LOG_I(TAG, "Disabling");
wifi->setRadioState(RadioState::OffPending);
publish_event(wifi, WifiEvent::RadioStateOffPending);
// Free up scan list memory
scan_list_free_safely(wifi_singleton);
// Detach netif from the internal WiFi event handlers before stopping.
// Those handlers call esp_netif_action_stop on WIFI_EVENT_STA_STOP, which
// queues esp_netif_stop_api to the lwIP thread. esp_netif_destroy later
// queues a second one; the first zeroes lwip_netif, and the second then
// crashes in netif_ip6_addr_set_parts (null pointer + 0x263 offset).
if (wifi->netif != nullptr) {
esp_wifi_clear_default_wifi_driver_and_handlers(wifi->netif);
}
// Note: handlers are already detached above, so we cannot safely return to
// RadioState::On from here — the netif would be missing its default WiFi
// event handlers and subsequent disable attempts would behave incorrectly.
// If stop fails, continue the teardown anyway so we end in a clean Off state.
if (esp_wifi_stop() != ESP_OK) {
LOG_E(TAG, "Failed to stop radio - continuing teardown");
}
if (esp_wifi_set_mode(WIFI_MODE_NULL) != ESP_OK) {
LOG_E(TAG, "Failed to unset mode");
}
if (esp_event_handler_instance_unregister(
WIFI_EVENT,
ESP_EVENT_ANY_ID,
wifi->event_handler_any_id
) != ESP_OK) {
LOG_E(TAG, "Failed to unregister id event handler");
}
if (esp_event_handler_instance_unregister(
IP_EVENT,
IP_EVENT_STA_GOT_IP,
wifi->event_handler_got_ip
) != ESP_OK) {
LOG_E(TAG, "Failed to unregister ip event handler");
}
if (esp_wifi_deinit() != ESP_OK) {
LOG_E(TAG, "Failed to deinit");
}
assert(wifi->netif != nullptr);
esp_netif_destroy(wifi->netif);
wifi->netif = nullptr;
wifi->setScanActive(false);
wifi->setRadioState(RadioState::Off);
publish_event(wifi, WifiEvent::RadioStateOff);
LOG_I(TAG, "Disabled");
}
static void dispatchScan(std::shared_ptr<Wifi> wifi) {
LOG_I(TAG, "dispatchScan()");
auto lock = wifi->radioMutex.asScopedLock();
if (!lock.lock(10 / portTICK_PERIOD_MS)) {
LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED);
return;
}
RadioState state = wifi->getRadioState();
if (state != RadioState::On && state != RadioState::ConnectionActive && state != RadioState::ConnectionPending) {
LOG_W(TAG, "Scan unavailable: wifi not enabled");
return;
}
if (wifi->isScanActive()) {
LOG_W(TAG, "Scan already pending");
return;
}
// TODO: Thread safety
wifi->last_scan_time = tt::kernel::getTicks();
if (esp_wifi_scan_start(nullptr, false) != ESP_OK) {
LOG_I(TAG, "Can't start scan");
return;
}
LOG_I(TAG, "Starting scan");
wifi->setScanActive(true);
publish_event(wifi, WifiEvent::ScanStarted);
}
static void dispatchConnect(std::shared_ptr<Wifi> wifi) {
LOG_I(TAG, "dispatchConnect()");
auto lock = wifi->radioMutex.asScopedLock();
if (!lock.lock(50 / portTICK_PERIOD_MS)) {
LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "dispatchConnect()");
return;
}
LOG_I(TAG, "Connecting to %s", wifi->connection_target.ssid.c_str());
// Stop radio first, if needed
RadioState radio_state = wifi->getRadioState();
if (
radio_state == RadioState::On ||
radio_state == RadioState::ConnectionActive ||
radio_state == RadioState::ConnectionPending
) {
LOG_I(TAG, "Connecting: Stopping radio first");
esp_err_t stop_result = esp_wifi_stop();
wifi->setScanActive(false);
if (stop_result != ESP_OK) {
LOG_E(TAG, "Connecting: Failed to disconnect (%s)", esp_err_to_name(stop_result));
return;
}
}
wifi->setScanActive(false);
wifi->setRadioState(RadioState::ConnectionPending);
publish_event(wifi, WifiEvent::ConnectionPending);
wifi_config_t config;
memset(&config, 0, sizeof(wifi_config_t));
config.sta.channel = wifi_singleton->connection_target.channel;
config.sta.scan_method = WIFI_FAST_SCAN;
config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL;
config.sta.threshold.rssi = -127;
config.sta.pmf_cfg.capable = true;
memcpy(config.sta.ssid, wifi_singleton->connection_target.ssid.c_str(), wifi_singleton->connection_target.ssid.size());
if (wifi_singleton->connection_target.password[0] != 0x00) {
memcpy(config.sta.password, wifi_singleton->connection_target.password.c_str(), wifi_singleton->connection_target.password.size());
config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
}
LOG_I(TAG, "esp_wifi_set_config()");
esp_err_t set_config_result = esp_wifi_set_config(WIFI_IF_STA, &config);
if (set_config_result != ESP_OK) {
wifi->setRadioState(RadioState::On);
LOG_E(TAG, "Failed to set wifi config (%s)", esp_err_to_name(set_config_result));
publish_event(wifi, WifiEvent::ConnectionFailed);
return;
}
LOG_I(TAG, "esp_wifi_start()");
esp_err_t wifi_start_result = esp_wifi_start();
if (wifi_start_result != ESP_OK) {
wifi->setRadioState(RadioState::On);
LOG_E(TAG, "Failed to start wifi to begin connecting (%s)", esp_err_to_name(wifi_start_result));
publish_event(wifi, WifiEvent::ConnectionFailed);
return;
}
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT)
* or connection failed for the maximum number of re-tries (WIFI_FAIL_BIT).
* The bits are set by wifi_event_handler() */
uint32_t flags;
if (wifi_singleton->connection_wait_flags.wait(WIFI_FAIL_BIT | WIFI_CONNECTED_BIT, false, true, &flags, kernel::MAX_TICKS)) {
LOG_I(TAG, "Waiting for EventGroup by event_handler()");
if (flags & WIFI_CONNECTED_BIT) {
wifi->setSecureConnection(config.sta.password[0] != 0x00U);
wifi->setRadioState(RadioState::ConnectionActive);
publish_event(wifi, WifiEvent::ConnectionSuccess);
LOG_I(TAG, "Connected to %s", wifi->connection_target.ssid.c_str());
if (wifi->connection_target_remember) {
if (!settings::save(wifi->connection_target)) {
LOG_E(TAG, "Failed to store credentials");
} else {
LOG_I(TAG, "Stored credentials");
}
}
} else if (flags & WIFI_FAIL_BIT) {
wifi->setRadioState(RadioState::On);
publish_event(wifi, WifiEvent::ConnectionFailed);
LOG_I(TAG, "Failed to connect to %s", wifi->connection_target.ssid.c_str());
} else {
wifi->setRadioState(RadioState::On);
publish_event(wifi, WifiEvent::ConnectionFailed);
LOG_E(TAG, "UNEXPECTED EVENT");
}
wifi_singleton->connection_wait_flags.clear(WIFI_FAIL_BIT | WIFI_CONNECTED_BIT);
}
}
static void dispatchDisconnectButKeepActive(std::shared_ptr<Wifi> wifi) {
LOG_I(TAG, "dispatchDisconnectButKeepActive()");
auto lock = wifi->radioMutex.asScopedLock();
if (!lock.lock(50 / portTICK_PERIOD_MS)) {
LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED);
return;
}
esp_err_t stop_result = esp_wifi_stop();
if (stop_result != ESP_OK) {
LOG_E(TAG, "Failed to disconnect (%s)", esp_err_to_name(stop_result));
return;
}
wifi_config_t config;
memset(&config, 0, sizeof(wifi_config_t));
config.sta.channel = wifi_singleton->connection_target.channel;
config.sta.scan_method = WIFI_ALL_CHANNEL_SCAN;
config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL;
config.sta.threshold.rssi = -127;
config.sta.pmf_cfg.capable = true;
esp_err_t set_config_result = esp_wifi_set_config(WIFI_IF_STA, &config);
if (set_config_result != ESP_OK) {
// TODO: disable radio, because radio state is in limbo between off and on
wifi->setRadioState(RadioState::Off);
LOG_E(TAG, "failed to set wifi config (%s)", esp_err_to_name(set_config_result));
publish_event(wifi, WifiEvent::RadioStateOff);
return;
}
esp_err_t wifi_start_result = esp_wifi_start();
if (wifi_start_result != ESP_OK) {
// TODO: disable radio, because radio state is in limbo between off and on
wifi->setRadioState(RadioState::Off);
LOG_E(TAG, "failed to start wifi to begin connecting (%s)", esp_err_to_name(wifi_start_result));
publish_event(wifi, WifiEvent::RadioStateOff);
return;
}
wifi->setRadioState(RadioState::On);
publish_event(wifi, WifiEvent::Disconnected);
LOG_I(TAG, "Disconnected");
}
static bool shouldScanForAutoConnect(std::shared_ptr<Wifi> wifi) {
auto lock = wifi->dataMutex.asScopedLock();
if (!lock.lock(100 / portTICK_PERIOD_MS)) {
return false;
}
bool is_radio_in_scannable_state = wifi->getRadioState() == RadioState::On &&
!wifi->isScanActive() &&
!wifi->pause_auto_connect;
if (!is_radio_in_scannable_state) {
return false;
}
TickType_t current_time = kernel::getTicks();
bool scan_time_has_looped = (current_time < wifi->last_scan_time);
bool no_recent_scan = (current_time - wifi->last_scan_time) > (AUTO_SCAN_INTERVAL / portTICK_PERIOD_MS);
if (!scan_time_has_looped && !no_recent_scan) {
}
return scan_time_has_looped || no_recent_scan;
}
void onAutoConnectTimer() {
auto wifi = std::static_pointer_cast<Wifi>(wifi_singleton);
// Automatic scanning is done so we can automatically connect to access points
bool should_auto_scan = shouldScanForAutoConnect(wifi);
if (should_auto_scan) {
getMainDispatcher().dispatch([wifi]() { dispatchScan(wifi); });
}
}
std::string getIp() {
auto wifi = std::static_pointer_cast<Wifi>(wifi_singleton);
if (wifi == nullptr) return "127.0.0.1";
auto lock = wifi->dataMutex.asScopedLock();
if (!lock.lock(100 / portTICK_PERIOD_MS)) {
return "127.0.0.1";
}
return std::format("{}.{}.{}.{}", IP2STR(&wifi->ip_info.ip));
}
class WifiService final : public Service {
public:
bool onStart(ServiceContext& service) override {
assert(wifi_singleton == nullptr);
wifi_singleton = std::make_shared<Wifi>();
wifi_singleton->bootEventSubscription = kernel::subscribeSystemEvent(kernel::SystemEvent::BootSplash, [](auto) {
bootSplashInit();
});
auto timer_interval = std::min(2000, AUTO_SCAN_INTERVAL);
wifi_singleton->autoConnectTimer = std::make_unique<Timer>(Timer::Type::Periodic, timer_interval, [] { onAutoConnectTimer(); });
// We want to try and scan more often in case of startup or scan lock failure
wifi_singleton->autoConnectTimer->start();
return true;
}
void onStop(ServiceContext& service) override {
auto wifi = wifi_singleton;
assert(wifi != nullptr);
RadioState state = wifi->getRadioState();
if (state != RadioState::Off) {
dispatchDisable(wifi);
}
wifi->autoConnectTimer->stop();
wifi->autoConnectTimer = nullptr; // Must release as it holds a reference to this Wifi instance
// Acquire all mutexes
wifi->dataMutex.lock();
wifi->radioMutex.lock();
// Detach
wifi_singleton = nullptr;
// Release mutexes
wifi->dataMutex.unlock();
wifi->radioMutex.unlock();
// Release (hopefully) last Wifi instance by scope
}
};
extern const ServiceManifest manifest = {
.id = "wifi",
.createService = create<WifiService>
};
} // namespace
#endif // CONFIG_SOC_WIFI_SUPPORTED or CONFIG_SLAVE_SOC_WIFI_SUPPORTED
-168
View File
@@ -1,168 +0,0 @@
#ifdef ESP_PLATFORM
#include <sdkconfig.h>
#endif
#if not defined(CONFIG_SOC_WIFI_SUPPORTED) && not defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#include <Tactility/service/wifi/Wifi.h>
#include <Tactility/PubSub.h>
#include <Tactility/RecursiveMutex.h>
#include <tactility/check.h>
#include <Tactility/service/Service.h>
#include <Tactility/service/ServiceManifest.h>
namespace tt::service::wifi {
struct Wifi {
/** @brief Locking mechanism for modifying the Wifi instance */
RecursiveMutex mutex;
/** @brief The public event bus */
std::shared_ptr<PubSub<WifiEvent>> pubsub = std::make_shared<PubSub<WifiEvent>>();
/** @brief The internal message queue */
bool scan_active = false;
bool secure_connection = false;
RadioState radio_state = RadioState::ConnectionActive;
};
static Wifi* wifi = nullptr;
// region Static
static void publish_event(WifiEvent event) {
wifi->pubsub->publish(event);
}
// endregion Static
// region Public functions
std::shared_ptr<PubSub<WifiEvent>> getPubsub() {
assert(wifi);
return wifi->pubsub;
}
RadioState getRadioState() {
return wifi->radio_state;
}
std::string getConnectionTarget() {
return "Home Wifi";
}
void scan() {
assert(wifi);
wifi->scan_active = false; // TODO: enable and then later disable automatically
}
bool isScanning() {
assert(wifi);
return wifi->scan_active;
}
void connect(const settings::WifiApSettings& ap, bool remember) {
assert(wifi);
// TODO: implement
}
void disconnect() {
assert(wifi);
}
void setScanRecords(uint16_t records) {
assert(wifi);
// TODO: implement
}
std::vector<ApRecord> getScanResults() {
check(wifi);
std::vector<ApRecord> records;
records.push_back((ApRecord) {
.ssid = "Home Wifi",
.rssi = -30,
.channel = 0,
.auth_mode = WIFI_AUTH_WPA2_PSK
});
records.push_back((ApRecord) {
.ssid = "No place like 127.0.0.1",
.rssi = -67,
.channel = 0,
.auth_mode = WIFI_AUTH_WPA2_PSK
});
records.push_back((ApRecord) {
.ssid = "Pretty fly for a Wi-Fi",
.rssi = -70,
.channel = 0,
.auth_mode = WIFI_AUTH_WPA2_PSK
});
records.push_back((ApRecord) {
.ssid = "An AP with a really, really long name",
.rssi = -80,
.channel = 0,
.auth_mode = WIFI_AUTH_WPA2_PSK
});
records.push_back((ApRecord) {
.ssid = "Bad Reception",
.rssi = -90,
.channel = 0,
.auth_mode = WIFI_AUTH_OPEN
});
return records;
}
void setEnabled(bool enabled) {
assert(wifi != nullptr);
if (enabled) {
wifi->radio_state = RadioState::On;
wifi->secure_connection = true;
} else {
wifi->radio_state = RadioState::Off;
}
}
bool isConnectionSecure() {
return wifi->secure_connection;
}
int getRssi() {
if (wifi->radio_state == RadioState::ConnectionActive) {
return -30;
} else {
return 0;
}
}
std::string getIp() {
return "192.168.1.2";
}
// endregion Public functions
class WifiService final : public Service {
public:
bool onStart(ServiceContext& service) override {
check(wifi == nullptr);
wifi = new Wifi();
return true;
}
void onStop(ServiceContext& service) override {
check(wifi != nullptr);
delete wifi;
wifi = nullptr;
}
};
extern const ServiceManifest manifest = {
.id = "wifi",
.createService = create<WifiService>
};
} // namespace
#endif // ESP_PLATFORM
+1 -1
View File
@@ -1,6 +1,6 @@
#include <Tactility/settings/Time.h> #include <Tactility/settings/Time.h>
#include <Tactility/kernel/SystemEvents.h> #include <Tactility/SystemEvents.h>
#include <Tactility/Preferences.h> #include <Tactility/Preferences.h>
#include <Tactility/settings/SystemSettings.h> #include <Tactility/settings/SystemSettings.h>
-74
View File
@@ -1,74 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#define TT_WIFI_SSID_LIMIT 32 // 32 characters/octets, according to IEEE 802.11-2020 spec
#define TT_WIFI_CREDENTIALS_PASSWORD_LIMIT 64 // 64 characters/octets, according to IEEE 802.11-2020 spec
#ifdef __cplusplus
extern "C" {
#endif
/** Important: These values must map to tt::service::wifi::RadioState values exactly */
typedef enum {
WifiRadioStateOnPending,
WifiRadioStateOn,
WifiRadioStateConnectionPending,
WifiRadioStateConnectionActive,
WifiRadioStateOffPending,
WifiRadioStateOff,
} WifiRadioState;
/** @return the state of the WiFi radio */
WifiRadioState tt_wifi_get_radio_state();
/** @return a textual representation of the WiFi radio state */
const char* tt_wifi_radio_state_to_string(WifiRadioState state);
/** Start scanning */
void tt_wifi_scan();
/** @return true if a scan is active/pending */
bool tt_wifi_is_scanning();
/**
* Return the WiFi SSID that the system tries to connect to, or is connected to.
* @param[out] buffer an allocated string buffer. Its size must be (WIFI_SSID_LIMIT + 1).
*/
void tt_wifi_get_connection_target(char* buffer);
/**
* @brief Enable/disable the radio. Ignores input if desired state matches current state.
* @param[in] enabled
*/
void tt_wifi_set_enabled(bool enabled);
/**
*
* @param ssid The access point identifier - maximal 32 characters/octets
* @param password the password - maximum 64 characters/octets
* @param channel 0 means "any"
* @param autoConnect whether we want to automatically reconnect if a disconnect occurs
* @param remember whether the record should be stored permanently on the device (it is only stored if this connection attempt succeeds)
*/
void tt_wifi_connect(const char* ssid, const char* password, int32_t channel, bool autoConnect, bool remember);
/**
* If WiFi is connected, this disconnects it.
*/
void tt_wifi_disconnect();
/**
* @return true if WiFi is active and encrypted
*/
bool tt_wifi_is_connnection_secure();
/**
* @return the current radio connection link quality
*/
int tt_wifi_get_rssi();
#ifdef __cplusplus
}
#endif
-11
View File
@@ -16,7 +16,6 @@
#include "tt_lvgl_toolbar.h" #include "tt_lvgl_toolbar.h"
#include "tt_preferences.h" #include "tt_preferences.h"
#include "tt_time.h" #include "tt_time.h"
#include "tt_wifi.h"
#include "symbols/cplusplus.h" #include "symbols/cplusplus.h"
#include "symbols/esp_event.h" #include "symbols/esp_event.h"
@@ -360,16 +359,6 @@ const esp_elfsym main_symbols[] {
ESP_ELFSYM_EXPORT(tt_timezone_get_code), ESP_ELFSYM_EXPORT(tt_timezone_get_code),
ESP_ELFSYM_EXPORT(tt_timezone_is_format_24_hour), ESP_ELFSYM_EXPORT(tt_timezone_is_format_24_hour),
ESP_ELFSYM_EXPORT(tt_timezone_set_format_24_hour), ESP_ELFSYM_EXPORT(tt_timezone_set_format_24_hour),
ESP_ELFSYM_EXPORT(tt_wifi_get_radio_state),
ESP_ELFSYM_EXPORT(tt_wifi_radio_state_to_string),
ESP_ELFSYM_EXPORT(tt_wifi_scan),
ESP_ELFSYM_EXPORT(tt_wifi_is_scanning),
ESP_ELFSYM_EXPORT(tt_wifi_get_connection_target),
ESP_ELFSYM_EXPORT(tt_wifi_set_enabled),
ESP_ELFSYM_EXPORT(tt_wifi_connect),
ESP_ELFSYM_EXPORT(tt_wifi_disconnect),
ESP_ELFSYM_EXPORT(tt_wifi_is_connnection_secure),
ESP_ELFSYM_EXPORT(tt_wifi_get_rssi),
// tt::lvgl // tt::lvgl
ESP_ELFSYM_EXPORT(tt_lvgl_spinner_create), ESP_ELFSYM_EXPORT(tt_lvgl_spinner_create),
-56
View File
@@ -1,56 +0,0 @@
#include "tt_wifi.h"
#include <cstring>
#include <Tactility/service/wifi/Wifi.h>
#include <Tactility/service/wifi/WifiSettings.h>
using namespace tt::service;
extern "C" {
WifiRadioState tt_wifi_get_radio_state() {
return static_cast<WifiRadioState>(wifi::getRadioState());
}
const char* tt_wifi_radio_state_to_string(WifiRadioState state) {
return wifi::radioStateToString(static_cast<wifi::RadioState>(state));
}
void tt_wifi_scan() {
wifi::scan();
}
bool tt_wifi_is_scanning() {
return wifi::isScanning();
}
void tt_wifi_get_connection_target(char* buffer) {
auto target = wifi::getConnectionTarget();
strcpy(buffer, target.c_str());
}
void tt_wifi_set_enabled(bool enabled) {
wifi::setEnabled(enabled);
}
void tt_wifi_connect(const char* ssid, const char* password, int32_t channel, bool autoConnect, bool remember) {
wifi::settings::WifiApSettings settings;
settings.ssid = ssid;
settings.password = password;
settings.channel = channel;
settings.autoConnect = autoConnect;
wifi::connect(settings, remember);
}
void tt_wifi_disconnect() {
wifi::disconnect();
}
bool tt_wifi_is_connnection_secure() {
return wifi::isConnectionSecure();
}
int tt_wifi_get_rssi() {
return wifi::getRssi();
}
}
+3 -2
View File
@@ -18,7 +18,8 @@ namespace tt {
class Thread final { class Thread final {
static constexpr size_t LOCAL_STORAGE_SELF_POINTER_INDEX = 0; // Slot 0 is reserved by ESP-IDF's pthread API (see esp_wifi/lwip use of pthread_getspecific).
static constexpr size_t LOCAL_STORAGE_SELF_POINTER_INDEX = 1;
public: public:
@@ -70,7 +71,7 @@ private:
ESP_LOGI(TAG, "Stopped %s", thread->name.c_str()); ESP_LOGI(TAG, "Stopped %s", thread->name.c_str());
#endif #endif
vTaskSetThreadLocalStoragePointer(nullptr, 0, nullptr); vTaskSetThreadLocalStoragePointer(nullptr, LOCAL_STORAGE_SELF_POINTER_INDEX, nullptr);
thread->taskHandle = nullptr; thread->taskHandle = nullptr;
vTaskDelete(nullptr); vTaskDelete(nullptr);

Some files were not shown because too many files have changed in this diff Show More