diff --git a/Buildscripts/sdkconfig/default.properties b/Buildscripts/sdkconfig/default.properties index 4bb2689b8..f61a09ffa 100644 --- a/Buildscripts/sdkconfig/default.properties +++ b/Buildscripts/sdkconfig/default.properties @@ -26,6 +26,9 @@ CONFIG_LV_USE_WIN=n CONFIG_LV_USE_SNAPSHOT=y CONFIG_LV_BUILD_EXAMPLES=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_TASK_NOTIFICATION_ARRAY_ENTRIES=2 CONFIG_FREERTOS_SMP=n diff --git a/Devices/btt-panda-touch/bigtreetech,panda-touch.dts b/Devices/btt-panda-touch/bigtreetech,panda-touch.dts index 2fedb9396..10bca414e 100644 --- a/Devices/btt-panda-touch/bigtreetech,panda-touch.dts +++ b/Devices/btt-panda-touch/bigtreetech,panda-touch.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -11,6 +12,11 @@ compatible = "root"; model = "BigTreeTech Panda Touch"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/cyd-2432s024c/cyd,2432s024c.dts b/Devices/cyd-2432s024c/cyd,2432s024c.dts index 621aaca5d..5d14f02d9 100644 --- a/Devices/cyd-2432s024c/cyd,2432s024c.dts +++ b/Devices/cyd-2432s024c/cyd,2432s024c.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -11,6 +12,11 @@ compatible = "root"; model = "CYD 2432S024C"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <40>; diff --git a/Devices/cyd-2432s024r/cyd,2432s024r.dts b/Devices/cyd-2432s024r/cyd,2432s024r.dts index db1aaed33..6e3953d6d 100644 --- a/Devices/cyd-2432s024r/cyd,2432s024r.dts +++ b/Devices/cyd-2432s024r/cyd,2432s024r.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -12,6 +13,11 @@ compatible = "root"; model = "CYD 2432S024R"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <40>; diff --git a/Devices/cyd-2432s028r/cyd,2432s028r.dts b/Devices/cyd-2432s028r/cyd,2432s028r.dts index 8d27feee2..87e451588 100644 --- a/Devices/cyd-2432s028r/cyd,2432s028r.dts +++ b/Devices/cyd-2432s028r/cyd,2432s028r.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -13,6 +14,11 @@ compatible = "root"; model = "CYD 2432S028R"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <40>; diff --git a/Devices/cyd-2432s028rv3/cyd,2432s028rv3.dts b/Devices/cyd-2432s028rv3/cyd,2432s028rv3.dts index 492dba5cb..096799359 100644 --- a/Devices/cyd-2432s028rv3/cyd,2432s028rv3.dts +++ b/Devices/cyd-2432s028rv3/cyd,2432s028rv3.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -13,6 +14,11 @@ compatible = "root"; model = "CYD 2432S028R v3"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <40>; diff --git a/Devices/cyd-2432s032c/Source/Configuration.cpp b/Devices/cyd-2432s032c/Source/Configuration.cpp index 52aaab007..326040665 100644 --- a/Devices/cyd-2432s032c/Source/Configuration.cpp +++ b/Devices/cyd-2432s032c/Source/Configuration.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include diff --git a/Devices/cyd-2432s032c/cyd,2432s032c.dts b/Devices/cyd-2432s032c/cyd,2432s032c.dts index bd0f62bfd..4dbc0c74d 100644 --- a/Devices/cyd-2432s032c/cyd,2432s032c.dts +++ b/Devices/cyd-2432s032c/cyd,2432s032c.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -11,6 +12,11 @@ compatible = "root"; model = "CYD 2432S032C"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <40>; diff --git a/Devices/cyd-3248s035c/cyd,3248s035c.dts b/Devices/cyd-3248s035c/cyd,3248s035c.dts index 2f71a5cf5..3edce5d2b 100644 --- a/Devices/cyd-3248s035c/cyd,3248s035c.dts +++ b/Devices/cyd-3248s035c/cyd,3248s035c.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -12,6 +13,11 @@ compatible = "root"; model = "CYD 3248S035C"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <40>; diff --git a/Devices/cyd-4848s040c/cyd,4848s040c.dts b/Devices/cyd-4848s040c/cyd,4848s040c.dts index 96e313742..3bf2a00eb 100644 --- a/Devices/cyd-4848s040c/cyd,4848s040c.dts +++ b/Devices/cyd-4848s040c/cyd,4848s040c.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -11,6 +12,11 @@ compatible = "root"; model = "CYD 4848S040C"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/cyd-8048s043c/cyd,8048s043c.dts b/Devices/cyd-8048s043c/cyd,8048s043c.dts index 7afe6c003..68087ce95 100644 --- a/Devices/cyd-8048s043c/cyd,8048s043c.dts +++ b/Devices/cyd-8048s043c/cyd,8048s043c.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -12,6 +13,11 @@ compatible = "root"; model = "CYD 8048S043C"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/cyd-e32r28t/cyd,e32r28t.dts b/Devices/cyd-e32r28t/cyd,e32r28t.dts index be36a8dac..2c1f66a97 100644 --- a/Devices/cyd-e32r28t/cyd,e32r28t.dts +++ b/Devices/cyd-e32r28t/cyd,e32r28t.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -11,6 +12,11 @@ compatible = "root"; model = "CYD E32R28T"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <40>; diff --git a/Devices/cyd-e32r32p/cyd,e32r32p.dts b/Devices/cyd-e32r32p/cyd,e32r32p.dts index af6ade973..3ada265f2 100644 --- a/Devices/cyd-e32r32p/cyd,e32r32p.dts +++ b/Devices/cyd-e32r32p/cyd,e32r32p.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -12,6 +13,11 @@ compatible = "root"; model = "CYD E32R32P"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <40>; diff --git a/Devices/elecrow-crowpanel-advance-28/elecrow,crowpanel-advance-28.dts b/Devices/elecrow-crowpanel-advance-28/elecrow,crowpanel-advance-28.dts index 4be2c09fb..b365f905d 100644 --- a/Devices/elecrow-crowpanel-advance-28/elecrow,crowpanel-advance-28.dts +++ b/Devices/elecrow-crowpanel-advance-28/elecrow,crowpanel-advance-28.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -13,6 +14,11 @@ compatible = "root"; model = "Elecrow CrowPanel Advance 2.8"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/elecrow-crowpanel-advance-35/elecrow,crowpanel-advance-35.dts b/Devices/elecrow-crowpanel-advance-35/elecrow,crowpanel-advance-35.dts index 3214a8f51..87b6ab5dc 100644 --- a/Devices/elecrow-crowpanel-advance-35/elecrow,crowpanel-advance-35.dts +++ b/Devices/elecrow-crowpanel-advance-35/elecrow,crowpanel-advance-35.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -13,6 +14,11 @@ compatible = "root"; model = "Elecrow CrowPanel Advance 3.5"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/elecrow-crowpanel-advance-50/elecrow,crowpanel-advance-50.dts b/Devices/elecrow-crowpanel-advance-50/elecrow,crowpanel-advance-50.dts index 35710b68d..7024ca43f 100644 --- a/Devices/elecrow-crowpanel-advance-50/elecrow,crowpanel-advance-50.dts +++ b/Devices/elecrow-crowpanel-advance-50/elecrow,crowpanel-advance-50.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -12,6 +13,11 @@ compatible = "root"; model = "Elecrow CrowPanel Advance 5.0"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/elecrow-crowpanel-basic-28/elecrow,crowpanel-basic-28.dts b/Devices/elecrow-crowpanel-basic-28/elecrow,crowpanel-basic-28.dts index 8ed72e950..b7d16a86b 100644 --- a/Devices/elecrow-crowpanel-basic-28/elecrow,crowpanel-basic-28.dts +++ b/Devices/elecrow-crowpanel-basic-28/elecrow,crowpanel-basic-28.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -13,6 +14,11 @@ compatible = "root"; model = "Elecrow CrowPanel Basic 2.8"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <40>; diff --git a/Devices/elecrow-crowpanel-basic-35/elecrow,crowpanel-basic-35.dts b/Devices/elecrow-crowpanel-basic-35/elecrow,crowpanel-basic-35.dts index dfd3d36e3..0417ba553 100644 --- a/Devices/elecrow-crowpanel-basic-35/elecrow,crowpanel-basic-35.dts +++ b/Devices/elecrow-crowpanel-basic-35/elecrow,crowpanel-basic-35.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -13,6 +14,11 @@ compatible = "root"; model = "Elecrow CrowPanel Basic 3.5"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <40>; diff --git a/Devices/elecrow-crowpanel-basic-50/elecrow,crowpanel-basic-50.dts b/Devices/elecrow-crowpanel-basic-50/elecrow,crowpanel-basic-50.dts index df64d2e8b..cf4bdc19f 100644 --- a/Devices/elecrow-crowpanel-basic-50/elecrow,crowpanel-basic-50.dts +++ b/Devices/elecrow-crowpanel-basic-50/elecrow,crowpanel-basic-50.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -12,6 +13,11 @@ compatible = "root"; model = "Elecrow CrowPanel Basic 5.0"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/generic-esp32/generic,esp32.dts b/Devices/generic-esp32/generic,esp32.dts index 65e17365d..3e49b6c50 100644 --- a/Devices/generic-esp32/generic,esp32.dts +++ b/Devices/generic-esp32/generic,esp32.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include @@ -8,6 +9,11 @@ compatible = "root"; model = "Generic ESP32"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/generic-esp32c6/generic,esp32c6.dts b/Devices/generic-esp32c6/generic,esp32c6.dts index 42618d62a..fef95675b 100644 --- a/Devices/generic-esp32c6/generic,esp32c6.dts +++ b/Devices/generic-esp32c6/generic,esp32c6.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include @@ -8,6 +9,11 @@ compatible = "root"; model = "Generic ESP32 C6"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <31>; diff --git a/Devices/generic-esp32s3/generic,esp32s3.dts b/Devices/generic-esp32s3/generic,esp32s3.dts index ed11aa7f6..8443fcf47 100644 --- a/Devices/generic-esp32s3/generic,esp32s3.dts +++ b/Devices/generic-esp32s3/generic,esp32s3.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include @@ -8,6 +9,11 @@ compatible = "root"; model = "Generic ESP32 S3"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/guition-jc2432w328c/guition,jc2432w328c.dts b/Devices/guition-jc2432w328c/guition,jc2432w328c.dts index 2b7c7ac99..0a76eaff6 100644 --- a/Devices/guition-jc2432w328c/guition,jc2432w328c.dts +++ b/Devices/guition-jc2432w328c/guition,jc2432w328c.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -12,6 +13,11 @@ compatible = "root"; model = "Guition JC2432W328C"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <40>; diff --git a/Devices/guition-jc3248w535c/guition,jc3248w535c.dts b/Devices/guition-jc3248w535c/guition,jc3248w535c.dts index b6364e01b..a3aabdcd9 100644 --- a/Devices/guition-jc3248w535c/guition,jc3248w535c.dts +++ b/Devices/guition-jc3248w535c/guition,jc3248w535c.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -14,6 +15,11 @@ compatible = "root"; model = "Guition JC3248W535C"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/guition-jc8048w550c/guition,jc8048w550c.dts b/Devices/guition-jc8048w550c/guition,jc8048w550c.dts index 714dcec7a..64c063463 100644 --- a/Devices/guition-jc8048w550c/guition,jc8048w550c.dts +++ b/Devices/guition-jc8048w550c/guition,jc8048w550c.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -13,6 +14,11 @@ compatible = "root"; model = "Guition JC8048W550C"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/heltec-wifi-lora-32-v3/heltec,wifi-lora-32-v3.dts b/Devices/heltec-wifi-lora-32-v3/heltec,wifi-lora-32-v3.dts index 7ea912c5b..4c5448a8c 100644 --- a/Devices/heltec-wifi-lora-32-v3/heltec,wifi-lora-32-v3.dts +++ b/Devices/heltec-wifi-lora-32-v3/heltec,wifi-lora-32-v3.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -9,6 +10,11 @@ compatible = "root"; model = "Heltec WiFi LoRa 32 V3"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/lilygo-tdeck-max/lilygo,tdeck-max.dts b/Devices/lilygo-tdeck-max/lilygo,tdeck-max.dts index bdccfce94..06dbb9db2 100644 --- a/Devices/lilygo-tdeck-max/lilygo,tdeck-max.dts +++ b/Devices/lilygo-tdeck-max/lilygo,tdeck-max.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -14,6 +15,11 @@ compatible = "root"; model = "LilyGO T-Deck Max"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; }; diff --git a/Devices/lilygo-tdeck/Source/Init.cpp b/Devices/lilygo-tdeck/Source/Init.cpp index d484c39b2..07ef9aca9 100644 --- a/Devices/lilygo-tdeck/Source/Init.cpp +++ b/Devices/lilygo-tdeck/Source/Init.cpp @@ -2,10 +2,10 @@ #include "devices/KeyboardBacklight.h" #include "devices/TrackballDevice.h" +#include +#include #include #include -#include -#include #include #include diff --git a/Devices/lilygo-tdeck/lilygo,tdeck.dts b/Devices/lilygo-tdeck/lilygo,tdeck.dts index 84c0b7919..37186d554 100644 --- a/Devices/lilygo-tdeck/lilygo,tdeck.dts +++ b/Devices/lilygo-tdeck/lilygo,tdeck.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -16,6 +17,11 @@ compatible = "root"; model = "LilyGO T-Deck"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/lilygo-tdisplay-s3/Source/Init.cpp b/Devices/lilygo-tdisplay-s3/Source/Init.cpp index 2dc55e5d6..e01fb78bd 100644 --- a/Devices/lilygo-tdisplay-s3/Source/Init.cpp +++ b/Devices/lilygo-tdisplay-s3/Source/Init.cpp @@ -2,7 +2,7 @@ #include "devices/Display.h" #include "PwmBacklight.h" -#include +#include #include #include diff --git a/Devices/lilygo-tdisplay-s3/lilygo,tdisplay-s3.dts b/Devices/lilygo-tdisplay-s3/lilygo,tdisplay-s3.dts index bb54df831..aa4236b74 100644 --- a/Devices/lilygo-tdisplay-s3/lilygo,tdisplay-s3.dts +++ b/Devices/lilygo-tdisplay-s3/lilygo,tdisplay-s3.dts @@ -2,12 +2,18 @@ #include #include +#include #include / { compatible = "root"; model = "LilyGO T-Display S3"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/lilygo-tdisplay/lilygo,tdisplay.dts b/Devices/lilygo-tdisplay/lilygo,tdisplay.dts index 10350088c..f940daeef 100644 --- a/Devices/lilygo-tdisplay/lilygo,tdisplay.dts +++ b/Devices/lilygo-tdisplay/lilygo,tdisplay.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -10,6 +11,11 @@ compatible = "root"; model = "LilyGO T-Display"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <49>; diff --git a/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts b/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts index 65151e66a..8cb047fa1 100644 --- a/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts +++ b/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -13,6 +14,11 @@ compatible = "root"; model = "LilyGO T-Dongle S3"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/lilygo-thmi/Source/Init.cpp b/Devices/lilygo-thmi/Source/Init.cpp index e6edc2623..e74fd553c 100644 --- a/Devices/lilygo-thmi/Source/Init.cpp +++ b/Devices/lilygo-thmi/Source/Init.cpp @@ -2,7 +2,7 @@ #include "devices/Display.h" #include "PwmBacklight.h" -#include +#include #include #include diff --git a/Devices/lilygo-thmi/lilygo,thmi.dts b/Devices/lilygo-thmi/lilygo,thmi.dts index a001369de..cb7768c18 100644 --- a/Devices/lilygo-thmi/lilygo,thmi.dts +++ b/Devices/lilygo-thmi/lilygo,thmi.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -13,6 +14,11 @@ compatible = "root"; model = "LilyGO T-HMI"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/lilygo-tlora-pager/Source/Init.cpp b/Devices/lilygo-tlora-pager/Source/Init.cpp index b5de9bc85..96e4ecf98 100644 --- a/Devices/lilygo-tlora-pager/Source/Init.cpp +++ b/Devices/lilygo-tlora-pager/Source/Init.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include diff --git a/Devices/lilygo-tlora-pager/lilygo,tlora-pager.dts b/Devices/lilygo-tlora-pager/lilygo,tlora-pager.dts index f13677613..af9c44b3b 100644 --- a/Devices/lilygo-tlora-pager/lilygo,tlora-pager.dts +++ b/Devices/lilygo-tlora-pager/lilygo,tlora-pager.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -15,6 +16,11 @@ compatible = "lilygo,tlora-pager"; model = "LilyGO T-Lora Pager"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/m5stack-cardputer-adv/m5stack,cardputer-adv.dts b/Devices/m5stack-cardputer-adv/m5stack,cardputer-adv.dts index 1c7fcedda..0a142a147 100644 --- a/Devices/m5stack-cardputer-adv/m5stack,cardputer-adv.dts +++ b/Devices/m5stack-cardputer-adv/m5stack,cardputer-adv.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -17,6 +18,11 @@ compatible = "root"; model = "M5Stack Cardputer Adv"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/m5stack-cardputer/m5stack,cardputer.dts b/Devices/m5stack-cardputer/m5stack,cardputer.dts index bbf188cea..39980fcdf 100644 --- a/Devices/m5stack-cardputer/m5stack,cardputer.dts +++ b/Devices/m5stack-cardputer/m5stack,cardputer.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -16,6 +17,11 @@ compatible = "root"; model = "M5Stack Cardputer"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/m5stack-core2/m5stack,core2.dts b/Devices/m5stack-core2/m5stack,core2.dts index 0d166c2e8..876912786 100644 --- a/Devices/m5stack-core2/m5stack,core2.dts +++ b/Devices/m5stack-core2/m5stack,core2.dts @@ -1,6 +1,7 @@ /dts-v1/; #include +#include #include #include #include @@ -17,6 +18,11 @@ compatible = "root"; model = "M5Stack Core2"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <40>; diff --git a/Devices/m5stack-cores3/m5stack,cores3.dts b/Devices/m5stack-cores3/m5stack,cores3.dts index 833fa1777..01e81260b 100644 --- a/Devices/m5stack-cores3/m5stack,cores3.dts +++ b/Devices/m5stack-cores3/m5stack,cores3.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -18,6 +19,11 @@ compatible = "root"; model = "M5Stack CoreS3"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/m5stack-papers3/m5stack,papers3.dts b/Devices/m5stack-papers3/m5stack,papers3.dts index 56963994d..e463fedd4 100644 --- a/Devices/m5stack-papers3/m5stack,papers3.dts +++ b/Devices/m5stack-papers3/m5stack,papers3.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -13,6 +14,11 @@ compatible = "root"; model = "M5Stack PaperS3"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/m5stack-stackchan/m5stack,stackchan.dts b/Devices/m5stack-stackchan/m5stack,stackchan.dts index 5d1b4ad09..98b678fc7 100644 --- a/Devices/m5stack-stackchan/m5stack,stackchan.dts +++ b/Devices/m5stack-stackchan/m5stack,stackchan.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -20,6 +21,11 @@ compatible = "root"; model = "M5Stack StackChan"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/m5stack-stickc-plus2/m5stack,stickc-plus2.dts b/Devices/m5stack-stickc-plus2/m5stack,stickc-plus2.dts index 5bfb45ebf..b783f05fd 100644 --- a/Devices/m5stack-stickc-plus2/m5stack,stickc-plus2.dts +++ b/Devices/m5stack-stickc-plus2/m5stack,stickc-plus2.dts @@ -1,5 +1,6 @@ /dts-v1/; #include +#include #include #include #include @@ -12,6 +13,11 @@ compatible = "root"; model = "M5Stack StickC Plus2"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + gpio0 { compatible = "espressif,esp32-gpio"; gpio-count = <40>; diff --git a/Devices/m5stack-sticks3/m5stack,sticks3.dts b/Devices/m5stack-sticks3/m5stack,sticks3.dts index 52b9deade..abe0f83e0 100644 --- a/Devices/m5stack-sticks3/m5stack,sticks3.dts +++ b/Devices/m5stack-sticks3/m5stack,sticks3.dts @@ -1,6 +1,7 @@ /dts-v1/; #include #include +#include #include #include #include @@ -15,6 +16,11 @@ compatible = "root"; model = "M5Stack StickS3"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/unphone/unphone.dts b/Devices/unphone/unphone.dts index 4b8dbd801..df4966b52 100644 --- a/Devices/unphone/unphone.dts +++ b/Devices/unphone/unphone.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -13,6 +14,11 @@ compatible = "root"; model = "unPhone"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/waveshare-esp32-s3-geek/waveshare,esp32-s3-geek.dts b/Devices/waveshare-esp32-s3-geek/waveshare,esp32-s3-geek.dts index ee6e05601..9b4b9e5d9 100644 --- a/Devices/waveshare-esp32-s3-geek/waveshare,esp32-s3-geek.dts +++ b/Devices/waveshare-esp32-s3-geek/waveshare,esp32-s3-geek.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -13,6 +14,11 @@ compatible = "root"; model = "Waveshare ESP32-S3-Geek"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/waveshare-s3-lcd-13/waveshare,s3-lcd-13.dts b/Devices/waveshare-s3-lcd-13/waveshare,s3-lcd-13.dts index e5b5b6739..8dcb3495a 100644 --- a/Devices/waveshare-s3-lcd-13/waveshare,s3-lcd-13.dts +++ b/Devices/waveshare-s3-lcd-13/waveshare,s3-lcd-13.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -14,6 +15,11 @@ compatible = "root"; model = "Waveshare S3 LCD 1.3"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/waveshare-s3-touch-lcd-128/waveshare,s3-touch-lcd-128.dts b/Devices/waveshare-s3-touch-lcd-128/waveshare,s3-touch-lcd-128.dts index 76f9f7701..ea6a295a8 100644 --- a/Devices/waveshare-s3-touch-lcd-128/waveshare,s3-touch-lcd-128.dts +++ b/Devices/waveshare-s3-touch-lcd-128/waveshare,s3-touch-lcd-128.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -14,6 +15,11 @@ compatible = "root"; model = "Waveshare S3 Touch LCD 1.28"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/waveshare-s3-touch-lcd-147/waveshare,s3-touch-lcd-147.dts b/Devices/waveshare-s3-touch-lcd-147/waveshare,s3-touch-lcd-147.dts index 95721df97..0f4b7613d 100644 --- a/Devices/waveshare-s3-touch-lcd-147/waveshare,s3-touch-lcd-147.dts +++ b/Devices/waveshare-s3-touch-lcd-147/waveshare,s3-touch-lcd-147.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -13,6 +14,11 @@ compatible = "root"; model = "Waveshare S3 Touch LCD 1.47"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/waveshare-s3-touch-lcd-43/waveshare,s3-touch-lcd-43.dts b/Devices/waveshare-s3-touch-lcd-43/waveshare,s3-touch-lcd-43.dts index eaff1c8d3..123bb3ab0 100644 --- a/Devices/waveshare-s3-touch-lcd-43/waveshare,s3-touch-lcd-43.dts +++ b/Devices/waveshare-s3-touch-lcd-43/waveshare,s3-touch-lcd-43.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -12,6 +13,11 @@ compatible = "root"; model = "Waveshare ESP32-S3-Touch-LCD-4.3"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Devices/wireless-tag-wt32-sc01-plus/wireless-tag,wt32-sc01-plus.dts b/Devices/wireless-tag-wt32-sc01-plus/wireless-tag,wt32-sc01-plus.dts index 4d67bc857..c32e41a07 100644 --- a/Devices/wireless-tag-wt32-sc01-plus/wireless-tag,wt32-sc01-plus.dts +++ b/Devices/wireless-tag-wt32-sc01-plus/wireless-tag,wt32-sc01-plus.dts @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -11,6 +12,11 @@ compatible = "root"; model = "Wireless-Tag WT32-SC01 Plus"; + wifi0 { + compatible = "espressif,esp32-wifi-pinned"; + status = "disabled"; + }; + ble0 { compatible = "espressif,esp32-ble"; status = "disabled"; diff --git a/Platforms/platform-esp32/CMakeLists.txt b/Platforms/platform-esp32/CMakeLists.txt index c655ff1ea..548e94fc3 100644 --- a/Platforms/platform-esp32/CMakeLists.txt +++ b/Platforms/platform-esp32/CMakeLists.txt @@ -6,7 +6,7 @@ idf_component_register( SRCS ${SOURCES} INCLUDE_DIRS "include/" 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) diff --git a/Platforms/platform-esp32/bindings/espressif,esp32-wifi-pinned.yaml b/Platforms/platform-esp32/bindings/espressif,esp32-wifi-pinned.yaml new file mode 100644 index 000000000..37dea6538 --- /dev/null +++ b/Platforms/platform-esp32/bindings/espressif,esp32-wifi-pinned.yaml @@ -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 diff --git a/Platforms/platform-esp32/bindings/espressif,esp32-wifi.yaml b/Platforms/platform-esp32/bindings/espressif,esp32-wifi.yaml new file mode 100644 index 000000000..a71136a8d --- /dev/null +++ b/Platforms/platform-esp32/bindings/espressif,esp32-wifi.yaml @@ -0,0 +1,8 @@ +description: ESP32 WiFi driver + +compatible: "espressif,esp32-wifi" + +properties: + _unused: + type: int + default: 0 diff --git a/Platforms/platform-esp32/include/tactility/bindings/esp32_wifi.h b/Platforms/platform-esp32/include/tactility/bindings/esp32_wifi.h new file mode 100644 index 000000000..215e7bd4b --- /dev/null +++ b/Platforms/platform-esp32/include/tactility/bindings/esp32_wifi.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(esp32_wifi, struct Esp32WifiConfig) + +#ifdef __cplusplus +} +#endif diff --git a/Platforms/platform-esp32/include/tactility/bindings/esp32_wifi_pinned.h b/Platforms/platform-esp32/include/tactility/bindings/esp32_wifi_pinned.h new file mode 100644 index 000000000..c49645a46 --- /dev/null +++ b/Platforms/platform-esp32/include/tactility/bindings/esp32_wifi_pinned.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(esp32_wifi_pinned, struct Esp32WifiPinnedConfig) + +#ifdef __cplusplus +} +#endif diff --git a/Platforms/platform-esp32/include/tactility/drivers/esp32_wifi.h b/Platforms/platform-esp32/include/tactility/drivers/esp32_wifi.h new file mode 100644 index 000000000..07083c9b1 --- /dev/null +++ b/Platforms/platform-esp32/include/tactility/drivers/esp32_wifi.h @@ -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 diff --git a/Platforms/platform-esp32/include/tactility/drivers/esp32_wifi_pinned.h b/Platforms/platform-esp32/include/tactility/drivers/esp32_wifi_pinned.h new file mode 100644 index 000000000..29e8a19bb --- /dev/null +++ b/Platforms/platform-esp32/include/tactility/drivers/esp32_wifi_pinned.h @@ -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 diff --git a/Platforms/platform-esp32/source/drivers/esp32_wifi.cpp b/Platforms/platform-esp32/source/drivers/esp32_wifi.cpp new file mode 100644 index 000000000..ccecbb590 --- /dev/null +++ b/Platforms/platform-esp32/source/drivers/esp32_wifi.cpp @@ -0,0 +1,567 @@ +#ifdef ESP_PLATFORM +#include +#endif + +#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED) + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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(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(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(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(*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(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(config.sta.ssid), ssid, sizeof(config.sta.ssid) - 1); + if (password != nullptr && password[0] != '\0') { + strncpy(reinterpret_cast(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 diff --git a/Platforms/platform-esp32/source/drivers/esp32_wifi_pinned.cpp b/Platforms/platform-esp32/source/drivers/esp32_wifi_pinned.cpp new file mode 100644 index 000000000..f83c86691 --- /dev/null +++ b/Platforms/platform-esp32/source/drivers/esp32_wifi_pinned.cpp @@ -0,0 +1,336 @@ +#ifdef ESP_PLATFORM +#include +#endif + +#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED) + +#include // for WIFI_TASK_CORE_ID + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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(device_get_driver_data(device))) + +struct MarshalledCall { + const std::function* work; + EventGroupHandle_t done; +}; + +void marshal_trampoline(void* context) { + auto* call = static_cast(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& 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(context)->running = false; +} + +int32_t pinned_thread_main(void* context) { + auto* ctx = static_cast(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 diff --git a/Platforms/platform-esp32/source/module.cpp b/Platforms/platform-esp32/source/module.cpp index 0bd3cfd30..f00c80858 100644 --- a/Platforms/platform-esp32/source/module.cpp +++ b/Platforms/platform-esp32/source/module.cpp @@ -22,6 +22,10 @@ extern Driver esp32_sdspi_driver; extern Driver esp32_spi_driver; extern Driver esp32_uart_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) extern Driver esp32_bluetooth_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_uart_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) check(driver_construct_add(&esp32_bluetooth_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() { /* 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 */ +#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 check(driver_remove_destruct(&esp32_usbhost_msc_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_usbhost_midi_driver) == ERROR_NONE); diff --git a/Platforms/platform-posix/source/drivers/mock_wifi.cpp b/Platforms/platform-posix/source/drivers/mock_wifi.cpp new file mode 100644 index 000000000..35855a8d6 --- /dev/null +++ b/Platforms/platform-posix/source/drivers/mock_wifi.cpp @@ -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 +#include +#include +#include +#include + +#include +#include +#include + +#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(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" diff --git a/Platforms/platform-posix/source/module.cpp b/Platforms/platform-posix/source/module.cpp index a61b078e6..5c83f7787 100644 --- a/Platforms/platform-posix/source/module.cpp +++ b/Platforms/platform-posix/source/module.cpp @@ -1,15 +1,19 @@ // SPDX-License-Identifier: Apache-2.0 +#include +#include #include extern "C" { +extern Driver posix_wifi_driver; + static error_t start() { - /* NO-OP for now */ + check(driver_construct_add(&posix_wifi_driver) == ERROR_NONE); return ERROR_NONE; } static error_t stop() { - /* NO-OP for now */ + check(driver_remove_destruct(&posix_wifi_driver) == ERROR_NONE); return ERROR_NONE; } diff --git a/Tactility/Include/Tactility/kernel/PanicHandler.h b/Tactility/Include/Tactility/PanicHandler.h similarity index 100% rename from Tactility/Include/Tactility/kernel/PanicHandler.h rename to Tactility/Include/Tactility/PanicHandler.h diff --git a/Tactility/Include/Tactility/kernel/Platform.h b/Tactility/Include/Tactility/Platform.h similarity index 100% rename from Tactility/Include/Tactility/kernel/Platform.h rename to Tactility/Include/Tactility/Platform.h diff --git a/Tactility/Include/Tactility/kernel/SystemEvents.h b/Tactility/Include/Tactility/SystemEvents.h similarity index 100% rename from Tactility/Include/Tactility/kernel/SystemEvents.h rename to Tactility/Include/Tactility/SystemEvents.h diff --git a/Tactility/Include/Tactility/Tactility.h b/Tactility/Include/Tactility/Tactility.h index 66b40257d..7078cb943 100644 --- a/Tactility/Include/Tactility/Tactility.h +++ b/Tactility/Include/Tactility/Tactility.h @@ -1,18 +1,45 @@ #pragma once +#include "tactility/concurrent/dispatcher.h" #include "tactility/device.h" #include "tactility/module.h" -#include #include #include #include +#include + extern "C" { struct DtsDevice; } 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; + + 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 * 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. * @return the dispatcher */ -Dispatcher& getMainDispatcher(); +MainDispatcher getMainDispatcher(); namespace hal { diff --git a/Tactility/Include/Tactility/kernel/SpiDeviceLock.h b/Tactility/Include/Tactility/kernel/SpiDeviceLock.h deleted file mode 100644 index ad3acd172..000000000 --- a/Tactility/Include/Tactility/kernel/SpiDeviceLock.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include -#include - -#include - -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); - } -}; - -} diff --git a/Tactility/Include/Tactility/network/HttpServer.h b/Tactility/Include/Tactility/network/HttpServer.h index 27dd8ee4d..1ae6e7ac7 100644 --- a/Tactility/Include/Tactility/network/HttpServer.h +++ b/Tactility/Include/Tactility/network/HttpServer.h @@ -1,9 +1,8 @@ #pragma once #ifdef ESP_PLATFORM -#include #include -#include +#include namespace tt::network { diff --git a/Tactility/Include/Tactility/service/wifi/Wifi.h b/Tactility/Include/Tactility/service/wifi/Wifi.h index 86a9a61ea..207ddb57f 100644 --- a/Tactility/Include/Tactility/service/wifi/Wifi.h +++ b/Tactility/Include/Tactility/service/wifi/Wifi.h @@ -6,52 +6,16 @@ #include #include -#include "WifiApSettings.h" +#include -#ifdef ESP_PLATFORM -#include -#else -#include -// 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 +#include "WifiApSettings.h" namespace tt::service::wifi { -enum class WifiEvent { - /** Radio was turned on */ - RadioStateOn, - /** Radio is turning on. */ - 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 -}; +/** The kernel wifi driver's event type: a WifiEventType tag plus a union + * payload (radio_state/station_state/access_point_state/connection_error + * depending on the tag). See tactility/drivers/wifi.h. */ +using WifiEvent = ::WifiEvent; enum class RadioState { OnPending, @@ -62,13 +26,6 @@ enum class RadioState { 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 * @return PubSub @@ -93,7 +50,7 @@ bool isScanning(); std::string getConnectionTarget(); /** @return the access points from the last scan (if any). It only contains public APs. */ -std::vector getScanResults(); +std::vector getScanResults(); /** * @brief Overrides the default scan result size of 16. diff --git a/Tactility/Private/Tactility/kernel/critical/Critical.h b/Tactility/Private/Tactility/Critical.h similarity index 100% rename from Tactility/Private/Tactility/kernel/critical/Critical.h rename to Tactility/Private/Tactility/Critical.h diff --git a/Tactility/Private/Tactility/app/wifimanage/State.h b/Tactility/Private/Tactility/app/wifimanage/State.h index f0a95bcd9..40ee84c02 100644 --- a/Tactility/Private/Tactility/app/wifimanage/State.h +++ b/Tactility/Private/Tactility/app/wifimanage/State.h @@ -14,7 +14,7 @@ class State final { bool scanning = false; bool scannedAfterRadioOn = false; service::wifi::RadioState radioState; - std::vector apRecords; + std::vector apRecords; std::string connectSsid; public: @@ -30,14 +30,14 @@ class State final { void updateApRecords(); - template &> Func> + template &> Func> void withApRecords(Func&& onApRecords) const { mutex.withLock([&] { std::invoke(std::forward(onApRecords), apRecords); }); } - std::vector getApRecords() const { + std::vector getApRecords() const { auto lock = mutex.asScopedLock(); lock.lock(); return apRecords; diff --git a/Tactility/Private/Tactility/app/wifimanage/View.h b/Tactility/Private/Tactility/app/wifimanage/View.h index 1475a6472..00a0a5055 100644 --- a/Tactility/Private/Tactility/app/wifimanage/View.h +++ b/Tactility/Private/Tactility/app/wifimanage/View.h @@ -27,7 +27,7 @@ class View final { void updateScanning(); void updateNetworkList(); 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 connect(lv_event_t* event); diff --git a/Tactility/Source/kernel/critical/Critical.cpp b/Tactility/Source/Critical.cpp similarity index 95% rename from Tactility/Source/kernel/critical/Critical.cpp rename to Tactility/Source/Critical.cpp index f05503732..26455b4cd 100644 --- a/Tactility/Source/kernel/critical/Critical.cpp +++ b/Tactility/Source/Critical.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/Tactility/Source/kernel/PanicHandler.cpp b/Tactility/Source/PanicHandler.cpp similarity index 98% rename from Tactility/Source/kernel/PanicHandler.cpp rename to Tactility/Source/PanicHandler.cpp index 3fdc1c481..ac20d4b1d 100644 --- a/Tactility/Source/kernel/PanicHandler.cpp +++ b/Tactility/Source/PanicHandler.cpp @@ -86,7 +86,7 @@ const CrashData& getRtcCrashData() { return crashData; } // Stub implementation for RISC-V and other architectures // TODO: Implement crash data collection for RISC-V using frame pointer or EH frame -#include +#include static CrashData emptyCrashData = {}; diff --git a/Tactility/Source/kernel/Platform.cpp b/Tactility/Source/Platform.cpp similarity index 78% rename from Tactility/Source/kernel/Platform.cpp rename to Tactility/Source/Platform.cpp index fd1cefae4..d9b8c9dae 100644 --- a/Tactility/Source/kernel/Platform.cpp +++ b/Tactility/Source/Platform.cpp @@ -1,4 +1,4 @@ -#include "Tactility/kernel/Platform.h" +#include "../Include/Tactility/Platform.h" namespace tt::kernel { diff --git a/Tactility/Source/kernel/SystemEvents.cpp b/Tactility/Source/SystemEvents.cpp similarity index 97% rename from Tactility/Source/kernel/SystemEvents.cpp rename to Tactility/Source/SystemEvents.cpp index c729f17b4..e34cc2749 100644 --- a/Tactility/Source/kernel/SystemEvents.cpp +++ b/Tactility/Source/SystemEvents.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 8fbb966c5..0b588aaa9 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -46,7 +45,26 @@ namespace tt { constexpr auto* TAG = "Tactility"; static const Configuration* config_instance = nullptr; -static Dispatcher mainDispatcher; +static DispatcherHandle_t mainDispatcherHandle = dispatcher_alloc(); + +namespace { + +void mainDispatcherTrampoline(void* context) { + auto* function = static_cast(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 namespace service { @@ -376,7 +394,7 @@ void run(const Configuration& config, Module* dtsModules[], DtsDevice dtsDevices LOG_I(TAG, "Main dispatcher ready"); while (true) { - mainDispatcher.consume(); + dispatcher_consume(mainDispatcherHandle); } } @@ -385,8 +403,8 @@ const Configuration* getConfiguration() { return config_instance; } -Dispatcher& getMainDispatcher() { - return mainDispatcher; +MainDispatcher getMainDispatcher() { + return MainDispatcher(mainDispatcherHandle); } } // namespace diff --git a/Tactility/Source/app/boot/Boot.cpp b/Tactility/Source/app/boot/Boot.cpp index 9e750100b..4b219a7d6 100644 --- a/Tactility/Source/app/boot/Boot.cpp +++ b/Tactility/Source/app/boot/Boot.cpp @@ -1,16 +1,15 @@ #include "Tactility/lvgl/Lvgl.h" -#include +#include +#include +#include #include -#include #include #include -#include +#include #include #include -#include #include -#include #include #include #include @@ -21,8 +20,8 @@ #include #ifdef ESP_PLATFORM -#include "Tactility/app/crashdiagnostics/CrashDiagnostics.h" -#include +#include +#include #include #include #else diff --git a/Tactility/Source/app/crashdiagnostics/QrUrl.cpp b/Tactility/Source/app/crashdiagnostics/QrUrl.cpp index 7a9475f55..576eaef69 100644 --- a/Tactility/Source/app/crashdiagnostics/QrUrl.cpp +++ b/Tactility/Source/app/crashdiagnostics/QrUrl.cpp @@ -1,7 +1,7 @@ #ifdef ESP_PLATFORM #include -#include +#include #include #include diff --git a/Tactility/Source/app/files/State.cpp b/Tactility/Source/app/files/State.cpp index 6788d421b..2791efb51 100644 --- a/Tactility/Source/app/files/State.cpp +++ b/Tactility/Source/app/files/State.cpp @@ -1,10 +1,9 @@ #include -#include +#include #include #include #include -#include #include #include diff --git a/Tactility/Source/app/files/View.cpp b/Tactility/Source/app/files/View.cpp index a9e8de618..fce5d182b 100644 --- a/Tactility/Source/app/files/View.cpp +++ b/Tactility/Source/app/files/View.cpp @@ -1,16 +1,14 @@ #include #include -#include +#include #include #include -#include #include #include #include #include #include -#include #include #include #include diff --git a/Tactility/Source/app/fileselection/State.cpp b/Tactility/Source/app/fileselection/State.cpp index 840d3294f..5f4f92ea4 100644 --- a/Tactility/Source/app/fileselection/State.cpp +++ b/Tactility/Source/app/fileselection/State.cpp @@ -2,12 +2,11 @@ #include #include -#include +#include -#include -#include -#include #include + +#include #include #include diff --git a/Tactility/Source/app/fileselection/View.cpp b/Tactility/Source/app/fileselection/View.cpp index 10fddc689..d685dba6d 100644 --- a/Tactility/Source/app/fileselection/View.cpp +++ b/Tactility/Source/app/fileselection/View.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/Tactility/Source/app/screenshot/Screenshot.cpp b/Tactility/Source/app/screenshot/Screenshot.cpp index e1bcc180a..1ef614cd8 100644 --- a/Tactility/Source/app/screenshot/Screenshot.cpp +++ b/Tactility/Source/app/screenshot/Screenshot.cpp @@ -3,9 +3,9 @@ #if TT_FEATURE_SCREENSHOT_ENABLED +#include #include #include -#include #include #include #include diff --git a/Tactility/Source/app/wificonnect/WifiConnect.cpp b/Tactility/Source/app/wificonnect/WifiConnect.cpp index 32b1cbb7a..eca5e4d05 100644 --- a/Tactility/Source/app/wificonnect/WifiConnect.cpp +++ b/Tactility/Source/app/wificonnect/WifiConnect.cpp @@ -25,22 +25,19 @@ static void onConnect(const service::wifi::settings::WifiApSettings& ap_settings void WifiConnect::onWifiEvent(service::wifi::WifiEvent event) { State& state = getState(); - switch (event) { - case service::wifi::WifiEvent::ConnectionFailed: + if (event.type == WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT) { + if (event.connection_error == WIFI_STATION_CONNECTION_ERROR_NONE) { if (state.isConnecting()) { state.setConnecting(false); - state.setConnectionError(true); - requestViewUpdate(); + stop(manifest.appId); } - break; - case service::wifi::WifiEvent::ConnectionSuccess: - if (getState().isConnecting()) { + } else { + if (state.isConnecting()) { state.setConnecting(false); - stop(manifest.appId); + state.setConnectionError(true); + requestViewUpdate(); } - break; - default: - break; + } } requestViewUpdate(); } diff --git a/Tactility/Source/app/wifimanage/View.cpp b/Tactility/Source/app/wifimanage/View.cpp index 74ee4838f..db712aaa7 100644 --- a/Tactility/Source/app/wifimanage/View.cpp +++ b/Tactility/Source/app/wifimanage/View.cpp @@ -76,7 +76,7 @@ void View::connect(lv_event_t* event) { if (index < ap_records.size()) { 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()); std::string connection_target = service::wifi::getConnectionTarget(); if (connection_target == ssid) { @@ -97,7 +97,7 @@ void View::showDetails(lv_event_t* event) { auto ap_records = self->state->getApRecords(); 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()); self->bindings->onShowApSettings(ssid); } 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) { - 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); } 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 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()); lv_obj_set_user_data(button, reinterpret_cast(index)); if (service::wifi::settings::contains(record.ssid)) { diff --git a/Tactility/Source/app/wifimanage/WifiManage.cpp b/Tactility/Source/app/wifimanage/WifiManage.cpp index b45fff1c0..918b90a0a 100644 --- a/Tactility/Source/app/wifimanage/WifiManage.cpp +++ b/Tactility/Source/app/wifimanage/WifiManage.cpp @@ -79,17 +79,16 @@ void WifiManage::onWifiEvent(service::wifi::WifiEvent event) { auto radio_state = service::wifi::getRadioState(); LOG_I(TAG, "Update with state %s", service::wifi::radioStateToString(radio_state)); getState().setRadioState(radio_state); - switch (event) { - using enum service::wifi::WifiEvent; - case ScanStarted: + switch (event.type) { + case WIFI_EVENT_TYPE_SCAN_STARTED: getState().setScanning(true); break; - case ScanFinished: + case WIFI_EVENT_TYPE_SCAN_FINISHED: getState().setScanning(false); getState().updateApRecords(); break; - case RadioStateOn: - if (!service::wifi::isScanning()) { + case WIFI_EVENT_TYPE_RADIO_STATE_CHANGED: + if (event.radio_state == WIFI_RADIO_STATE_ON && !service::wifi::isScanning()) { service::wifi::scan(); } break; diff --git a/Tactility/Source/hal/Hal.cpp b/Tactility/Source/hal/Hal.cpp index 48fcad00a..8ce2ed084 100644 --- a/Tactility/Source/hal/Hal.cpp +++ b/Tactility/Source/hal/Hal.cpp @@ -1,9 +1,9 @@ +#include #include #include -#include #include +#include #include -#include #include #include diff --git a/Tactility/Source/lvgl/Statusbar.cpp b/Tactility/Source/lvgl/Statusbar.cpp index 341edb238..017ed1782 100644 --- a/Tactility/Source/lvgl/Statusbar.cpp +++ b/Tactility/Source/lvgl/Statusbar.cpp @@ -1,11 +1,10 @@ #define LV_USE_PRIVATE_API 1 // For actual lv_obj_t declaration -#include +#include #include #include #include #include -#include #include #include #include @@ -17,6 +16,7 @@ #include #include +#include namespace tt::lvgl { diff --git a/Tactility/Source/network/Ntp.cpp b/Tactility/Source/network/Ntp.cpp index b8e41223d..fa7506a1a 100644 --- a/Tactility/Source/network/Ntp.cpp +++ b/Tactility/Source/network/Ntp.cpp @@ -6,7 +6,7 @@ #include #ifdef ESP_PLATFORM -#include +#include #include #include #include diff --git a/Tactility/Source/service/wifi/Wifi.cpp b/Tactility/Source/service/wifi/Wifi.cpp index e6e9226df..933251fcc 100644 --- a/Tactility/Source/service/wifi/Wifi.cpp +++ b/Tactility/Source/service/wifi/Wifi.cpp @@ -1,12 +1,30 @@ #include #include -#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include + +#include +#include +#include +#include + +#include namespace tt::service::wifi { +constexpr auto* TAG = "WifiService"; +constexpr auto AUTO_SCAN_INTERVAL = 10000; // ms + const char* radioStateToString(RadioState state) { switch (state) { using enum RadioState; @@ -32,4 +50,479 @@ std::shared_ptr findServiceContext() { 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 = std::make_shared>(); + 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 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> 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 getScanResults() { + std::vector 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::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 +}; + +} // namespace tt::service::wifi diff --git a/Tactility/Source/service/wifi/WifiEsp.cpp b/Tactility/Source/service/wifi/WifiEsp.cpp deleted file mode 100644 index a985c865b..000000000 --- a/Tactility/Source/service/wifi/WifiEsp.cpp +++ /dev/null @@ -1,967 +0,0 @@ -#ifdef ESP_PLATFORM -#include -#endif - -#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED) - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -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); -// Methods for main thread dispatcher -static void dispatchAutoConnect(std::shared_ptr wifi); -static void dispatchEnable(std::shared_ptr wifi); -static void dispatchDisable(std::shared_ptr wifi); -static void dispatchScan(std::shared_ptr wifi); -static void dispatchConnect(std::shared_ptr wifi); -static void dispatchDisconnectButKeepActive(std::shared_ptr wifi); - -class Wifi { - - std::atomic radio_state = RadioState::Off; - std::atomic scan_active = false; - std::atomic secure_connection = false; - -public: - - /** @brief Locking mechanism for modifying the Wifi instance */ - RecursiveMutex radioMutex; - RecursiveMutex dataMutex; - std::unique_ptr autoConnectTimer; - /** @brief The public event bus */ - std::shared_ptr> pubsub = std::make_shared>(); - // 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_singleton; - - -// region Public functions - -std::shared_ptr> 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 getScanResults() { - LOG_I(TAG, "getScanResults()"); - auto wifi = wifi_singleton; - - std::vector 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(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) { - auto lock = wifi->dataMutex.asScopedLock(); - if (lock.lock()) { - assert(wifi->scan_list == nullptr); - wifi->scan_list = static_cast(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) { - 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) { - 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) { - 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, WifiEvent event) { - auto lock = wifi->dataMutex.asScopedLock(); - if (lock.lock()) { - wifi->pubsub->publish(event); - } -} - -static bool copy_scan_list(std::shared_ptr 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(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, 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(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) { - 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(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(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) { - 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) { - 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) { - 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) { - 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) { - 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) { - 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_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_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_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::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 -}; - -} // namespace - -#endif // CONFIG_SOC_WIFI_SUPPORTED or CONFIG_SLAVE_SOC_WIFI_SUPPORTED diff --git a/Tactility/Source/service/wifi/WifiMock.cpp b/Tactility/Source/service/wifi/WifiMock.cpp deleted file mode 100644 index 47c0c9055..000000000 --- a/Tactility/Source/service/wifi/WifiMock.cpp +++ /dev/null @@ -1,168 +0,0 @@ -#ifdef ESP_PLATFORM -#include -#endif - -#if not defined(CONFIG_SOC_WIFI_SUPPORTED) && not defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED) - -#include - -#include -#include -#include -#include -#include - -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 = std::make_shared>(); - /** @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> 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 getScanResults() { - check(wifi); - - std::vector 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 -}; - -} // namespace - -#endif // ESP_PLATFORM \ No newline at end of file diff --git a/Tactility/Source/settings/time.cpp b/Tactility/Source/settings/time.cpp index 76088601e..073a3ad7d 100644 --- a/Tactility/Source/settings/time.cpp +++ b/Tactility/Source/settings/time.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include #include diff --git a/TactilityC/Include/tt_wifi.h b/TactilityC/Include/tt_wifi.h deleted file mode 100644 index 4f8d2e685..000000000 --- a/TactilityC/Include/tt_wifi.h +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -#include -#include - -#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 diff --git a/TactilityC/Source/tt_init.cpp b/TactilityC/Source/tt_init.cpp index da2d97f68..8c1fa0184 100644 --- a/TactilityC/Source/tt_init.cpp +++ b/TactilityC/Source/tt_init.cpp @@ -16,7 +16,6 @@ #include "tt_lvgl_toolbar.h" #include "tt_preferences.h" #include "tt_time.h" -#include "tt_wifi.h" #include "symbols/cplusplus.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_is_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 ESP_ELFSYM_EXPORT(tt_lvgl_spinner_create), diff --git a/TactilityC/Source/tt_wifi.cpp b/TactilityC/Source/tt_wifi.cpp deleted file mode 100644 index 202809930..000000000 --- a/TactilityC/Source/tt_wifi.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "tt_wifi.h" - -#include -#include -#include - -using namespace tt::service; - -extern "C" { - -WifiRadioState tt_wifi_get_radio_state() { - return static_cast(wifi::getRadioState()); -} -const char* tt_wifi_radio_state_to_string(WifiRadioState state) { - return wifi::radioStateToString(static_cast(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(); -} - -} diff --git a/TactilityFreeRtos/Include/Tactility/Thread.h b/TactilityFreeRtos/Include/Tactility/Thread.h index c34263dd7..2caefaa65 100644 --- a/TactilityFreeRtos/Include/Tactility/Thread.h +++ b/TactilityFreeRtos/Include/Tactility/Thread.h @@ -18,7 +18,8 @@ namespace tt { 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: @@ -70,7 +71,7 @@ class Thread final { ESP_LOGI(TAG, "Stopped %s", thread->name.c_str()); #endif - vTaskSetThreadLocalStoragePointer(nullptr, 0, nullptr); + vTaskSetThreadLocalStoragePointer(nullptr, LOCAL_STORAGE_SELF_POINTER_INDEX, nullptr); thread->taskHandle = nullptr; vTaskDelete(nullptr); diff --git a/TactilityKernel/include/tactility/drivers/wifi.h b/TactilityKernel/include/tactility/drivers/wifi.h index dafc3e84b..71fedf6d8 100644 --- a/TactilityKernel/include/tactility/drivers/wifi.h +++ b/TactilityKernel/include/tactility/drivers/wifi.h @@ -200,6 +200,23 @@ struct WifiApi { extern const struct DeviceType WIFI_TYPE; +/** @return the first registered WiFi device, regardless of started state, or NULL if none exists */ +struct Device* wifi_find_first_registered_device(void); + +error_t wifi_get_radio_state(struct Device* device, enum WifiRadioState* state); +error_t wifi_get_station_state(struct Device* device, enum WifiStationState* state); +error_t wifi_get_access_point_state(struct Device* device, enum WifiAccessPointState* state); +bool wifi_is_scanning(struct Device* device); +error_t wifi_scan(struct Device* device); +error_t wifi_get_scan_results(struct Device* device, struct WifiApRecord* results, size_t* num_results); +error_t wifi_station_get_ipv4_address(struct Device* device, char* ipv4); +error_t wifi_station_get_target_ssid(struct Device* device, char* ssid); +error_t wifi_station_connect(struct Device* device, const char* ssid, const char* password, int32_t channel); +error_t wifi_station_disconnect(struct Device* device); +error_t wifi_station_get_rssi(struct Device* device, int32_t* rssi); +error_t wifi_add_event_callback(struct Device* device, void* callback_context, WifiEventCallback callback); +error_t wifi_remove_event_callback(struct Device* device, WifiEventCallback callback); + #ifdef __cplusplus } #endif diff --git a/TactilityKernel/source/concurrent/thread.cpp b/TactilityKernel/source/concurrent/thread.cpp index 5bed31093..bb4a2be9f 100644 --- a/TactilityKernel/source/concurrent/thread.cpp +++ b/TactilityKernel/source/concurrent/thread.cpp @@ -10,7 +10,8 @@ #include #include -static const 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 const size_t LOCAL_STORAGE_SELF_POINTER_INDEX = 1; static const char* TAG = "Thread"; struct Thread { diff --git a/TactilityKernel/source/drivers/wifi.cpp b/TactilityKernel/source/drivers/wifi.cpp new file mode 100644 index 000000000..f25b158bd --- /dev/null +++ b/TactilityKernel/source/drivers/wifi.cpp @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +#define WIFI_API(device) ((const struct WifiApi*)device_get_driver(device)->api) + +extern "C" { + +struct Device* wifi_find_first_registered_device() { + struct Device* found = nullptr; + device_for_each_of_type(&WIFI_TYPE, &found, [](struct Device* dev, void* ctx) -> bool { + *static_cast(ctx) = dev; + return false; + }); + return found; +} + +error_t wifi_get_radio_state(struct Device* device, enum WifiRadioState* state) { + return WIFI_API(device)->get_radio_state(device, state); +} + +error_t wifi_get_station_state(struct Device* device, enum WifiStationState* state) { + return WIFI_API(device)->get_station_state(device, state); +} + +error_t wifi_get_access_point_state(struct Device* device, enum WifiAccessPointState* state) { + return WIFI_API(device)->get_access_point_state(device, state); +} + +bool wifi_is_scanning(struct Device* device) { + return WIFI_API(device)->is_scanning(device); +} + +error_t wifi_scan(struct Device* device) { + return WIFI_API(device)->scan(device); +} + +error_t wifi_get_scan_results(struct Device* device, struct WifiApRecord* results, size_t* num_results) { + return WIFI_API(device)->get_scan_results(device, results, num_results); +} + +error_t wifi_station_get_ipv4_address(struct Device* device, char* ipv4) { + return WIFI_API(device)->station_get_ipv4_address(device, ipv4); +} + +error_t wifi_station_get_target_ssid(struct Device* device, char* ssid) { + return WIFI_API(device)->station_get_target_ssid(device, ssid); +} + +error_t wifi_station_connect(struct Device* device, const char* ssid, const char* password, int32_t channel) { + return WIFI_API(device)->station_connect(device, ssid, password, channel); +} + +error_t wifi_station_disconnect(struct Device* device) { + return WIFI_API(device)->station_disconnect(device); +} + +error_t wifi_station_get_rssi(struct Device* device, int32_t* rssi) { + return WIFI_API(device)->station_get_rssi(device, rssi); +} + +error_t wifi_add_event_callback(struct Device* device, void* callback_context, WifiEventCallback callback) { + return WIFI_API(device)->add_event_callback(device, callback_context, callback); +} + +error_t wifi_remove_event_callback(struct Device* device, WifiEventCallback callback) { + return WIFI_API(device)->remove_event_callback(device, callback); +} + +const struct DeviceType WIFI_TYPE = { + .name = "wifi" +}; + +} // extern "C" diff --git a/TactilityKernel/source/kernel_symbols.c b/TactilityKernel/source/kernel_symbols.c index 4e2b2e8e4..70c79d7c2 100644 --- a/TactilityKernel/source/kernel_symbols.c +++ b/TactilityKernel/source/kernel_symbols.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -181,6 +182,22 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = { DEFINE_MODULE_SYMBOL(bluetooth_hid_device_send_gamepad), DEFINE_MODULE_SYMBOL(bluetooth_hid_device_is_connected), DEFINE_MODULE_SYMBOL(BLUETOOTH_HID_DEVICE_TYPE), + // drivers/wifi + DEFINE_MODULE_SYMBOL(wifi_find_first_registered_device), + DEFINE_MODULE_SYMBOL(wifi_get_radio_state), + DEFINE_MODULE_SYMBOL(wifi_get_station_state), + DEFINE_MODULE_SYMBOL(wifi_get_access_point_state), + DEFINE_MODULE_SYMBOL(wifi_is_scanning), + DEFINE_MODULE_SYMBOL(wifi_scan), + DEFINE_MODULE_SYMBOL(wifi_get_scan_results), + DEFINE_MODULE_SYMBOL(wifi_station_get_ipv4_address), + DEFINE_MODULE_SYMBOL(wifi_station_get_target_ssid), + DEFINE_MODULE_SYMBOL(wifi_station_connect), + DEFINE_MODULE_SYMBOL(wifi_station_disconnect), + DEFINE_MODULE_SYMBOL(wifi_station_get_rssi), + DEFINE_MODULE_SYMBOL(wifi_add_event_callback), + DEFINE_MODULE_SYMBOL(wifi_remove_event_callback), + DEFINE_MODULE_SYMBOL(WIFI_TYPE), // drivers/usb_host_hid DEFINE_MODULE_SYMBOL(usb_host_hid_is_connected), DEFINE_MODULE_SYMBOL(USB_HOST_HID_TYPE),