From 2a2558b29aa5ed775e487ec0c184c52aa1bb4803 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 25 Jul 2026 17:20:17 +0200 Subject: [PATCH] Remove old HAL components and refactored GPS-related code (#583) - Added generic GPS/GNSS support with device detection, configuration, and persistent settings. - Improved device and module lifecycle management. - Added flexible filesystem locking support for displays and storage. - Improved display-idle and keyboard backlight handling. - Updated architecture, driver, module, testing, and licensing documentation. - Removed old HAL device and related code. --- .github/workflows/tests.yml | 10 +- .../DevicetreeCompiler/source/generator.py | 3 +- .../tests/data/expected_devicetree.c | 3 + CMakeLists.txt | 3 +- Devices/lilygo-tdeck-plus/devicetree.yaml | 1 + .../lilygo-tdeck-plus/lilygo,tdeck-plus.dts | 9 + Devices/lilygo-tdeck-plus/source/module.cpp | 64 +- Devices/lilygo-tdeck/source/module.cpp | 7 +- Devices/lilygo-tlora-pager/devicetree.yaml | 1 + .../lilygo-tlora-pager/lilygo,tlora-pager.dts | 8 + Devices/lilygo-tlora-pager/source/module.cpp | 32 +- Devices/simulator/Source/Main.cpp | 1 - Devices/simulator/Source/Simulator.cpp | 22 - .../simulator/Source/drivers/sdl_display.cpp | 160 +++++ .../simulator/Source/drivers/sdl_display.h | 17 + .../simulator/Source/drivers/sdl_input.cpp | 108 +++ Devices/simulator/Source/drivers/sdl_input.h | 47 ++ .../simulator/Source/drivers/sdl_keyboard.cpp | 53 ++ .../simulator/Source/drivers/sdl_pointer.cpp | 67 ++ Devices/simulator/Source/hal/SdlDisplay.h | 42 -- Devices/simulator/Source/hal/SdlKeyboard.h | 26 - Devices/simulator/Source/hal/SdlTouch.h | 36 - .../simulator/Source/hal/SimulatorPower.cpp | 36 - Devices/simulator/Source/hal/SimulatorPower.h | 26 - Devices/simulator/Source/module.cpp | 95 ++- Documentation/README.md | 38 +- Documentation/ideas.md | 1 + Drivers/gps-generic-module/CMakeLists.txt | 12 + Drivers/gps-generic-module/LICENSE-GPL-3.0.md | 677 ++++++++++++++++++ Drivers/gps-generic-module/README.md | 14 + .../bindings/tactility,gps-generic.yaml | 17 + Drivers/gps-generic-module/devicetree.yaml | 4 + .../include/gps_generic/bindings.h | 7 + .../include/gps_generic/gps_generic.h | 24 + .../include/gps_generic/gps_generic_module.h | 12 + .../gps_generic/private/cas_messages.h | 57 ++ .../gps_generic/private/gps_response.h | 11 + .../private/gps_generic/private/init.h | 11 + .../private/gps_generic/private/probe.h | 12 + .../private/gps_generic/private/ublox.h | 22 + .../gps_generic/private/ublox_messages.h | 5 +- .../gps-generic-module/source/gps_generic.cpp | 343 +++++++++ .../gps-generic-module/source/init.cpp | 168 ++--- Drivers/gps-generic-module/source/module.cpp | 19 + Drivers/gps-generic-module/source/probe.cpp | 120 ++++ .../gps-generic-module/source/ublox.cpp | 240 +++---- Firmware/CMakeLists.txt | 3 +- Firmware/Source/Main.cpp | 18 +- LICENSE.md | 15 +- .../LICENSE-Apache-2.0.md | 0 Modules/gps-module/CMakeLists.txt | 12 + Modules/gps-module/LICENSE-Apache-2.0.md | 195 +++++ Modules/gps-module/devicetree.yaml | 2 + Modules/gps-module/include/gps/gps.h | 137 ++++ .../include/gps/gps_module.h} | 2 +- Modules/gps-module/include/gps/gps_settings.h | 51 ++ .../private/gps/private/gps_ledger.h | 20 + Modules/gps-module/source/gps.cpp | 57 ++ Modules/gps-module/source/gps_ledger.cpp | 167 +++++ Modules/gps-module/source/gps_settings.cpp | 169 +++++ Modules/gps-module/source/module.cpp | 30 + Modules/hal-device-module/CMakeLists.txt | 20 - Modules/hal-device-module/README.md | 10 - .../include/tactility/drivers/hal_device.h | 28 - .../include/tactility/drivers/hal_device.hpp | 21 - .../include/tactility/hal/Device.h | 144 ---- .../source/drivers/hal_device.cpp | 130 ---- .../hal-device-module/source/hal/Device.cpp | 145 ---- Modules/hal-device-module/source/module.cpp | 19 - .../include/tactility/lvgl_module.h | 2 +- Modules/lvgl-module/source/arch/lvgl_esp32.c | 10 +- Modules/lvgl-module/source/arch/lvgl_posix.c | 12 +- Tactility/CMakeLists.txt | 4 +- Tactility/Include/Tactility/SystemEvents.h | 2 - Tactility/Include/Tactility/Tactility.h | 32 +- Tactility/Include/Tactility/file/File.h | 6 +- Tactility/Include/Tactility/file/FileLock.h | 3 +- .../Include/Tactility/hal/Configuration.h | 24 - .../Tactility/hal/display/DisplayDevice.h | 63 -- .../Tactility/hal/display/DisplayDriver.h | 37 - .../hal/display/KernelDisplayDriver.h | 26 - .../Tactility/hal/encoder/EncoderDevice.h | 24 - Tactility/Include/Tactility/hal/gpio/Gpio.h | 34 - .../Tactility/hal/gps/GpsConfiguration.h | 36 - .../Include/Tactility/hal/gps/GpsDevice.h | 124 ---- .../Include/Tactility/hal/gps/Satellites.h | 59 -- .../Include/Tactility/hal/i2c/I2cDevice.h | 49 -- .../Tactility/hal/keyboard/KeyboardDevice.h | 27 - .../Include/Tactility/hal/power/PowerDevice.h | 63 -- .../Tactility/hal/touch/KernelTouchDriver.h | 22 - .../Include/Tactility/hal/touch/TouchDevice.h | 36 - .../Include/Tactility/hal/touch/TouchDriver.h | 25 - Tactility/Include/Tactility/lvgl/LvglSync.h | 6 +- .../Tactility/service/gps/GpsService.h | 73 -- .../Include/Tactility/service/gps/GpsState.h | 12 - .../Include/Tactility/service/gps/GpsUtil.h | 10 - Tactility/Private/Tactility/hal/HalPrivate.h | 9 - Tactility/Private/Tactility/hal/SdCard.h | 10 +- Tactility/Private/Tactility/hal/gps/Cas.h | 68 -- Tactility/Private/Tactility/hal/gps/GpsInit.h | 14 - Tactility/Private/Tactility/hal/gps/Probe.h | 9 - Tactility/Private/Tactility/hal/gps/Ublox.h | 21 - .../Private/Tactility/lvgl/LvglPrivate.h | 11 - Tactility/Source/MountPoints.cpp | 1 - Tactility/Source/SystemEvents.cpp | 4 - Tactility/Source/Tactility.cpp | 57 +- Tactility/Source/app/AppInstall.cpp | 7 +- Tactility/Source/app/addgps/AddGps.cpp | 58 +- Tactility/Source/app/apphub/AppHubApp.cpp | 6 +- Tactility/Source/app/apphub/AppHubEntry.cpp | 3 +- Tactility/Source/app/boot/Boot.cpp | 35 +- Tactility/Source/app/chat/ChatApp.cpp | 9 +- .../app/crashdiagnostics/CrashDiagnostics.cpp | 4 +- .../Source/app/development/Development.cpp | 12 +- Tactility/Source/app/display/Display.cpp | 310 -------- Tactility/Source/app/files/View.cpp | 9 +- Tactility/Source/app/fileselection/View.cpp | 3 +- .../Source/app/gpssettings/GpsSettings.cpp | 599 ++++++---------- Tactility/Source/app/notes/Notes.cpp | 3 +- Tactility/Source/app/poweroff/PowerOff.cpp | 2 - .../Source/app/screenshot/Screenshot.cpp | 3 +- .../Source/app/systeminfo/SystemInfo.cpp | 6 +- .../app/timedatesettings/TimeDateSettings.cpp | 2 +- Tactility/Source/file/File.cpp | 63 +- Tactility/Source/file/FileLock.cpp | 11 - Tactility/Source/file/FileMutexLvgl.cpp | 80 +++ Tactility/Source/hal/Hal.cpp | 83 --- .../hal/display/KernelDisplayDriver.cpp | 59 -- Tactility/Source/hal/gpio/Gpio.cpp | 87 --- Tactility/Source/hal/gps/GpsConfiguration.cpp | 51 -- Tactility/Source/hal/gps/GpsDevice.cpp | 199 ----- Tactility/Source/hal/gps/Probe.cpp | 146 ---- Tactility/Source/hal/gps/Satellites.cpp | 115 --- Tactility/Source/hal/i2c/I2cDevice.cpp | 81 --- Tactility/Source/hal/power/PowerDevice.cpp | 172 ----- Tactility/Source/hal/sdcard/SdCard.cpp | 36 +- .../Source/hal/touch/KernelTouchDriver.cpp | 24 - Tactility/Source/lvgl/Lvgl.cpp | 205 ------ Tactility/Source/lvgl/Toolbar.cpp | 4 +- Tactility/Source/network/Http.cpp | 3 +- Tactility/Source/network/HttpdReq.cpp | 3 +- .../service/displayidle/DisplayIdle.cpp | 83 ++- .../Source/service/gps/GpsConfiguration.cpp | 119 --- Tactility/Source/service/gps/GpsService.cpp | 259 ------- .../service/keyboardidle/KeyboardIdle.cpp | 44 +- .../Source/service/statusbar/Statusbar.cpp | 33 +- .../service/webserver/WebServerService.cpp | 1 - Tactility/Source/settings/DisplaySettings.cpp | 2 - TactilityC/Include/tt_gps.h | 23 - TactilityC/Include/tt_hal_device.h | 34 - TactilityC/Include/tt_hal_display.h | 100 --- TactilityC/Include/tt_hal_gpio.h | 24 - TactilityC/Include/tt_hal_touch.h | 48 -- TactilityC/Include/tt_hal_uart.h | 159 ---- TactilityC/Include/tt_lvgl.h | 29 - TactilityC/Source/tt_gps.cpp | 45 -- TactilityC/Source/tt_hal_device.cpp | 48 -- TactilityC/Source/tt_hal_display.cpp | 93 --- TactilityC/Source/tt_hal_touch.cpp | 43 -- TactilityC/Source/tt_hal_uart.cpp | 61 -- TactilityC/Source/tt_init.cpp | 42 -- TactilityC/Source/tt_lvgl.cpp | 26 - TactilityKernel/include/tactility/device.h | 26 +- .../include/tactility/drivers/keyboard.h | 21 + .../include/tactility/filesystem/file_mutex.h | 51 ++ TactilityKernel/include/tactility/module.h | 16 + TactilityKernel/source/device.cpp | 14 + TactilityKernel/source/drivers/keyboard.cpp | 11 + .../source/filesystem/file_mutex.cpp | 73 ++ TactilityKernel/source/module.cpp | 64 +- Tests/Tactility/CMakeLists.txt | 3 +- Tests/Tactility/Source/HalDeviceTest.cpp | 98 --- Tests/Tactility/Source/Main.cpp | 3 +- 173 files changed, 3855 insertions(+), 5445 deletions(-) delete mode 100644 Devices/simulator/Source/Simulator.cpp create mode 100644 Devices/simulator/Source/drivers/sdl_display.cpp create mode 100644 Devices/simulator/Source/drivers/sdl_display.h create mode 100644 Devices/simulator/Source/drivers/sdl_input.cpp create mode 100644 Devices/simulator/Source/drivers/sdl_input.h create mode 100644 Devices/simulator/Source/drivers/sdl_keyboard.cpp create mode 100644 Devices/simulator/Source/drivers/sdl_pointer.cpp delete mode 100644 Devices/simulator/Source/hal/SdlDisplay.h delete mode 100644 Devices/simulator/Source/hal/SdlKeyboard.h delete mode 100644 Devices/simulator/Source/hal/SdlTouch.h delete mode 100644 Devices/simulator/Source/hal/SimulatorPower.cpp delete mode 100644 Devices/simulator/Source/hal/SimulatorPower.h create mode 100644 Drivers/gps-generic-module/CMakeLists.txt create mode 100644 Drivers/gps-generic-module/LICENSE-GPL-3.0.md create mode 100644 Drivers/gps-generic-module/README.md create mode 100644 Drivers/gps-generic-module/bindings/tactility,gps-generic.yaml create mode 100644 Drivers/gps-generic-module/devicetree.yaml create mode 100644 Drivers/gps-generic-module/include/gps_generic/bindings.h create mode 100644 Drivers/gps-generic-module/include/gps_generic/gps_generic.h create mode 100644 Drivers/gps-generic-module/include/gps_generic/gps_generic_module.h create mode 100644 Drivers/gps-generic-module/private/gps_generic/private/cas_messages.h create mode 100644 Drivers/gps-generic-module/private/gps_generic/private/gps_response.h create mode 100644 Drivers/gps-generic-module/private/gps_generic/private/init.h create mode 100644 Drivers/gps-generic-module/private/gps_generic/private/probe.h create mode 100644 Drivers/gps-generic-module/private/gps_generic/private/ublox.h rename Tactility/Private/Tactility/hal/gps/UbloxMessages.h => Drivers/gps-generic-module/private/gps_generic/private/ublox_messages.h (99%) create mode 100644 Drivers/gps-generic-module/source/gps_generic.cpp rename Tactility/Source/hal/gps/GpsInit.cpp => Drivers/gps-generic-module/source/init.cpp (73%) create mode 100644 Drivers/gps-generic-module/source/module.cpp create mode 100644 Drivers/gps-generic-module/source/probe.cpp rename Tactility/Source/hal/gps/Ublox.cpp => Drivers/gps-generic-module/source/ublox.cpp (72%) rename Modules/{hal-device-module => crypt-module}/LICENSE-Apache-2.0.md (100%) create mode 100644 Modules/gps-module/CMakeLists.txt create mode 100644 Modules/gps-module/LICENSE-Apache-2.0.md create mode 100644 Modules/gps-module/devicetree.yaml create mode 100644 Modules/gps-module/include/gps/gps.h rename Modules/{hal-device-module/include/tactility/hal_device_module.h => gps-module/include/gps/gps_module.h} (75%) create mode 100644 Modules/gps-module/include/gps/gps_settings.h create mode 100644 Modules/gps-module/private/gps/private/gps_ledger.h create mode 100644 Modules/gps-module/source/gps.cpp create mode 100644 Modules/gps-module/source/gps_ledger.cpp create mode 100644 Modules/gps-module/source/gps_settings.cpp create mode 100644 Modules/gps-module/source/module.cpp delete mode 100644 Modules/hal-device-module/CMakeLists.txt delete mode 100644 Modules/hal-device-module/README.md delete mode 100644 Modules/hal-device-module/include/tactility/drivers/hal_device.h delete mode 100644 Modules/hal-device-module/include/tactility/drivers/hal_device.hpp delete mode 100644 Modules/hal-device-module/include/tactility/hal/Device.h delete mode 100644 Modules/hal-device-module/source/drivers/hal_device.cpp delete mode 100644 Modules/hal-device-module/source/hal/Device.cpp delete mode 100644 Modules/hal-device-module/source/module.cpp delete mode 100644 Tactility/Include/Tactility/hal/Configuration.h delete mode 100644 Tactility/Include/Tactility/hal/display/DisplayDevice.h delete mode 100644 Tactility/Include/Tactility/hal/display/DisplayDriver.h delete mode 100644 Tactility/Include/Tactility/hal/display/KernelDisplayDriver.h delete mode 100644 Tactility/Include/Tactility/hal/encoder/EncoderDevice.h delete mode 100644 Tactility/Include/Tactility/hal/gpio/Gpio.h delete mode 100644 Tactility/Include/Tactility/hal/gps/GpsConfiguration.h delete mode 100644 Tactility/Include/Tactility/hal/gps/GpsDevice.h delete mode 100644 Tactility/Include/Tactility/hal/gps/Satellites.h delete mode 100644 Tactility/Include/Tactility/hal/i2c/I2cDevice.h delete mode 100644 Tactility/Include/Tactility/hal/keyboard/KeyboardDevice.h delete mode 100644 Tactility/Include/Tactility/hal/power/PowerDevice.h delete mode 100644 Tactility/Include/Tactility/hal/touch/KernelTouchDriver.h delete mode 100644 Tactility/Include/Tactility/hal/touch/TouchDevice.h delete mode 100644 Tactility/Include/Tactility/hal/touch/TouchDriver.h delete mode 100644 Tactility/Include/Tactility/service/gps/GpsService.h delete mode 100644 Tactility/Include/Tactility/service/gps/GpsState.h delete mode 100644 Tactility/Include/Tactility/service/gps/GpsUtil.h delete mode 100644 Tactility/Private/Tactility/hal/HalPrivate.h delete mode 100644 Tactility/Private/Tactility/hal/gps/Cas.h delete mode 100644 Tactility/Private/Tactility/hal/gps/GpsInit.h delete mode 100644 Tactility/Private/Tactility/hal/gps/Probe.h delete mode 100644 Tactility/Private/Tactility/hal/gps/Ublox.h delete mode 100644 Tactility/Private/Tactility/lvgl/LvglPrivate.h delete mode 100644 Tactility/Source/app/display/Display.cpp delete mode 100644 Tactility/Source/file/FileLock.cpp create mode 100644 Tactility/Source/file/FileMutexLvgl.cpp delete mode 100644 Tactility/Source/hal/Hal.cpp delete mode 100644 Tactility/Source/hal/display/KernelDisplayDriver.cpp delete mode 100644 Tactility/Source/hal/gpio/Gpio.cpp delete mode 100644 Tactility/Source/hal/gps/GpsConfiguration.cpp delete mode 100644 Tactility/Source/hal/gps/GpsDevice.cpp delete mode 100644 Tactility/Source/hal/gps/Probe.cpp delete mode 100644 Tactility/Source/hal/gps/Satellites.cpp delete mode 100644 Tactility/Source/hal/i2c/I2cDevice.cpp delete mode 100644 Tactility/Source/hal/power/PowerDevice.cpp delete mode 100644 Tactility/Source/hal/touch/KernelTouchDriver.cpp delete mode 100644 Tactility/Source/lvgl/Lvgl.cpp delete mode 100644 Tactility/Source/service/gps/GpsConfiguration.cpp delete mode 100644 Tactility/Source/service/gps/GpsService.cpp delete mode 100644 TactilityC/Include/tt_gps.h delete mode 100644 TactilityC/Include/tt_hal_device.h delete mode 100644 TactilityC/Include/tt_hal_display.h delete mode 100644 TactilityC/Include/tt_hal_gpio.h delete mode 100644 TactilityC/Include/tt_hal_touch.h delete mode 100644 TactilityC/Include/tt_hal_uart.h delete mode 100644 TactilityC/Include/tt_lvgl.h delete mode 100644 TactilityC/Source/tt_gps.cpp delete mode 100644 TactilityC/Source/tt_hal_device.cpp delete mode 100644 TactilityC/Source/tt_hal_display.cpp delete mode 100644 TactilityC/Source/tt_hal_touch.cpp delete mode 100644 TactilityC/Source/tt_hal_uart.cpp delete mode 100644 TactilityC/Source/tt_lvgl.cpp create mode 100644 TactilityKernel/include/tactility/filesystem/file_mutex.h create mode 100644 TactilityKernel/source/filesystem/file_mutex.cpp delete mode 100644 Tests/Tactility/Source/HalDeviceTest.cpp diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 78ab3c9a..5aaf86b5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,14 +19,8 @@ jobs: run: cmake -S ./ -B build - name: "Build Tests" run: cmake --build build --target build-tests - - name: "Run TactilityFreeRtos Tests" - run: build/Tests/TactilityFreeRtos/TactilityFreeRtosTests - - name: "Run Tactility Tests" - run: build/Tests/Tactility/TactilityTests - - name: "Run TactilityKernel Tests" - run: build/Tests/TactilityKernel/TactilityKernelTests - - name: "Run CryptModuleTests Tests" - run: build/Tests/crypt-module/CryptModuleTests + - name: "Run Tests" + run: ctest --test-dir build/Tests DevicetreeTests: runs-on: ubuntu-latest steps: diff --git a/Buildscripts/DevicetreeCompiler/source/generator.py b/Buildscripts/DevicetreeCompiler/source/generator.py index 4a45cce7..f40a1d2c 100644 --- a/Buildscripts/DevicetreeCompiler/source/generator.py +++ b/Buildscripts/DevicetreeCompiler/source/generator.py @@ -26,7 +26,7 @@ def get_device_node_name_safe(device: Device): def get_device_type_name(device: Device, bindings: list[Binding]): device_binding = find_device_binding(device, bindings) if device_binding is None: - raise DevicetreeException(f"Binding not found for {device.node_name}") + raise DevicetreeException(f"Binding not found for {device.node_name}. Make sure that the driver name in the driver's yaml and driver code declarations matches with the device dts file.") if device_binding.compatible is None: raise DevicetreeException(f"Couldn't find compatible binding for {device.node_name}") compatible_safe = device_binding.compatible.split(",")[-1] @@ -282,6 +282,7 @@ def write_device_structs(file, device: Device, parent_device: Device, bindings: file.write(f"\t.address = {address_value},\n") file.write(f"\t.name = \"{device.node_name}\",\n") # Use original name file.write(f"\t.config = &{config_variable_name},\n") + file.write("\t.flags = DEVICE_FLAG_DTS,\n") file.write(f"\t.parent = {parent_value},\n") file.write("\t.internal = NULL\n") file.write("};\n\n") diff --git a/Buildscripts/DevicetreeCompiler/tests/data/expected_devicetree.c b/Buildscripts/DevicetreeCompiler/tests/data/expected_devicetree.c index 9ec715e4..1768f40e 100644 --- a/Buildscripts/DevicetreeCompiler/tests/data/expected_devicetree.c +++ b/Buildscripts/DevicetreeCompiler/tests/data/expected_devicetree.c @@ -13,6 +13,7 @@ static struct Device root = { .address = 0, .name = "/", .config = &root_config, + .flags = DEVICE_FLAG_DTS, .parent = NULL, .internal = NULL }; @@ -27,6 +28,7 @@ static struct Device test_device = { .address = 0, .name = "test-device", .config = &test_device_config, + .flags = DEVICE_FLAG_DTS, .parent = &root, .internal = NULL }; @@ -43,6 +45,7 @@ static struct Device bool_test_device = { .address = 0, .name = "bool-test-device", .config = &bool_test_device_config, + .flags = DEVICE_FLAG_DTS, .parent = &root, .internal = NULL }; diff --git a/CMakeLists.txt b/CMakeLists.txt index bce5ea47..0ab91438 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,9 +96,10 @@ if (NOT DEFINED ENV{ESP_IDF_VERSION}) add_subdirectory(Libraries/QRCode) add_subdirectory(Libraries/minitar) add_subdirectory(Libraries/minmea) - add_subdirectory(Modules/hal-device-module) add_subdirectory(Modules/lvgl-module) add_subdirectory(Modules/crypt-module) + add_subdirectory(Modules/gps-module) + add_subdirectory(Drivers/gps-generic-module) # FreeRTOS set(FREERTOS_CONFIG_FILE_DIRECTORY ${PROJECT_SOURCE_DIR}/Devices/simulator/Source CACHE STRING "") diff --git a/Devices/lilygo-tdeck-plus/devicetree.yaml b/Devices/lilygo-tdeck-plus/devicetree.yaml index 805ca1ea..8634f22b 100644 --- a/Devices/lilygo-tdeck-plus/devicetree.yaml +++ b/Devices/lilygo-tdeck-plus/devicetree.yaml @@ -1,5 +1,6 @@ dependencies: - Platforms/platform-esp32 + - Drivers/gps-generic-module - Drivers/st7789-module - Drivers/gt911-module - Drivers/lilygo-module diff --git a/Devices/lilygo-tdeck-plus/lilygo,tdeck-plus.dts b/Devices/lilygo-tdeck-plus/lilygo,tdeck-plus.dts index 1cfc2c93..f9bd97d0 100644 --- a/Devices/lilygo-tdeck-plus/lilygo,tdeck-plus.dts +++ b/Devices/lilygo-tdeck-plus/lilygo,tdeck-plus.dts @@ -20,6 +20,8 @@ #include #include +#include + #include #include #include @@ -188,5 +190,12 @@ port = ; pin-tx = <&gpio0 43 GPIO_FLAG_NONE>; pin-rx = <&gpio0 44 GPIO_FLAG_NONE>; + + gps { + compatible = "tactility,gps-generic"; + status = "disabled"; + baud-rate = <38400>; + model = ; + }; }; }; diff --git a/Devices/lilygo-tdeck-plus/source/module.cpp b/Devices/lilygo-tdeck-plus/source/module.cpp index 8b3e97a7..ea51abaa 100644 --- a/Devices/lilygo-tdeck-plus/source/module.cpp +++ b/Devices/lilygo-tdeck-plus/source/module.cpp @@ -1,75 +1,57 @@ -#include +#include #include #include #include +#include #include #include -#include -#include -#include -#include -#include #include #include #include -#include - constexpr auto* TAG = "tdeck-plus"; extern "C" { -void subscribe_events() { - tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) { - auto gps_service = tt::service::gps::findGpsService(); - if (gps_service != nullptr) { - std::vector gps_configurations; - gps_service->getGpsConfigurations(gps_configurations); - if (gps_configurations.empty()) { - if (gps_service->addGpsConfiguration(tt::hal::gps::GpsConfiguration {.uartName = "uart0", .baudRate = 38400, .model = tt::hal::gps::GpsModel::UBLOX10})) { - LOG_I(TAG, "Configured internal GPS"); - } else { - LOG_E(TAG, "Failed to configure internal GPS"); - } - } - } - }); +static tt::kernel::SystemEventSubscription tdeck_boot_splash_subscription = tt::kernel::NoSystemEventSubscription; - // The kernel trackball device is already started by kernel_init(); this just registers it as an - // LVGL input device and applies persisted settings, both of which require LVGL to be up first. - tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) { - auto tbSettings = tt::settings::trackball::loadOrGetDefault(); - lvgl_lock(); - if (trackball::init() != nullptr) { - trackball::setMode(tbSettings.trackballMode == tt::settings::trackball::TrackballMode::Pointer - ? trackball::Mode::Pointer - : trackball::Mode::Encoder); - trackball::setEncoderSensitivity(tbSettings.encoderSensitivity); - trackball::setPointerSensitivity(tbSettings.pointerSensitivity); - trackball::setEnabled(tbSettings.trackballEnabled); - } - lvgl_unlock(); - }); +void init_trackball() { + auto tbSettings = tt::settings::trackball::loadOrGetDefault(); + lvgl_lock(); + if (trackball::init() != nullptr) { + trackball::setMode(tbSettings.trackballMode == tt::settings::trackball::TrackballMode::Pointer + ? trackball::Mode::Pointer + : trackball::Mode::Encoder); + trackball::setEncoderSensitivity(tbSettings.encoderSensitivity); + trackball::setPointerSensitivity(tbSettings.pointerSensitivity); + trackball::setEnabled(tbSettings.trackballEnabled); + } + lvgl_unlock(); } static error_t start() { LOG_I(TAG, LOG_MESSAGE_POWER_ON_START); + if (!tdeck_power_on()) { LOG_E(TAG, LOG_MESSAGE_POWER_ON_FAILED); return ERROR_RESOURCE; } // Avoids crash when no SD card is inserted. It's unknown why, but likely is related to power draw. - tt::kernel::delayMillis(100); + delay_millis(100); - subscribe_events(); + tdeck_boot_splash_subscription = tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent event) { + init_trackball(); + }); return ERROR_NONE; } static error_t stop() { + tt::kernel::unsubscribeSystemEvent(tdeck_boot_splash_subscription); + tdeck_boot_splash_subscription = tt::kernel::NoSystemEventSubscription; return ERROR_NONE; } @@ -77,8 +59,6 @@ Module lilygo_tdeck_plus_module = { .name = "lilygo-tdeck-plus", .start = start, .stop = stop, - .symbols = nullptr, - .internal = nullptr }; } diff --git a/Devices/lilygo-tdeck/source/module.cpp b/Devices/lilygo-tdeck/source/module.cpp index 0566fcec..41907e63 100644 --- a/Devices/lilygo-tdeck/source/module.cpp +++ b/Devices/lilygo-tdeck/source/module.cpp @@ -1,20 +1,17 @@ #include -#include "lilygo/drivers/tdeck_power_on.h" -#include "tactility/lvgl_module.h" - #include #include +#include #include #include -#include #include #include -#include #include #include +#include constexpr auto* TAG = "tdeck"; diff --git a/Devices/lilygo-tlora-pager/devicetree.yaml b/Devices/lilygo-tlora-pager/devicetree.yaml index 82ada517..d38da1a5 100644 --- a/Devices/lilygo-tlora-pager/devicetree.yaml +++ b/Devices/lilygo-tlora-pager/devicetree.yaml @@ -1,5 +1,6 @@ dependencies: - Platforms/platform-esp32 + - Drivers/gps-generic-module - Drivers/st7796-module - Drivers/bq27220-module - Drivers/tca8418-module diff --git a/Devices/lilygo-tlora-pager/lilygo,tlora-pager.dts b/Devices/lilygo-tlora-pager/lilygo,tlora-pager.dts index f3343fb5..a2e55af7 100644 --- a/Devices/lilygo-tlora-pager/lilygo,tlora-pager.dts +++ b/Devices/lilygo-tlora-pager/lilygo,tlora-pager.dts @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -181,6 +182,13 @@ port = ; pin-tx = <&gpio0 12 GPIO_FLAG_NONE>; pin-rx = <&gpio0 4 GPIO_FLAG_NONE>; + + gps { + compatible = "tactility,gps-generic"; + status = "disabled"; + baud-rate = <38400>; + model = ; + }; }; uart_external: uart1 { diff --git a/Devices/lilygo-tlora-pager/source/module.cpp b/Devices/lilygo-tlora-pager/source/module.cpp index b5408b87..81f331b2 100644 --- a/Devices/lilygo-tlora-pager/source/module.cpp +++ b/Devices/lilygo-tlora-pager/source/module.cpp @@ -1,15 +1,8 @@ -#include -#include +#include #include -#include #include -#include -#include #include -#include -#include -#include #include @@ -17,41 +10,22 @@ constexpr auto* TAG = "T-Lora Pager"; extern "C" { -tt::kernel::SystemEventSubscription event_subscription; +tt::kernel::SystemEventSubscription event_subscription = tt::kernel::NoSystemEventSubscription; static error_t start() { - - LOG_I(TAG, LOG_MESSAGE_POWER_ON_START); - event_subscription = tt::kernel::subscribeSystemEvent(tt::kernel::SystemEvent::BootSplash, [](tt::kernel::SystemEvent) { // The kernel tpager_encoder device is already started by kernel_init(); this just // registers it as an LVGL input device, which requires LVGL to be up first. lvgl_lock(); tpager_encoder::init(); lvgl_unlock(); - - auto gps_service = tt::service::gps::findGpsService(); - if (gps_service != nullptr) { - std::vector gps_configurations; - gps_service->getGpsConfigurations(gps_configurations); - if (gps_configurations.empty()) { - if (gps_service->addGpsConfiguration(tt::hal::gps::GpsConfiguration { - .uartName = "uart0", - .baudRate = 38400, - .model = tt::hal::gps::GpsModel::UBLOX10 - })) { - LOG_I(TAG, "Configured internal GPS"); - } else { - LOG_E(TAG, "Failed to configure internal GPS"); - } - } - } }); return ERROR_NONE; } static error_t stop() { tt::kernel::unsubscribeSystemEvent(event_subscription); + event_subscription = tt::kernel::NoSystemEventSubscription; return ERROR_NONE; } diff --git a/Devices/simulator/Source/Main.cpp b/Devices/simulator/Source/Main.cpp index 464607a2..85e834ea 100644 --- a/Devices/simulator/Source/Main.cpp +++ b/Devices/simulator/Source/Main.cpp @@ -1,6 +1,5 @@ #include "Main.h" #include -#include #include "FreeRTOS.h" #include "task.h" diff --git a/Devices/simulator/Source/Simulator.cpp b/Devices/simulator/Source/Simulator.cpp deleted file mode 100644 index 0e44471d..00000000 --- a/Devices/simulator/Source/Simulator.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "hal/SdlDisplay.h" -#include "hal/SdlKeyboard.h" -#include "hal/SimulatorPower.h" - -#include - -#define TAG "hardware" - -using namespace tt::hal; - -static std::vector> createDevices() { - return { - std::make_shared(), - std::make_shared(), - std::make_shared(), - }; -} - -extern const Configuration hardwareConfiguration = { - .initBoot = nullptr, - .createDevices = createDevices -}; diff --git a/Devices/simulator/Source/drivers/sdl_display.cpp b/Devices/simulator/Source/drivers/sdl_display.cpp new file mode 100644 index 00000000..ca39fbca --- /dev/null +++ b/Devices/simulator/Source/drivers/sdl_display.cpp @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "sdl_display.h" + +#include +#include +#include +#include +#include + +#include + +#include + +constexpr auto* TAG = "SdlDisplay"; +#define GET_CONFIG(device) (static_cast((device)->config)) + +struct SdlDisplayInternal { + SDL_Window* window; + SDL_Renderer* renderer; + SDL_Texture* texture; +}; + +// region Driver lifecycle + +static error_t start(Device* device) { + const auto* config = GET_CONFIG(device); + + auto* internal = static_cast(malloc(sizeof(SdlDisplayInternal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) { + LOG_E(TAG, "SDL_InitSubSystem failed: %s", SDL_GetError()); + free(internal); + return ERROR_RESOURCE; + } + + internal->window = SDL_CreateWindow( + "Tactility", + SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + config->horizontal_resolution, config->vertical_resolution, + SDL_WINDOW_SHOWN + ); + internal->renderer = internal->window != nullptr + ? SDL_CreateRenderer(internal->window, -1, SDL_RENDERER_ACCELERATED) + : nullptr; + internal->texture = internal->renderer != nullptr + ? SDL_CreateTexture(internal->renderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, + config->horizontal_resolution, config->vertical_resolution) + : nullptr; + + if (internal->window == nullptr || internal->renderer == nullptr || internal->texture == nullptr) { + LOG_E(TAG, "Failed to create SDL window: %s", SDL_GetError()); + if (internal->texture != nullptr) SDL_DestroyTexture(internal->texture); + if (internal->renderer != nullptr) SDL_DestroyRenderer(internal->renderer); + if (internal->window != nullptr) SDL_DestroyWindow(internal->window); + SDL_QuitSubSystem(SDL_INIT_VIDEO); + free(internal); + return ERROR_RESOURCE; + } + + device_set_driver_data(device, internal); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + + SDL_DestroyTexture(internal->texture); + SDL_DestroyRenderer(internal->renderer); + SDL_DestroyWindow(internal->window); + SDL_QuitSubSystem(SDL_INIT_VIDEO); + + free(internal); + device_set_driver_data(device, nullptr); + return ERROR_NONE; +} + +// endregion + +// region DisplayApi + +static error_t sdl_display_reset(Device*) { return ERROR_NONE; } +static error_t sdl_display_init(Device*) { return ERROR_NONE; } + +static error_t sdl_display_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) { + auto* internal = static_cast(device_get_driver_data(device)); + + SDL_Rect rect = { x_start, y_start, x_end - x_start, y_end - y_start }; + // RGB565 = 2 bytes/pixel. + if (SDL_UpdateTexture(internal->texture, &rect, color_data, (x_end - x_start) * 2) != 0) { + return ERROR_RESOURCE; + } + + SDL_RenderClear(internal->renderer); + SDL_RenderCopy(internal->renderer, internal->texture, nullptr, nullptr); + SDL_RenderPresent(internal->renderer); + return ERROR_NONE; +} + +static enum DisplayColorFormat sdl_display_get_color_format(Device*) { + return DISPLAY_COLOR_FORMAT_RGB565; +} + +static uint16_t sdl_display_get_resolution_x(Device* device) { + return GET_CONFIG(device)->horizontal_resolution; +} + +static uint16_t sdl_display_get_resolution_y(Device* device) { + return GET_CONFIG(device)->vertical_resolution; +} + +static void sdl_display_get_frame_buffer(Device*, uint8_t, void** out_buffer) { + *out_buffer = nullptr; +} + +static uint8_t sdl_display_get_frame_buffer_count(Device*) { + return 0; +} + +// endregion + +static const DisplayApi sdl_display_api = { + .capabilities = 0, + .reset = sdl_display_reset, + .init = sdl_display_init, + .draw_bitmap = sdl_display_draw_bitmap, + .mirror = nullptr, + .swap_xy = nullptr, + .get_swap_xy = nullptr, + .get_mirror_x = nullptr, + .get_mirror_y = nullptr, + .set_gap = nullptr, + .get_gap_x = nullptr, + .get_gap_y = nullptr, + .invert_color = nullptr, + .disp_on_off = nullptr, + .disp_sleep = nullptr, + .get_color_format = sdl_display_get_color_format, + .get_resolution_x = sdl_display_get_resolution_x, + .get_resolution_y = sdl_display_get_resolution_y, + .get_frame_buffer = sdl_display_get_frame_buffer, + .get_frame_buffer_count = sdl_display_get_frame_buffer_count, + .get_backlight = nullptr, + .has_capability = nullptr, +}; + +extern Module simulator_module; + +Driver sdl_display_driver = { + .name = "sdl-display", + .compatible = (const char*[]) { "tactility,sdl-display", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &sdl_display_api, + .device_type = &DISPLAY_TYPE, + .owner = &simulator_module, + .internal = nullptr +}; diff --git a/Devices/simulator/Source/drivers/sdl_display.h b/Devices/simulator/Source/drivers/sdl_display.h new file mode 100644 index 00000000..9e91c20a --- /dev/null +++ b/Devices/simulator/Source/drivers/sdl_display.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct SdlDisplayConfig { + uint16_t horizontal_resolution; + uint16_t vertical_resolution; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Devices/simulator/Source/drivers/sdl_input.cpp b/Devices/simulator/Source/drivers/sdl_input.cpp new file mode 100644 index 00000000..86aea8c8 --- /dev/null +++ b/Devices/simulator/Source/drivers/sdl_input.cpp @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "sdl_input.h" + +#include + +#include + +#include + +namespace { + +constexpr size_t KEY_QUEUE_CAPACITY = 32; + +SdlPointerState pointer_state = { 0, 0, false }; + +uint32_t key_queue[KEY_QUEUE_CAPACITY]; +size_t key_queue_head = 0; +size_t key_queue_count = 0; + +bool text_input_started = false; + +void push_key(uint32_t key) { + if (key == 0 || key_queue_count >= KEY_QUEUE_CAPACITY) { + return; + } + key_queue[(key_queue_head + key_queue_count) % KEY_QUEUE_CAPACITY] = key; + key_queue_count++; +} + +// Mirrors LVGL's own lv_sdl_keyboard.c keycode_to_ctrl_key(): maps navigation/control keys to +// LV_KEY_* constants. Printable characters arrive separately via SDL_TEXTINPUT. +uint32_t keycode_to_key(SDL_Keycode sdl_key) { + switch (sdl_key) { + case SDLK_RIGHT: return LV_KEY_RIGHT; + case SDLK_LEFT: return LV_KEY_LEFT; + case SDLK_UP: return LV_KEY_UP; + case SDLK_DOWN: return LV_KEY_DOWN; + case SDLK_ESCAPE: return LV_KEY_ESC; + case SDLK_BACKSPACE: return LV_KEY_BACKSPACE; + case SDLK_DELETE: return LV_KEY_DEL; + case SDLK_RETURN: + case SDLK_KP_ENTER: return LV_KEY_ENTER; + case SDLK_TAB: return LV_KEY_NEXT; + case SDLK_HOME: return LV_KEY_HOME; + case SDLK_END: return LV_KEY_END; + default: return 0; + } +} + +} + +void sdl_input_pump() { + if (!text_input_started) { + SDL_StartTextInput(); + text_input_started = true; + } + + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_MOUSEMOTION: + pointer_state.x = event.motion.x; + pointer_state.y = event.motion.y; + break; + case SDL_MOUSEBUTTONDOWN: + if (event.button.button == SDL_BUTTON_LEFT) { + pointer_state.x = event.button.x; + pointer_state.y = event.button.y; + pointer_state.pressed = true; + } + break; + case SDL_MOUSEBUTTONUP: + if (event.button.button == SDL_BUTTON_LEFT) { + pointer_state.pressed = false; + } + break; + case SDL_KEYDOWN: + push_key(keycode_to_key(event.key.keysym.sym)); + break; + case SDL_TEXTINPUT: + // ASCII only (first byte of event.text.text) - sufficient for a simulator keyboard. + push_key(static_cast(event.text.text[0])); + break; + case SDL_QUIT: + exit(0); + default: + break; + } + } +} + +void sdl_input_get_pointer_state(SdlPointerState* out_state) { + *out_state = pointer_state; +} + +bool sdl_input_pop_key(uint32_t* out_key) { + if (key_queue_count == 0) { + return false; + } + *out_key = key_queue[key_queue_head]; + key_queue_head = (key_queue_head + 1) % KEY_QUEUE_CAPACITY; + key_queue_count--; + return true; +} + +bool sdl_input_has_queued_key() { + return key_queue_count > 0; +} diff --git a/Devices/simulator/Source/drivers/sdl_input.h b/Devices/simulator/Source/drivers/sdl_input.h new file mode 100644 index 00000000..8e7158ef --- /dev/null +++ b/Devices/simulator/Source/drivers/sdl_input.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * @brief Latest pointer (mouse) state as tracked by sdl_input_pump(). + */ +struct SdlPointerState { + int32_t x; + int32_t y; + bool pressed; +}; + +/** + * @brief Drains all pending SDL events exactly once, updating the pointer state and key queue + * below. Safe to call from both the sdl-pointer and sdl-keyboard drivers' polling functions: + * SDL_PollEvent() drains a single global queue, so whichever driver is polled first on a given + * LVGL indev tick pumps events for both. + */ +void sdl_input_pump(void); + +/** + * @brief Gets the pointer state as of the most recent sdl_input_pump() call. + */ +void sdl_input_get_pointer_state(struct SdlPointerState* out_state); + +/** + * @brief Pops the next queued key event (produced by SDL_KEYDOWN/SDL_TEXTINPUT during + * sdl_input_pump()). + * @retval false when no key event is pending + */ +bool sdl_input_pop_key(uint32_t* out_key); + +/** + * @brief Returns true if another key event is queued after the one just popped. + */ +bool sdl_input_has_queued_key(void); + +#ifdef __cplusplus +} +#endif diff --git a/Devices/simulator/Source/drivers/sdl_keyboard.cpp b/Devices/simulator/Source/drivers/sdl_keyboard.cpp new file mode 100644 index 00000000..09be781c --- /dev/null +++ b/Devices/simulator/Source/drivers/sdl_keyboard.cpp @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "sdl_input.h" + +#include +#include +#include +#include + +// region Driver lifecycle + +static error_t start(Device*) { return ERROR_NONE; } +static error_t stop(Device*) { return ERROR_NONE; } + +// endregion + +// region KeyboardApi + +static error_t sdl_keyboard_read_key(Device*, KeyboardKeyData* data) { + sdl_input_pump(); + + uint32_t key = 0; + if (sdl_input_pop_key(&key)) { + data->key = key; + data->pressed = true; + data->continue_reading = sdl_input_has_queued_key(); + } else { + data->key = 0; + data->pressed = false; + data->continue_reading = false; + } + + return ERROR_NONE; +} + +// endregion + +static const KeyboardApi sdl_keyboard_api = { + .read_key = sdl_keyboard_read_key, + .get_backlight = nullptr, +}; + +extern Module simulator_module; + +Driver sdl_keyboard_driver = { + .name = "sdl-keyboard", + .compatible = (const char*[]) { "tactility,sdl-keyboard", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &sdl_keyboard_api, + .device_type = &KEYBOARD_TYPE, + .owner = &simulator_module, + .internal = nullptr +}; diff --git a/Devices/simulator/Source/drivers/sdl_pointer.cpp b/Devices/simulator/Source/drivers/sdl_pointer.cpp new file mode 100644 index 00000000..33b48bc1 --- /dev/null +++ b/Devices/simulator/Source/drivers/sdl_pointer.cpp @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "sdl_input.h" + +#include +#include +#include +#include + +// region Driver lifecycle + +static error_t start(Device*) { return ERROR_NONE; } +static error_t stop(Device*) { return ERROR_NONE; } + +// endregion + +// region PointerApi + +static error_t sdl_pointer_read_data(Device*, TickType_t) { + sdl_input_pump(); + return ERROR_NONE; +} + +static bool sdl_pointer_get_touched_points(Device*, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_count, uint8_t max_point_count) { + SdlPointerState state; + sdl_input_get_pointer_state(&state); + + if (!state.pressed || max_point_count == 0) { + *point_count = 0; + return false; + } + + x[0] = static_cast(state.x); + y[0] = static_cast(state.y); + if (strength != nullptr) { + strength[0] = 0xFFFF; + } + *point_count = 1; + return true; +} + +// endregion + +static const PointerApi sdl_pointer_api = { + .enter_sleep = nullptr, + .exit_sleep = nullptr, + .read_data = sdl_pointer_read_data, + .get_touched_points = sdl_pointer_get_touched_points, + .set_swap_xy = nullptr, + .get_swap_xy = nullptr, + .set_mirror_x = nullptr, + .get_mirror_x = nullptr, + .set_mirror_y = nullptr, + .get_mirror_y = nullptr, +}; + +extern Module simulator_module; + +Driver sdl_pointer_driver = { + .name = "sdl-pointer", + .compatible = (const char*[]) { "tactility,sdl-pointer", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &sdl_pointer_api, + .device_type = &POINTER_TYPE, + .owner = &simulator_module, + .internal = nullptr +}; diff --git a/Devices/simulator/Source/hal/SdlDisplay.h b/Devices/simulator/Source/hal/SdlDisplay.h deleted file mode 100644 index 99f8aa65..00000000 --- a/Devices/simulator/Source/hal/SdlDisplay.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include "SdlTouch.h" -#include -#include - -class SdlDisplay final : public tt::hal::display::DisplayDevice { - - lv_disp_t* displayHandle = nullptr; - -public: - - std::string getName() const override { return "SDL Display"; } - std::string getDescription() const override { return ""; } - - bool start() override { return true; } - - bool stop() override { return true; } - - bool supportsLvgl() const override { return true; } - - bool startLvgl() override { - if (displayHandle) return true; // already started - displayHandle = lv_sdl_window_create(320, 240); - lv_sdl_window_set_title(displayHandle, "Tactility"); - return displayHandle != nullptr; - } - - bool stopLvgl() override { - if (!displayHandle) return true; - lv_display_delete(displayHandle); - displayHandle = nullptr; - return true; - } - - lv_display_t* getLvglDisplay() const override { return displayHandle; } - - std::shared_ptr getTouchDevice() override { return std::make_shared(); } - - bool supportsDisplayDriver() const override { return false; } - std::shared_ptr getDisplayDriver() override { return nullptr; } -}; diff --git a/Devices/simulator/Source/hal/SdlKeyboard.h b/Devices/simulator/Source/hal/SdlKeyboard.h deleted file mode 100644 index 876bf70d..00000000 --- a/Devices/simulator/Source/hal/SdlKeyboard.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include -#include -#include - -class SdlKeyboard final : public tt::hal::keyboard::KeyboardDevice { - - lv_indev_t* handle = nullptr; - -public: - - std::string getName() const override { return "SDL Keyboard"; } - std::string getDescription() const override { return "SDL keyboard device"; } - - bool startLvgl(lv_display_t* display) override { - handle = lv_sdl_keyboard_create(); - return handle != nullptr; - } - - bool stopLvgl() override { check(false, "Not supported"); } - - bool isAttached() const override { return true; } - - lv_indev_t* getLvglIndev() override { return handle; } -}; diff --git a/Devices/simulator/Source/hal/SdlTouch.h b/Devices/simulator/Source/hal/SdlTouch.h deleted file mode 100644 index 09ebc757..00000000 --- a/Devices/simulator/Source/hal/SdlTouch.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include "Tactility/hal/touch/TouchDevice.h" -#include -#include - -class SdlTouch final : public tt::hal::touch::TouchDevice { - - lv_indev_t* handle = nullptr; - -public: - - std::string getName() const override { return "SDL Mouse"; } - - std::string getDescription() const override { return "SDL mouse/touch pointer device"; } - - bool start() override { return true; } - - bool stop() override { check(false, "Not supported"); } - - bool supportsLvgl() const override { return true; } - - bool startLvgl(lv_display_t* display) override { - handle = lv_sdl_mouse_create(); - return handle != nullptr; - } - - bool stopLvgl() override { check(false, "Not supported"); } - - lv_indev_t* getLvglIndev() override { return handle; } - - bool supportsTouchDriver() override { return false; } - - std::shared_ptr getTouchDriver() override { return nullptr; }; -}; - diff --git a/Devices/simulator/Source/hal/SimulatorPower.cpp b/Devices/simulator/Source/hal/SimulatorPower.cpp deleted file mode 100644 index 84450bc0..00000000 --- a/Devices/simulator/Source/hal/SimulatorPower.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "SimulatorPower.h" - -constexpr auto* TAG = "SimulatorPower"; - -bool SimulatorPower::supportsMetric(MetricType type) const { - switch (type) { - using enum MetricType; - case IsCharging: - case Current: - case BatteryVoltage: - case ChargeLevel: - return true; - } - - return false; // Safety guard for when new enum values are introduced -} - -bool SimulatorPower::getMetric(MetricType type, MetricData& data) { - switch (type) { - using enum MetricType; - case IsCharging: - data.valueAsBool = true; - return true; - case Current: - data.valueAsInt32 = 42; - return true; - case BatteryVoltage: - data.valueAsUint32 = 4032; - return true; - case ChargeLevel: - data.valueAsUint8 = 100; - return true; - } - - return false; // Safety guard for when new enum values are introduced -} diff --git a/Devices/simulator/Source/hal/SimulatorPower.h b/Devices/simulator/Source/hal/SimulatorPower.h deleted file mode 100644 index f2b6bc91..00000000 --- a/Devices/simulator/Source/hal/SimulatorPower.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include -#include - -using tt::hal::power::PowerDevice; - -class SimulatorPower final : public PowerDevice { - - bool allowedToCharge = false; - -public: - - SimulatorPower() = default; - ~SimulatorPower() override = default; - - std::string getName() const override { return "Power Mock"; } - std::string getDescription() const override { return ""; } - - bool supportsMetric(MetricType type) const override; - bool getMetric(MetricType type, MetricData& data) override; - - bool supportsChargeControl() const override { return true; } - bool isAllowedToCharge() const override { return allowedToCharge; } - void setAllowedToCharge(bool canCharge) override { allowedToCharge = canCharge; } -}; diff --git a/Devices/simulator/Source/module.cpp b/Devices/simulator/Source/module.cpp index 3d1008a8..393ba5d4 100644 --- a/Devices/simulator/Source/module.cpp +++ b/Devices/simulator/Source/module.cpp @@ -1,23 +1,108 @@ +#include "drivers/sdl_display.h" + +#include +#include +#include +#include +#include #include +#include + +constexpr auto* TAG = "Simulator"; + +extern "C" { + +extern Driver sdl_display_driver; +extern Driver sdl_pointer_driver; +extern Driver sdl_keyboard_driver; + +static Driver* const simulator_drivers[] = { + &sdl_display_driver, + &sdl_pointer_driver, + &sdl_keyboard_driver, + nullptr +}; + +} + +// These devices have no real bus to attach to (SDL has no notion of one), but every non-root +// device is still expected to have a parent (see Device::parent) - they're parented to root once +// it's available below. +static const SdlDisplayConfig sdl_display_config = { 320, 240 }; +static Device sdl_display_device {}; +static Device sdl_pointer_device {}; +static Device sdl_keyboard_device {}; + +static bool construct_add_start(Device* device, Device* parent, const char* name, const void* config, const char* compatible) { + device->address = 0; + device->name = name; + device->config = config; + device->parent = nullptr; + device->internal = nullptr; + + error_t error = device_construct(device); + if (error != ERROR_NONE) { + LOG_E(TAG, "Failed to construct %s: %s", name, error_to_string(error)); + return false; + } + + device_set_parent(device, parent); + + Driver* driver = driver_find_compatible(compatible); + if (driver == nullptr) { + LOG_E(TAG, "No driver registered for %s", compatible); + device_destruct(device); + return false; + } + device_set_driver(device, driver); + + if (device_add(device) != ERROR_NONE) { + LOG_E(TAG, "Failed to add %s", name); + device_destruct(device); + return false; + } + + if (device_start(device) != ERROR_NONE) { + LOG_E(TAG, "Failed to start %s", name); + device_remove(device); + device_destruct(device); + return false; + } + + return true; +} + +// Root is only constructed/added/started after all dts_modules (including this one) have already +// started (see kernel_init()), so it can't be looked up by name from this module's own start() - +// wait for its DEVICE_EVENT_STARTED instead, same as e.g. m5stack-tab5's display/keyboard detection. +static void on_root_started(Device* device, DeviceEvent event, void* context) { + if (event != DEVICE_EVENT_STARTED || strcmp(device->name, "/") != 0) { + return; + } + + construct_add_start(&sdl_display_device, device, "display0", &sdl_display_config, "tactility,sdl-display"); + construct_add_start(&sdl_pointer_device, device, "pointer0", nullptr, "tactility,sdl-pointer"); + construct_add_start(&sdl_keyboard_device, device, "keyboard0", nullptr, "tactility,sdl-keyboard"); +} + extern "C" { static error_t start() { - // Empty for now + device_listener_add(on_root_started, nullptr); return ERROR_NONE; } static error_t stop() { - // Empty for now + device_listener_remove(on_root_started); return ERROR_NONE; } -struct Module simulator_module = { +Module simulator_module = { .name = "simulator", .start = start, .stop = stop, - .symbols = nullptr, - .internal = nullptr + .drivers = simulator_drivers }; } diff --git a/Documentation/README.md b/Documentation/README.md index 2ff6ed2a..628acd14 100644 --- a/Documentation/README.md +++ b/Documentation/README.md @@ -64,11 +64,7 @@ Tests use Doctest and run on simulator (POSIX) target only: ```bash cmake -B buildsim -G Ninja ninja -C buildsim build-tests -cd buildsim && ctest # run all tests -./buildsim/Tests/TactilityKernel/TactilityKernelTests -./buildsim/Tests/Tactility/TactilityTests -./buildsim/Tests/TactilityFreeRtos/TactilityFreeRtosTests -./buildsim/Tests/crypt-module/CryptModuleTests +cd buildsim && ctest --test-dir Tests ``` ## Architecture @@ -77,7 +73,7 @@ cd buildsim && ctest # run all tests - **TactilityKernel** — C API kernel: device/driver/module lifecycle, concurrency primitives (thread, mutex, timer, dispatcher), filesystem, logging. Header convention: `` (lowercase snake_case). - **TactilityFreeRtos** — Thin C++ wrappers around FreeRTOS primitives. -- **Tactility** — Main OS layer: app framework, service framework, HAL (deprecated, replaced by TactilityKernel), LVGL integration, networking and services (Wi-Fi, BLE, NTP, ESP-NOW), settings, i18n. +- **Tactility** — Main OS layer: app framework, service framework, LVGL integration, networking and services (Wi-Fi, BLE, NTP, ESP-NOW), settings, i18n. - **TactilityC** — C bindings (`tt_*.h`) for Tactility, used by side-loaded ELF apps on ESP32. Deprecated, replaced by TactilityKernel. - **Firmware** — Entry point (`app_main`). @@ -100,34 +96,28 @@ Apps implement `tt::app::App` (or just provide callbacks). Each app has an `AppM Services implement `tt::service::Service` with a `ServiceManifest`. Services are long-running background processes (GUI, Wi-Fi, loader, statusbar, GPS, etc.). -### HAL Layer - -#### Deprecated HAL - -Located in Tactility folder. - -`tt::hal::Configuration` is declared per-device board (in `Devices//Source/Configuration.cpp`). It provides `initBoot` for early hardware setup and `createDevices` to instantiate HAL device wrappers (display, touch, power, keyboard, etc.). - -#### Current HAL - -Located in TactilityKernel. Based on Linux driver subsystems. +### Hardware Abstraction Layer #### Driver A driver generally consists of: -- Registration of driver in parent module (optional) +- Registration of driver in parent module (optional, but desirable) - YAML bindings in the `bindings/` folder - An `#include` that is used in the `.dts` file. The include is in `[projectname]/bindings/[drivername].h` -- The driver implementation: a `.cpp` and `.h` file. The implementation is C++, but the header exposes pure C functions. +- The driver implementation: a `.cpp` and `.h` file. The implementation is C++, but the header exposes pure C functions. C implementations are allowed, but C++ is preferred. -Drivers can be stored in: +Drivers are part of a kernel module. + +Modules with drivers can be stored in: - TactilityKernel -- A subproject in Platforms/ folder -- A subproject in Devices/ folder -- A subproject in Drivers/ folder. This is a kernel module. Naming is lower case and postfixed with `-module` +- A subproject in `Platforms` folder +- A subproject in `Devices` folder +- A subproject in `Drivers` folder #### Kernel Modules +Kernel module names are lower case and postfixed with `-module`. + Projects that are kernel modules: 1. Declare a `struct Module` @@ -169,6 +159,6 @@ Pointers are expected to be non-null unless documented otherwise. - `#ifdef ESP_PLATFORM` guards ESP32-specific code; the simulator uses POSIX equivalents. - The `Drivers/` directory contains hardware drivers (display controllers, touch controllers, PMICs, etc.) — each is its own CMake component. -- `Modules/` contains cross-cutting modules: `hal-device-module` (device lifecycle) and `lvgl-module` (LVGL task management). +- `Modules/` contains cross-cutting modules. e.g.`lvgl-module` (LVGL task management). - `Data/system/` and `Data/data/` are flashed as FAT filesystem images on ESP32. - Translations are in `Translations/` as CSV files, generated via `generate.py`. \ No newline at end of file diff --git a/Documentation/ideas.md b/Documentation/ideas.md index f418b756..ae2d7b0d 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -13,6 +13,7 @@ ## Higher Priority +- Remove and migrate `Include/Tactility/kernel/Kernel.h` into `tactility/delay.h` - Drivers/audio-codec-module is not a module. Move it somewhere else. Or make it an actual module. - LilyGO T-Dongle S3: 1 button control, stop auto-launching web server - Core2: support power off via software diff --git a/Drivers/gps-generic-module/CMakeLists.txt b/Drivers/gps-generic-module/CMakeLists.txt new file mode 100644 index 00000000..828c1297 --- /dev/null +++ b/Drivers/gps-generic-module/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.20) + +include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") + +file(GLOB_RECURSE SOURCE_FILES "source/*.c*") + +tactility_add_module(gps-generic-module + SRCS ${SOURCE_FILES} + PRIV_INCLUDE_DIRS private/ + INCLUDE_DIRS include/ + REQUIRES TactilityKernel gps-module minmea +) diff --git a/Drivers/gps-generic-module/LICENSE-GPL-3.0.md b/Drivers/gps-generic-module/LICENSE-GPL-3.0.md new file mode 100644 index 00000000..5a9d38dd --- /dev/null +++ b/Drivers/gps-generic-module/LICENSE-GPL-3.0.md @@ -0,0 +1,677 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible feature +that (1) displays an appropriate copyright notice, and (2) tells the +user that there is no warranty for the work (except to the extent that +warranties are provided), that licensees may convey the work under this +License, and how to view a copy of this License. If the interface +presents a list of user commands or options, such as a menu, a +prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that is +widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system (if +any) on which the executable work runs, or a compiler used to produce +the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include +a requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not convey it at all. For example, if you agree to terms that +obligate you to collect a royalty for further conveying from those to +whom you convey the Program, the only way you could satisfy both those +terms and this License would be to refrain entirely from conveying the +Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your +program into proprietary programs. If your program is a subroutine +library, you may consider it more useful to permit linking proprietary +applications with the library. If this is what you want to do, use +the GNU Lesser General Public License instead of this License. But +first, please read . diff --git a/Drivers/gps-generic-module/README.md b/Drivers/gps-generic-module/README.md new file mode 100644 index 00000000..167979d8 --- /dev/null +++ b/Drivers/gps-generic-module/README.md @@ -0,0 +1,14 @@ +# gps-generic-module + +Kernel driver implementing the `GPS_TYPE`/`GpsApi` interface with for generic UART-connected GPS/GNSS receivers: +NMEA parsing for MTK, Airoha/AG33xx, ATGM336H/CASIC, Unicore UC6580 and u-blox 6/7/8/9/10 modules. + +It is ported from [Meshtastic Firmware](https://github.com/MeshTastic/firmware), so it has a GPL v3.0 license. + +## License + +This module is licensed under **GPL-3.0-or-later** (see `LICENSE-GPL-3.0.md`), separately from +the rest of Tactility (Apache-2.0). The probing and initialization logic (`source/probe.cpp`, +`source/init.cpp`, `source/ublox.cpp` and their private headers) is ported from +[meshtastic/firmware](https://github.com/meshtastic/firmware) (GPL-3.0-or-later); see the +`From: ` comments in those files for the exact origin of each ported function. diff --git a/Drivers/gps-generic-module/bindings/tactility,gps-generic.yaml b/Drivers/gps-generic-module/bindings/tactility,gps-generic.yaml new file mode 100644 index 00000000..f7122434 --- /dev/null +++ b/Drivers/gps-generic-module/bindings/tactility,gps-generic.yaml @@ -0,0 +1,17 @@ +description: > + Generic UART-connected GPS/GNSS receiver. Supports MTK, Airoha/AG33xx, ATGM336H/CASIC, + Unicore UC6580 and u-blox 6/7/8/9/10 chipsets, either auto-probed or fixed via 'model'. + +compatible: "tactility,gps-generic" + +properties: + baud-rate: + type: int + required: true + description: UART baud rate, e.g. 9600 or 38400 + model: + type: int + default: 0 + description: | + GpsModel enum value (see gps/gps.h, e.g. GPS_MODEL_UBLOX10 = 12). Defaults to + GPS_MODEL_UNKNOWN (0), which triggers an autoprobe on start(). diff --git a/Drivers/gps-generic-module/devicetree.yaml b/Drivers/gps-generic-module/devicetree.yaml new file mode 100644 index 00000000..e3dee67d --- /dev/null +++ b/Drivers/gps-generic-module/devicetree.yaml @@ -0,0 +1,4 @@ +dependencies: + - TactilityKernel + - Modules/gps-module +bindings: bindings diff --git a/Drivers/gps-generic-module/include/gps_generic/bindings.h b/Drivers/gps-generic-module/include/gps_generic/bindings.h new file mode 100644 index 00000000..a6496127 --- /dev/null +++ b/Drivers/gps-generic-module/include/gps_generic/bindings.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#pragma once + +#include +#include + +DEFINE_DEVICETREE(gps_generic, struct GpsConfig) diff --git a/Drivers/gps-generic-module/include/gps_generic/gps_generic.h b/Drivers/gps-generic-module/include/gps_generic/gps_generic.h new file mode 100644 index 00000000..ecfdf526 --- /dev/null +++ b/Drivers/gps-generic-module/include/gps_generic/gps_generic.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include +#include + +/** + * @brief Devicetree configuration for a generic UART-connected GPS/GNSS receiver. + */ +struct GpsConfig { + uint32_t baud_rate; + /** GPS_MODEL_UNKNOWN triggers an autoprobe on start(); the detected model is then available via get_model(). */ + enum GpsModel model; +}; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/gps-generic-module/include/gps_generic/gps_generic_module.h b/Drivers/gps-generic-module/include/gps_generic/gps_generic_module.h new file mode 100644 index 00000000..03d1c041 --- /dev/null +++ b/Drivers/gps-generic-module/include/gps_generic/gps_generic_module.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module gps_generic_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/gps-generic-module/private/gps_generic/private/cas_messages.h b/Drivers/gps-generic-module/private/gps_generic/private/cas_messages.h new file mode 100644 index 00000000..011944af --- /dev/null +++ b/Drivers/gps-generic-module/private/gps_generic/private/cas_messages.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +// NEMA message IDs + +constexpr uint8_t CAS_NEMA_GGA = 0x00; +constexpr uint8_t CAS_NEMA_GLL = 0x01; +constexpr uint8_t CAS_NEMA_GSA = 0x02; +constexpr uint8_t CAS_NEMA_GSV = 0x03; +constexpr uint8_t CAS_NEMA_RMC = 0x04; +constexpr uint8_t CAS_NEMA_VTG = 0x05; +constexpr uint8_t CAS_NEMA_GST = 0x07; +constexpr uint8_t CAS_NEMA_ZDA = 0x08; +constexpr uint8_t CAS_NEMA_DHV = 0x0D; + +/** Size of a CAS-ACK-(N)ACK message */ +constexpr size_t CAS_MESSAGE_ACK_NACK_SIZE = 0x0E; // 14 bytes + +/** Factory reset message */ +constexpr uint8_t CAS_MESSAGE_CFG_RST_FACTORY[] = { + 0xFF, 0x03, + 0x01, + 0x03 +}; + +/** Configure update rate to 1 Hz. */ +constexpr uint8_t CAS_MESSAGE_CFG_RATE_1HZ[] = { + 0xE8, 0x03, // 0x03E8 = 1000ms + 0x00, 0x00 +}; + +/** Config navx */ +constexpr uint8_t CAS_MESSAGE_CFG_NAVX_CONF[] = { + 0x03, 0x01, 0x00, 0x00, + 0x03, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x07, + 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; diff --git a/Drivers/gps-generic-module/private/gps_generic/private/gps_response.h b/Drivers/gps-generic-module/private/gps_generic/private/gps_response.h new file mode 100644 index 00000000..4f8fdabd --- /dev/null +++ b/Drivers/gps-generic-module/private/gps_generic/private/gps_response.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#pragma once + +// Internal-only result of waiting for a chip's ACK/NACK response during probing/initialization. +// Not part of the public API (see gps/gps.h) - callers only ever see GpsState/GpsModel. +enum class GpsResponse { + None, + NotAck, + FrameErrors, + Ok, +}; diff --git a/Drivers/gps-generic-module/private/gps_generic/private/init.h b/Drivers/gps-generic-module/private/gps_generic/private/init.h new file mode 100644 index 00000000..6c5cf89e --- /dev/null +++ b/Drivers/gps-generic-module/private/gps_generic/private/init.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#pragma once + +#include + +struct Device; + +/** + * Sends the init sequence for a specific, already-probed GPS model over uart. + */ +bool gps_init(Device* uart, GpsModel model); diff --git a/Drivers/gps-generic-module/private/gps_generic/private/probe.h b/Drivers/gps-generic-module/private/gps_generic/private/probe.h new file mode 100644 index 00000000..5e2dc9c0 --- /dev/null +++ b/Drivers/gps-generic-module/private/gps_generic/private/probe.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#pragma once + +#include + +struct Device; + +/** + * Attempts to auto-detect the GPS/GNSS chipset connected via uart. + * @return GPS_MODEL_UNKNOWN when no supported chipset responded + */ +GpsModel gps_probe(Device* uart); diff --git a/Drivers/gps-generic-module/private/gps_generic/private/ublox.h b/Drivers/gps-generic-module/private/gps_generic/private/ublox.h new file mode 100644 index 00000000..d7465cc7 --- /dev/null +++ b/Drivers/gps-generic-module/private/gps_generic/private/ublox.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#pragma once + +#include + +#include +#include + +struct Device; + +namespace gps_ublox { + +void checksum(uint8_t* message, size_t length); + +// From https://github.com/meshtastic/firmware/blob/7648391f91f2b84e367ae2b38220b30936fb45b1/src/gps/GPS.cpp#L128 +uint8_t make_packet(uint8_t class_id, uint8_t message_id, const uint8_t* payload, uint8_t payload_size, uint8_t* buffer_out); + +GpsModel probe(Device* uart); + +bool init(Device* uart, GpsModel model); + +} diff --git a/Tactility/Private/Tactility/hal/gps/UbloxMessages.h b/Drivers/gps-generic-module/private/gps_generic/private/ublox_messages.h similarity index 99% rename from Tactility/Private/Tactility/hal/gps/UbloxMessages.h rename to Drivers/gps-generic-module/private/gps_generic/private/ublox_messages.h index 52092e9c..5fd71d1b 100644 --- a/Tactility/Private/Tactility/hal/gps/UbloxMessages.h +++ b/Drivers/gps-generic-module/private/gps_generic/private/ublox_messages.h @@ -1,8 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0-or-later #pragma once #include -namespace tt::hal::gps::ublox { +namespace gps_ublox { // Power Management @@ -466,4 +467,4 @@ BBR layer config message: b5 62 06 8a 0e 00 00 02 00 00 20 00 31 10 00 05 00 31 10 00 47 94 */ -} \ No newline at end of file +} diff --git a/Drivers/gps-generic-module/source/gps_generic.cpp b/Drivers/gps-generic-module/source/gps_generic.cpp new file mode 100644 index 00000000..beaf767a --- /dev/null +++ b/Drivers/gps-generic-module/source/gps_generic.cpp @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include // For calloc() in PC builds + +constexpr auto* TAG = "gps-generic"; + +#define GET_CONFIG(device) (static_cast((device)->config)) + +constexpr uint32_t GPS_UART_BUFFER_SIZE = 256; +constexpr TickType_t GPS_THREAD_STOP_TIMEOUT_TICKS = pdMS_TO_TICKS(5000); +constexpr TickType_t GPS_THREAD_STOP_POLL_TICKS = pdMS_TO_TICKS(1000); + +struct GpsInternal { + RecursiveMutex mutex; + Thread* thread; + volatile bool interrupt_requested; + GpsState state; + // Mirrors GpsConfig::model, but overwritten with the autodetected model once probing succeeds. + GpsModel model; + // Singly-linked list of subscribers, guarded by `mutex`. + GpsSubscription* subscribers; +}; + +static const char* gpsModelToString(GpsModel model) { + switch (model) { + case GPS_MODEL_AG3335: + return "AG3335"; + case GPS_MODEL_AG3352: + return "AG3352"; + case GPS_MODEL_ATGM336H: + return "ATGM336H"; + case GPS_MODEL_LS20031: + return "LS20031"; + case GPS_MODEL_MTK: + return "MTK"; + case GPS_MODEL_MTK_L76B: + return "MTK L76B"; + case GPS_MODEL_MTK_PA1616S: + return "MTK PA1616S"; + case GPS_MODEL_UBLOX6: + return "U-blox 6"; + case GPS_MODEL_UBLOX7: + return "U-blox 7"; + case GPS_MODEL_UBLOX8: + return "U-blox 8"; + case GPS_MODEL_UBLOX9: + return "U-blox 9"; + case GPS_MODEL_UBLOX10: + return "U-blox 10"; + case GPS_MODEL_UC6580: + return "UC6580"; + case GPS_MODEL_UNKNOWN: + return "Auto-detect"; + default: + return "Unknown"; + } +} + +// Pushes `event` to every current subscriber and wakes their waiting task. Safe to call from the +// GPS thread's parsing loop. +static void notify_subscribers(GpsInternal* internal, const GpsEvent& event) { + recursive_mutex_lock(&internal->mutex); + + for (GpsSubscription* sub = internal->subscribers; sub != nullptr; sub = sub->next) { + sub->event = event; + sub->sequence++; + xTaskNotifyGive(sub->task); + } + + recursive_mutex_unlock(&internal->mutex); +} + +static void set_state(GpsInternal* internal, GpsState state) { + recursive_mutex_lock(&internal->mutex); + internal->state = state; + recursive_mutex_unlock(&internal->mutex); +} + +static bool is_interrupted(GpsInternal* internal) { + recursive_mutex_lock(&internal->mutex); + bool result = internal->interrupt_requested; + recursive_mutex_unlock(&internal->mutex); + return result; +} + +// region Driver lifecycle + +static int32_t gps_thread_main(void* context) { + auto* device = static_cast(context); + auto* internal = static_cast(device_get_driver_data(device)); + const auto* config = GET_CONFIG(device); + auto* uart = device_get_parent(device); + check(uart); + check(device_get_type(uart) == &UART_CONTROLLER_TYPE); + + const UartConfig uart_config = { + .baud_rate = config->baud_rate, + .data_bits = UART_CONTROLLER_DATA_8_BITS, + .parity = UART_CONTROLLER_PARITY_DISABLE, + .stop_bits = UART_CONTROLLER_STOP_BITS_1 + }; + + if (uart_controller_set_config(uart, &uart_config) != ERROR_NONE) { + LOG_E(TAG, "Failed to configure UART %s", uart->name); + set_state(internal, GpsState::GPS_STATE_ERROR); + return -1; + } + + if (uart_controller_open(uart) != ERROR_NONE) { + LOG_E(TAG, "Failed to open UART %s", uart->name); + set_state(internal, GpsState::GPS_STATE_ERROR); + return -1; + } + + GpsModel model = internal->model; + if (model == GpsModel::GPS_MODEL_UNKNOWN) { + model = gps_probe(uart); + if (model == GpsModel::GPS_MODEL_UNKNOWN) { + LOG_E(TAG, "Probe failed"); + set_state(internal, GpsState::GPS_STATE_ERROR); + return -1; + } + recursive_mutex_lock(&internal->mutex); + internal->model = model; + recursive_mutex_unlock(&internal->mutex); + } + + if (!gps_init(uart, model)) { + LOG_E(TAG, "Init failed"); + set_state(internal, GpsState::GPS_STATE_ERROR); + return -1; + } + + set_state(internal, GpsState::GPS_STATE_ON); + + // Reference: https://gpsd.gitlab.io/gpsd/NMEA.html + uint8_t buffer[GPS_UART_BUFFER_SIZE]; + while (!is_interrupted(internal)) { + size_t bytes_read = 0; + uart_controller_read_until(uart, buffer, sizeof(buffer), '\n', true, &bytes_read, pdMS_TO_TICKS(100)); + + // Thread might've been interrupted in the meanwhile + if (is_interrupted(internal)) { + break; + } + + if (bytes_read > 0U) { + switch (minmea_sentence_id(reinterpret_cast(buffer), false)) { + case MINMEA_SENTENCE_RMC: { + GpsEvent event { .type = GPS_EVENT_MESSAGE_RMC }; + if (minmea_parse_rmc(&event.data.rmc, reinterpret_cast(buffer))) { + notify_subscribers(internal, event); + } else { + LOG_E(TAG, "RMC parse error: %s", reinterpret_cast(buffer)); + } + break; + } + case MINMEA_SENTENCE_GGA: { + GpsEvent event { .type = GPS_EVENT_MESSAGE_GGA }; + if (minmea_parse_gga(&event.data.gga, reinterpret_cast(buffer))) { + notify_subscribers(internal, event); + } else { + LOG_E(TAG, "GGA parse error: %s", reinterpret_cast(buffer)); + } + break; + } + default: + break; + } + } + } + + if (uart_controller_close(uart) != ERROR_NONE) { + LOG_W(TAG, "Failed to close UART %s", uart->name); + } + + // Wake any subscribers still awaiting an event so they don't block forever on a device that's + // going away, then drop them - stop() is about to free `internal`. + notify_subscribers(internal, GpsEvent { .type = GPS_EVENT_UNSUBSCRIBED }); + recursive_mutex_lock(&internal->mutex); + internal->subscribers = nullptr; + recursive_mutex_unlock(&internal->mutex); + + set_state(internal, GPS_STATE_OFF); + return 0; +} + +static error_t start(Device* device) { + const auto* config = GET_CONFIG(device); + + auto* internal = static_cast(calloc(1, sizeof(GpsInternal))); + if (internal == nullptr) { + return ERROR_OUT_OF_MEMORY; + } + + recursive_mutex_construct(&internal->mutex); + internal->model = config->model; + internal->state = GPS_STATE_PENDING_ON; + internal->thread = thread_alloc_full("gps", 4096, gps_thread_main, device, -1); + if (internal->thread == nullptr) { + recursive_mutex_destruct(&internal->mutex); + free(internal); + return ERROR_OUT_OF_MEMORY; + } + thread_set_priority(internal->thread, THREAD_PRIORITY_HIGH); + + device_set_driver_data(device, internal); + + if (thread_start(internal->thread) != ERROR_NONE) { + thread_free(internal->thread); + recursive_mutex_destruct(&internal->mutex); + free(internal); + device_set_driver_data(device, nullptr); + return ERROR_RESOURCE; + } + + return ERROR_NONE; +} + +static error_t stop(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + + recursive_mutex_lock(&internal->mutex); + internal->interrupt_requested = true; + internal->state = GPS_STATE_PENDING_OFF; + recursive_mutex_unlock(&internal->mutex); + + if (thread_join(internal->thread, GPS_THREAD_STOP_TIMEOUT_TICKS, GPS_THREAD_STOP_POLL_TICKS) != ERROR_NONE) { + LOG_E(TAG, "GPS thread for %s did not stop in time", device->name); + return ERROR_RESOURCE_BUSY; + } + thread_free(internal->thread); + + recursive_mutex_destruct(&internal->mutex); + free(internal); + device_set_driver_data(device, nullptr); + return ERROR_NONE; +} + +// endregion + +// region GpsApi + +static error_t gps_api_event_subscribe(Device* device, GpsSubscription* sub) { + auto* internal = static_cast(device_get_driver_data(device)); + + sub->task = xTaskGetCurrentTaskHandle(); + sub->sequence = 0; + sub->consumed_sequence = 0; + + recursive_mutex_lock(&internal->mutex); + sub->next = internal->subscribers; + internal->subscribers = sub; + recursive_mutex_unlock(&internal->mutex); + + return ERROR_NONE; +} + +static error_t gps_api_event_unsubscribe(Device* device, GpsSubscription* sub) { + auto* internal = static_cast(device_get_driver_data(device)); + + error_t result = ERROR_NOT_FOUND; + recursive_mutex_lock(&internal->mutex); + for (GpsSubscription** link = &internal->subscribers; *link != nullptr; link = &(*link)->next) { + if (*link == sub) { + *link = sub->next; + result = ERROR_NONE; + break; + } + } + recursive_mutex_unlock(&internal->mutex); + + return result; +} + +static error_t gps_api_event_await(Device*, GpsSubscription* sub, TickType_t timeout) { + uint32_t old_sequence = sub->sequence; + + while (sub->sequence == old_sequence) { + if (ulTaskNotifyTake(pdTRUE, timeout) == 0) { + return ERROR_TIMEOUT; + } + } + + sub->consumed_sequence = sub->sequence; + return ERROR_NONE; +} + +static GpsState gps_api_get_state(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + recursive_mutex_lock(&internal->mutex); + auto state = internal->state; + recursive_mutex_unlock(&internal->mutex); + return state; +} + +static error_t gps_api_get_model_name(Device* device, char* model_name, size_t buffer_size) { + const auto* config = GET_CONFIG(device); + const char* name_to_set = gpsModelToString(config->model); + snprintf(model_name, buffer_size, "%s", name_to_set); + return ERROR_NONE; +} + +// endregion + +static const GpsApi generic_gps_api = { + .event_subscribe = gps_api_event_subscribe, + .event_unsubscribe = gps_api_event_unsubscribe, + .event_await = gps_api_event_await, + .get_state = gps_api_get_state, + .get_model_name = gps_api_get_model_name +}; + +extern Module gps_generic_module; + +Driver generic_gps_driver = { + .name = "gps-generic", + .compatible = (const char*[]) { "tactility,gps-generic", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &generic_gps_api, + .device_type = &GPS_TYPE, + .owner = &gps_generic_module +}; diff --git a/Tactility/Source/hal/gps/GpsInit.cpp b/Drivers/gps-generic-module/source/init.cpp similarity index 73% rename from Tactility/Source/hal/gps/GpsInit.cpp rename to Drivers/gps-generic-module/source/init.cpp index f3501f4c..655d4e86 100644 --- a/Tactility/Source/hal/gps/GpsInit.cpp +++ b/Drivers/gps-generic-module/source/init.cpp @@ -1,31 +1,31 @@ -#include -#include -#include -#include +// SPDX-License-Identifier: GPL-3.0-or-later +#include +#include +#include +#include #include +#include #include #include #include +#include #include -namespace tt::hal::gps { +constexpr auto* TAG = "gps"; -constexpr auto* TAG = "Gps"; - -bool initMtk(::Device* uart); -bool initMtkL76b(::Device* uart); -bool initMtkPa1616s(::Device* uart); -bool initAtgm336h(::Device* uart); -bool initUc6580(::Device* uart); -bool initAg33xx(::Device* uart); +bool init_mtk(Device* uart); +bool init_mtk_l76b(Device* uart); +bool init_mtk_pa1616s(Device* uart); +bool init_atgm336h(Device* uart); +bool init_uc6580(Device* uart); +bool init_ag33xx(Device* uart); // region CAS // Calculate the checksum for a CAS packet -static void CASChecksum(uint8_t *message, size_t length) -{ +static void cas_checksum(uint8_t* message, size_t length) { uint32_t cksum = ((uint32_t)message[5] << 24); // Message ID cksum += ((uint32_t)message[4]) << 16; // Class cksum += message[2]; // Payload Len @@ -46,8 +46,7 @@ static void CASChecksum(uint8_t *message, size_t length) } // Function to create a CAS packet for editing in memory -static uint8_t makeCASPacket(uint8_t* buffer, uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg) -{ +static uint8_t make_cas_packet(uint8_t* buffer, uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t* msg) { // General CAS structure // | H1 | H2 | payload_len | cls | msg | Payload ... | Checksum | // Size: | 1 | 1 | 2 | 1 | 1 | payload_len | 4 | @@ -71,17 +70,16 @@ static uint8_t makeCASPacket(uint8_t* buffer, uint8_t class_id, uint8_t msg_id, for (int i = 0; i < payload_size; i++) { buffer[6 + i] = msg[i]; } - CASChecksum(buffer, (payload_size + 10)); + cas_checksum(buffer, (payload_size + 10)); return (payload_size + 10); } -GpsResponse getACKCas(::Device* uart, uint8_t class_id, uint8_t msg_id, uint32_t waitMillis) -{ - uint32_t startTime = kernel::getMillis(); - uint8_t buffer[CAS_ACK_NACK_MSG_SIZE] = {0}; - uint8_t bufferPos = 0; - TickType_t waitTicks = pdMS_TO_TICKS(waitMillis); +static GpsResponse get_ack_cas(Device* uart, uint8_t class_id, uint8_t msg_id, uint32_t wait_millis) { + uint32_t start_time = get_millis(); + uint8_t buffer[CAS_MESSAGE_ACK_NACK_SIZE] = {0}; + uint8_t buffer_pos = 0; + TickType_t wait_ticks = pdMS_TO_TICKS(wait_millis); // CAS-ACK-(N)ACK structure // | H1 | H2 | Payload Len | cls | msg | Payload | Checksum (4) | @@ -90,26 +88,26 @@ GpsResponse getACKCas(::Device* uart, uint8_t class_id, uint8_t msg_id, uint32_t // ACK-NACK| 0xBA | 0xCE | 0x04 | 0x00 | 0x05 | 0x00 | 0xXX | 0xXX | 0x00 | 0x00 | 0xXX | 0xXX | 0xXX | 0xXX | // ACK-ACK | 0xBA | 0xCE | 0x04 | 0x00 | 0x05 | 0x01 | 0xXX | 0xXX | 0x00 | 0x00 | 0xXX | 0xXX | 0xXX | 0xXX | - while (kernel::getTicks() - startTime < waitTicks) { + while (get_ticks() - start_time < wait_ticks) { size_t available = 0; uart_controller_get_available(uart, &available); if (available > 0) { - uart_controller_read_byte(uart, &buffer[bufferPos++], 1); + uart_controller_read_byte(uart, &buffer[buffer_pos++], 1); // keep looking at the first two bytes of buffer until // we have found the CAS frame header (0xBA, 0xCE), if not // keep reading bytes until we find a frame header or we run // out of time. - if ((bufferPos == 2) && !(buffer[0] == 0xBA && buffer[1] == 0xCE)) { + if ((buffer_pos == 2) && !(buffer[0] == 0xBA && buffer[1] == 0xCE)) { buffer[0] = buffer[1]; buffer[1] = 0; - bufferPos = 1; + buffer_pos = 1; } } // we have read all the bytes required for the Ack/Nack (14-bytes) // and we must have found a frame to get this far - if (bufferPos == sizeof(buffer) - 1) { + if (buffer_pos == sizeof(buffer) - 1) { uint8_t msg_cls = buffer[4]; // message class should be 0x05 uint8_t msg_msg_id = buffer[5]; // message id should be 0x00 or 0x01 uint8_t payload_cls = buffer[6]; // payload class id @@ -117,24 +115,18 @@ GpsResponse getACKCas(::Device* uart, uint8_t class_id, uint8_t msg_id, uint32_t // Check for an ACK-ACK for the specified class and message id if ((msg_cls == 0x05) && (msg_msg_id == 0x01) && payload_cls == class_id && payload_msg == msg_id) { -#ifdef GPS_DEBUG - LOG_I(TAG, "Got ACK for class %02X message %02X in %zu ms", class_id, msg_id, kernel::getMillis() - startTime); -#endif return GpsResponse::Ok; } // Check for an ACK-NACK for the specified class and message id if ((msg_cls == 0x05) && (msg_msg_id == 0x00) && payload_cls == class_id && payload_msg == msg_id) { -#ifdef GPS_DEBUG - LOG_W(TAG, "Got NACK for class %02X message %02X in %zu ms", class_id, msg_id, millis() - startTime); -#endif return GpsResponse::NotAck; } // This isn't the frame we are looking for, clear the buffer // and try again until we run out of time. memset(buffer, 0x0, sizeof(buffer)); - bufferPos = 0; + buffer_pos = 0; } } return GpsResponse::None; @@ -142,38 +134,38 @@ GpsResponse getACKCas(::Device* uart, uint8_t class_id, uint8_t msg_id, uint32_t // endregion -bool init(::Device* uart, GpsModel type) { +bool gps_init(Device* uart, GpsModel type) { switch (type) { - case GpsModel::Unknown: + case GPS_MODEL_UNKNOWN: check(false); - case GpsModel::AG3335: - case GpsModel::AG3352: - return initAg33xx(uart); - case GpsModel::ATGM336H: - return initAtgm336h(uart); - case GpsModel::LS20031: + case GPS_MODEL_AG3335: + case GPS_MODEL_AG3352: + return init_ag33xx(uart); + case GPS_MODEL_ATGM336H: + return init_atgm336h(uart); + case GPS_MODEL_LS20031: return true; - case GpsModel::MTK: - return initMtk(uart); - case GpsModel::MTK_L76B: - return initMtkL76b(uart); - case GpsModel::MTK_PA1616S: - return initMtkPa1616s(uart); - case GpsModel::UBLOX6: - case GpsModel::UBLOX7: - case GpsModel::UBLOX8: - case GpsModel::UBLOX9: - case GpsModel::UBLOX10: - return ublox::init(uart, type); - case GpsModel::UC6580: - return initUc6580(uart); + case GPS_MODEL_MTK: + return init_mtk(uart); + case GPS_MODEL_MTK_L76B: + return init_mtk_l76b(uart); + case GPS_MODEL_MTK_PA1616S: + return init_mtk_pa1616s(uart); + case GPS_MODEL_UBLOX6: + case GPS_MODEL_UBLOX7: + case GPS_MODEL_UBLOX8: + case GPS_MODEL_UBLOX9: + case GPS_MODEL_UBLOX10: + return gps_ublox::init(uart, type); + case GPS_MODEL_UC6580: + return init_uc6580(uart); } LOG_I(TAG, "Init not implemented %d", static_cast(type)); return false; } -bool initAg33xx(::Device* uart) { +bool init_ag33xx(Device* uart) { uart_controller_write_bytes(uart, (const uint8_t*)"$PAIR066,1,0,1,0,0,1*3B\r\n", 25, 250); // Enable GPS+GALILEO+NAVIC // Configure NMEA (sentences will output once per fix) @@ -185,47 +177,47 @@ bool initAg33xx(::Device* uart) { uart_controller_write_bytes(uart, (const uint8_t*)"$PAIR062,5,0*3B\r\n", 17, 250); // VTG OFF uart_controller_write_bytes(uart, (const uint8_t*)"$PAIR062,6,0*38\r\n", 17, 250); // ZDA ON - kernel::delayMillis(250); + delay_millis(250); uart_controller_write_bytes(uart, (const uint8_t*)"$PAIR513*3D\r\n", 13, 250); // save configuration return true; } -bool initUc6580(::Device* uart) { +bool init_uc6580(Device* uart) { // The Unicore UC6580 can use a lot of sat systems, enable it to // use GPS L1 & L5 + BDS B1I & B2a + GLONASS L1 + GALILEO E1 & E5a + SBAS + QZSS // This will reset the receiver, so wait a bit afterwards // The paranoid will wait for the OK*04 confirmation response after each command. uart_controller_write_bytes(uart, (const uint8_t*)"$CFGSYS,h35155\r\n", 16, 250); - kernel::delayMillis(750); + delay_millis(750); // Must be done after the CFGSYS command // Turn off GSV messages, we don't really care about which and where the sats are, maybe someday. uart_controller_write_bytes(uart, (const uint8_t*)"$CFGMSG,0,3,0\r\n", 15, 250); - kernel::delayMillis(250); + delay_millis(250); // Turn off GSA messages, TinyGPS++ doesn't use this message. uart_controller_write_bytes(uart, (const uint8_t*)"$CFGMSG,0,2,0\r\n", 15, 250); - kernel::delayMillis(250); + delay_millis(250); // Turn off NOTICE __TXT messages, these may provide Unicore some info but we don't care. uart_controller_write_bytes(uart, (const uint8_t*)"$CFGMSG,6,0,0\r\n", 15, 250); - kernel::delayMillis(250); + delay_millis(250); uart_controller_write_bytes(uart, (const uint8_t*)"$CFGMSG,6,1,0\r\n", 15, 250); - kernel::delayMillis(250); + delay_millis(250); return true; } -bool initAtgm336h(::Device* uart) { +bool init_atgm336h(Device* uart) { uint8_t buffer[256]; // Set the intial configuration of the device - these _should_ work for most AT6558 devices - int msglen = makeCASPacket(buffer, 0x06, 0x07, sizeof(_message_CAS_CFG_NAVX_CONF), _message_CAS_CFG_NAVX_CONF); + int msglen = make_cas_packet(buffer, 0x06, 0x07, sizeof(CAS_MESSAGE_CFG_NAVX_CONF), CAS_MESSAGE_CFG_NAVX_CONF); uart_controller_write_bytes(uart, buffer, msglen, 250); - if (getACKCas(uart, 0x06, 0x07, 250) != GpsResponse::Ok) { + if (get_ack_cas(uart, 0x06, 0x07, 250) != GpsResponse::Ok) { LOG_W(TAG, "ATGM336H: Could not set Config"); } // Set the update frequence to 1Hz - msglen = makeCASPacket(buffer, 0x06, 0x04, sizeof(_message_CAS_CFG_RATE_1HZ), _message_CAS_CFG_RATE_1HZ); + msglen = make_cas_packet(buffer, 0x06, 0x04, sizeof(CAS_MESSAGE_CFG_RATE_1HZ), CAS_MESSAGE_CFG_RATE_1HZ); uart_controller_write_bytes(uart, buffer, msglen, 250); - if (getACKCas(uart, 0x06, 0x04, 250) != GpsResponse::Ok) { + if (get_ack_cas(uart, 0x06, 0x04, 250) != GpsResponse::Ok) { LOG_W(TAG, "ATGM336H: Could not set Update Frequency"); } @@ -235,64 +227,62 @@ bool initAtgm336h(::Device* uart) { for (unsigned int i = 0; i < sizeof(fields); i++) { // Construct a CAS-CFG-MSG packet uint8_t cas_cfg_msg_packet[] = {0x4e, fields[i], 0x01, 0x00}; - msglen = makeCASPacket(buffer, 0x06, 0x01, sizeof(cas_cfg_msg_packet), cas_cfg_msg_packet); + msglen = make_cas_packet(buffer, 0x06, 0x01, sizeof(cas_cfg_msg_packet), cas_cfg_msg_packet); uart_controller_write_bytes(uart, buffer, msglen, 250); - if (getACKCas(uart, 0x06, 0x01, 250) != GpsResponse::Ok) { + if (get_ack_cas(uart, 0x06, 0x01, 250) != GpsResponse::Ok) { LOG_W(TAG, "ATGM336H: Could not enable NMEA MSG: %u", fields[i]); } } return true; } -bool initMtkPa1616s(::Device* uart) { +bool init_mtk_pa1616s(Device* uart) { // PA1616S is used in some GPS breakout boards from Adafruit // PA1616S does not have GLONASS capability. PA1616D does, but is not implemented here. uart_controller_write_bytes(uart, (const uint8_t*)"$PMTK353,1,0,0,0,0*2A\r\n", 23, 250); // Above command will reset the GPS and takes longer before it will accept new commands - kernel::delayMillis(1000); + delay_millis(1000); // Only ask for RMC and GGA (GNRMC and GNGGA) uart_controller_write_bytes(uart, (const uint8_t*)"$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n", 51, 250); - kernel::delayMillis(250); + delay_millis(250); // Enable SBAS / WAAS uart_controller_write_bytes(uart, (const uint8_t*)"$PMTK301,2*2E\r\n", 15, 250); - kernel::delayMillis(250); + delay_millis(250); return true; } -bool initMtkL76b(::Device* uart) { +bool init_mtk_l76b(Device* uart) { // Waveshare Pico-GPS hat uses the L76B with 9600 baud // Initialize the L76B Chip, use GPS + GLONASS // See note in L76_Series_GNSS_Protocol_Specification, chapter 3.29 uart_controller_write_bytes(uart, (const uint8_t*)"$PMTK353,1,1,0,0,0*2B\r\n", 23, 250); // Above command will reset the GPS and takes longer before it will accept new commands - kernel::delayMillis(1000); + delay_millis(1000); // only ask for RMC and GGA (GNRMC and GNGGA) // See note in L76_Series_GNSS_Protocol_Specification, chapter 2.1 uart_controller_write_bytes(uart, (const uint8_t*)"$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n", 51, 250); - kernel::delayMillis(250); + delay_millis(250); // Enable SBAS uart_controller_write_bytes(uart, (const uint8_t*)"$PMTK301,2*2E\r\n", 15, 250); - kernel::delayMillis(250); + delay_millis(250); // Enable PPS for 2D/3D fix only uart_controller_write_bytes(uart, (const uint8_t*)"$PMTK285,3,100*3F\r\n", 19, 250); - kernel::delayMillis(250); + delay_millis(250); // Switch to Fitness Mode, for running and walking purpose with low speed (<5 m/s) uart_controller_write_bytes(uart, (const uint8_t*)"$PMTK886,1*29\r\n", 15, 250); - kernel::delayMillis(250); + delay_millis(250); return true; } -bool initMtk(::Device* uart) { +bool init_mtk(Device* uart) { // Initialize the L76K Chip, use GPS + GLONASS + BEIDOU uart_controller_write_bytes(uart, (const uint8_t*)"$PCAS04,7*1E\r\n", 14, 250); - kernel::delayMillis(250); + delay_millis(250); // only ask for RMC and GGA uart_controller_write_bytes(uart, (const uint8_t*)"$PCAS03,1,0,0,0,1,0,0,0,0,0,,,0,0*02\r\n", 38, 250); - kernel::delayMillis(250); + delay_millis(250); // Switch to Vehicle Mode, since SoftRF enables Aviation < 2g uart_controller_write_bytes(uart, (const uint8_t*)"$PCAS11,3*1E\r\n", 14, 250); - kernel::delayMillis(250); + delay_millis(250); return true; } - -} // namespace tt::hal::gps diff --git a/Drivers/gps-generic-module/source/module.cpp b/Drivers/gps-generic-module/source/module.cpp new file mode 100644 index 00000000..13808508 --- /dev/null +++ b/Drivers/gps-generic-module/source/module.cpp @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#include +#include + +extern "C" { + +extern Driver generic_gps_driver; + +static Driver* const gps_generic_drivers[] = { + &generic_gps_driver, + nullptr +}; + +Module gps_generic_module = { + .name = "gps-generic", + .drivers = gps_generic_drivers +}; + +} diff --git a/Drivers/gps-generic-module/source/probe.cpp b/Drivers/gps-generic-module/source/probe.cpp new file mode 100644 index 00000000..68c08d15 --- /dev/null +++ b/Drivers/gps-generic-module/source/probe.cpp @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +constexpr auto* TAG = "Gps"; + +static char* probe_strnstr(const char* s, const char* find, size_t slen) { + char c; + if ((c = *find++) != '\0') { + char sc; + size_t len; + + len = strlen(find); + do { + do { + if (slen-- < 1 || (sc = *s++) == '\0') + return (nullptr); + } while (sc != c); + if (len > slen) + return (nullptr); + } while (strncmp(s, find, len) != 0); + s--; + } + return ((char*)s); +} + +static GpsResponse get_ack(Device* uart, const char* message, uint32_t wait_millis) { + uint8_t buffer[768] = {0}; + uint8_t b; + int bytes_read = 0; + uint32_t start_timeout = get_millis() + wait_millis; + while (get_millis() < start_timeout) { + size_t available = 0; + uart_controller_get_available(uart, &available); + if (available > 0) { + uart_controller_read_byte(uart, &b, 1); + + buffer[bytes_read] = b; + bytes_read++; + if ((bytes_read == 767) || (b == '\r')) { + if (probe_strnstr((char*)buffer, message, bytes_read) != nullptr) { + return GpsResponse::Ok; + } else { + bytes_read = 0; + } + } + } + } + return GpsResponse::None; +} + +#define PROBE_SIMPLE(UART, CHIP, TOWRITE, RESPONSE, DRIVER, TIMEOUT, ...) \ + do { \ + LOG_I(TAG, "Probing for %s (%s)", CHIP, TOWRITE); \ + uart_controller_flush_input(UART); \ + uart_controller_write_bytes(UART, (const uint8_t*)(TOWRITE "\r\n"), strlen(TOWRITE "\r\n"), TIMEOUT); \ + if (get_ack(UART, RESPONSE, TIMEOUT) == GpsResponse::Ok) { \ + LOG_I(TAG, "Probe detected %s %s", CHIP, #DRIVER); \ + return DRIVER; \ + } \ + } while (0) + +GpsModel gps_probe(Device* uart) { + // Close all NMEA sentences + // Valid for L76K, ATGM336H and likely other AT6558 devices + uart_controller_write_bytes(uart, reinterpret_cast("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n"), 40, 500); + delay_millis(20); + + // Close NMEA sequences on Ublox + uart_controller_write_bytes(uart, reinterpret_cast("$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n"), 29, 500); + uart_controller_write_bytes(uart, reinterpret_cast("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n"), 29, 500); + uart_controller_write_bytes(uart, reinterpret_cast("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n"), 29, 500); + delay_millis(20); + + // Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A + PROBE_SIMPLE(uart, "UC6580", "$PDTINFO", "UC6580", GpsModel::GPS_MODEL_UC6580, 500); + PROBE_SIMPLE(uart, "UM600", "$PDTINFO", "UM600", GpsModel::GPS_MODEL_UC6580, 500); + PROBE_SIMPLE(uart, "ATGM336H", "$PCAS06,1*1A", "$GPTXT,01,01,02,HW=ATGM336H", GpsModel::GPS_MODEL_ATGM336H, 500); + + // ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) based on AT6558 + PROBE_SIMPLE(uart, "ATGM332D", "$PCAS06,1*1A", "$GPTXT,01,01,02,HW=ATGM332D", GpsModel::GPS_MODEL_ATGM336H, 500); + + // Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M + // GSA OFF, reduce volume + uart_controller_write_bytes(uart, reinterpret_cast("$PAIR062,2,0*3C\r\n"), 17, 500); + // GSV OFF, reduce volume + uart_controller_write_bytes(uart, reinterpret_cast("$PAIR062,3,0*3D\r\n"), 17, 500); + // Save configuration + uart_controller_write_bytes(uart, reinterpret_cast("$PAIR513*3D\r\n"), 13, 500); + PROBE_SIMPLE(uart, "AG3335", "$PAIR021*39", "$PAIR021,AG3335", GpsModel::GPS_MODEL_AG3335, 500); + PROBE_SIMPLE(uart, "AG3352", "$PAIR021*39", "$PAIR021,AG3352", GpsModel::GPS_MODEL_AG3352, 500); + PROBE_SIMPLE(uart, "LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GpsModel::GPS_MODEL_AG3352, 500); + + PROBE_SIMPLE(uart, "L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GpsModel::GPS_MODEL_MTK, 500); + + // Close all NMEA sentences + // Valid for L76B MTK + uart_controller_write_bytes(uart, reinterpret_cast("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n"), 51, 500); + delay_millis(20); + + PROBE_SIMPLE(uart, "L76B", "$PMTK605*31", "Quectel-L76B", GpsModel::GPS_MODEL_MTK_L76B, 500); + PROBE_SIMPLE(uart, "PA1616S", "$PMTK605*31", "1616S", GpsModel::GPS_MODEL_MTK_PA1616S, 500); + + auto ublox_result = gps_ublox::probe(uart); + if (ublox_result != GPS_MODEL_UNKNOWN) { + return ublox_result; + } else { + LOG_W(TAG, "No GNSS Module"); + return GPS_MODEL_UNKNOWN; + } +} diff --git a/Tactility/Source/hal/gps/Ublox.cpp b/Drivers/gps-generic-module/source/ublox.cpp similarity index 72% rename from Tactility/Source/hal/gps/Ublox.cpp rename to Drivers/gps-generic-module/source/ublox.cpp index bed6861f..8462cc89 100644 --- a/Tactility/Source/hal/gps/Ublox.cpp +++ b/Drivers/gps-generic-module/source/ublox.cpp @@ -1,26 +1,32 @@ -#include -#include -#include +// SPDX-License-Identifier: GPL-3.0-or-later +#include +#include +#include +#include + +#include #include #include #include +#include #include +#include -namespace tt::hal::gps::ublox { +namespace gps_ublox { constexpr auto* TAG = "Ublox"; -bool initUblox6(::Device* uart); -bool initUblox789(::Device* uart, GpsModel model); -bool initUblox10(::Device* uart); +bool init_ublox_6(Device* uart); +bool init_ublox_789(Device* uart, GpsModel model); +bool init_ublox_10(Device* uart); #define SEND_UBX_PACKET(UART, BUFFER, TYPE, ID, DATA, ERRMSG, TIMEOUT_MILLIS) \ do { \ - auto msglen = makePacket(TYPE, ID, DATA, sizeof(DATA), BUFFER); \ + auto msglen = make_packet(TYPE, ID, DATA, sizeof(DATA), BUFFER); \ uart_controller_write_bytes(UART, BUFFER, msglen, TIMEOUT_MILLIS / portTICK_PERIOD_MS); \ - if (getAck(UART, TYPE, ID, TIMEOUT_MILLIS) != GpsResponse::Ok) { \ + if (get_ack(UART, TYPE, ID, TIMEOUT_MILLIS) != GpsResponse::Ok) { \ LOG_I(TAG, "Sending packet failed: %s", #ERRMSG); \ } \ } while (0) @@ -39,37 +45,34 @@ void checksum(uint8_t* message, size_t length) { message[length - 1] = CK_B; } -uint8_t makePacket(uint8_t classId, uint8_t messageId, const uint8_t* payload, uint8_t payloadSize, uint8_t* bufferOut) { +uint8_t make_packet(uint8_t class_id, uint8_t message_id, const uint8_t* payload, uint8_t payload_size, uint8_t* buffer_out) { // Construct the UBX packet - bufferOut[0] = 0xB5U; // header - bufferOut[1] = 0x62U; // header - bufferOut[2] = classId; // class - bufferOut[3] = messageId; // id - bufferOut[4] = payloadSize; // length - bufferOut[5] = 0x00U; + buffer_out[0] = 0xB5U; // header + buffer_out[1] = 0x62U; // header + buffer_out[2] = class_id; // class + buffer_out[3] = message_id; // id + buffer_out[4] = payload_size; // length + buffer_out[5] = 0x00U; - bufferOut[6 + payloadSize] = 0x00U; // CK_A - bufferOut[7 + payloadSize] = 0x00U; // CK_B + buffer_out[6 + payload_size] = 0x00U; // CK_A + buffer_out[7 + payload_size] = 0x00U; // CK_B - for (int i = 0; i < payloadSize; i++) { - bufferOut[6 + i] = payload[i]; + for (int i = 0; i < payload_size; i++) { + buffer_out[6 + i] = payload[i]; } - checksum(bufferOut, (payloadSize + 8U)); - return (payloadSize + 8U); + checksum(buffer_out, (payload_size + 8U)); + return (payload_size + 8U); } -GpsResponse getAck(::Device* uart, uint8_t class_id, uint8_t msg_id, uint32_t waitMillis) { +GpsResponse get_ack(Device* uart, uint8_t class_id, uint8_t msg_id, uint32_t wait_millis) { uint8_t b; uint8_t ack = 0; const uint8_t ackP[2] = {class_id, msg_id}; uint8_t buf[10] = {0xB5, 0x62, 0x05, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00}; - uint32_t startTime = kernel::getTicks(); - TickType_t waitTicks = pdMS_TO_TICKS(waitMillis); + uint32_t start_time = get_ticks(); + TickType_t wait_ticks = pdMS_TO_TICKS(wait_millis); const char frame_errors[] = "More than 100 frame errors"; int sCounter = 0; -#ifdef GPS_DEBUG - std::string debugmsg = ""; -#endif for (int j = 2; j < 6; j++) { buf[8] += buf[j]; @@ -82,11 +85,8 @@ GpsResponse getAck(::Device* uart, uint8_t class_id, uint8_t msg_id, uint32_t wa buf[9] += buf[8]; } - while (kernel::getTicks() - startTime < waitTicks) { + while (get_ticks() - start_time < wait_ticks) { if (ack > 9) { -#ifdef GPS_DEBUG - LOG_I(TAG, "Got ACK for class %02X message %02X in %zums", class_id, msg_id, kernel::getMillis() - startTime); -#endif return GpsResponse::Ok; // ACK received } size_t available = 0; @@ -96,25 +96,15 @@ GpsResponse getAck(::Device* uart, uint8_t class_id, uint8_t msg_id, uint32_t wa if (b == frame_errors[sCounter]) { sCounter++; if (sCounter == 26) { -#ifdef GPS_DEBUG - - LOG_I(TAG, "%s", debugmsg.c_str()); -#endif return GpsResponse::FrameErrors; } } else { sCounter = 0; } -#ifdef GPS_DEBUG - debugmsg += std::format("%02X", b); -#endif if (b == buf[ack]) { ack++; } else { if (ack == 3 && b == 0x00) { // UBX-ACK-NAK message -#ifdef GPS_DEBUG - LOG_I(TAG, "%s", debugmsg.c_str()); -#endif LOG_W(TAG, "Got NAK for class %02X message %02X", class_id, msg_id); return GpsResponse::NotAck; // NAK received } @@ -122,20 +112,17 @@ GpsResponse getAck(::Device* uart, uint8_t class_id, uint8_t msg_id, uint32_t wa } } } -#ifdef GPS_DEBUG - LOG_I(TAG, "%s", debugmsg.c_str()); LOG_W(TAG, "No response for class %02X message %02X", class_id, msg_id); -#endif return GpsResponse::None; // No response received within timeout } -static int getAck(::Device* uart, uint8_t* buffer, uint16_t size, uint8_t requestedClass, uint8_t requestedId, uint32_t timeoutMillis) { - uint16_t ubxFrameCounter = 0; - TickType_t startTime = kernel::getTicks(); - TickType_t timeoutTicks = pdMS_TO_TICKS(timeoutMillis); - uint16_t needRead = 0; +static int get_ack(Device* uart, uint8_t* buffer, uint16_t size, uint8_t requested_class, uint8_t requested_id, uint32_t timeout_millis) { + uint16_t ubx_frame_counter = 0; + TickType_t start_time = get_ticks(); + TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_millis); + uint16_t need_read = 0; - while ((kernel::getTicks() - startTime) < timeoutTicks) { + while ((get_ticks() - start_time) < timeout_ticks) { size_t available = 0; uart_controller_get_available(uart, &available); while (available > 0) { @@ -143,56 +130,53 @@ static int getAck(::Device* uart, uint8_t* buffer, uint16_t size, uint8_t reques uart_controller_read_byte(uart, &c, 1); available--; - switch (ubxFrameCounter) { + switch (ubx_frame_counter) { case 0: if (c == 0xB5) { - ubxFrameCounter++; + ubx_frame_counter++; } break; case 1: if (c == 0x62) { - ubxFrameCounter++; + ubx_frame_counter++; } else { - ubxFrameCounter = 0; + ubx_frame_counter = 0; } break; case 2: - if (c == requestedClass) { - ubxFrameCounter++; + if (c == requested_class) { + ubx_frame_counter++; } else { - ubxFrameCounter = 0; + ubx_frame_counter = 0; } break; case 3: - if (c == requestedId) { - ubxFrameCounter++; + if (c == requested_id) { + ubx_frame_counter++; } else { - ubxFrameCounter = 0; + ubx_frame_counter = 0; } break; case 4: - needRead = c; - ubxFrameCounter++; + need_read = c; + ubx_frame_counter++; break; case 5: { // Payload length msb - needRead |= (c << 8); - ubxFrameCounter++; + need_read |= (c << 8); + ubx_frame_counter++; // Check for buffer overflow - if (needRead >= size) { - ubxFrameCounter = 0; + if (need_read >= size) { + ubx_frame_counter = 0; break; } auto read_bytes = 0U; - uart_controller_read_bytes(uart, buffer, needRead, 250 / portTICK_PERIOD_MS); - if (read_bytes != needRead) { - ubxFrameCounter = 0; + uart_controller_read_bytes(uart, buffer, need_read, 250 / portTICK_PERIOD_MS); + if (read_bytes != need_read) { + ubx_frame_counter = 0; } else { // return payload length -#ifdef GPS_DEBUG - LOG_I(TAG, "Got ACK for class %02X message %02X in %zums", requestedClass, requestedId, kernel::getMillis() - startTime); -#endif - return needRead; + return need_read; } break; } @@ -205,7 +189,7 @@ static int getAck(::Device* uart, uint8_t* buffer, uint16_t size, uint8_t reques return 0; } -static struct uBloxGnssModelInfo { +static struct UbloxGnssModelInfo { char swVersion[30]; char hwVersion[10]; uint8_t extensionNo; @@ -213,7 +197,7 @@ static struct uBloxGnssModelInfo { uint8_t protocol_version; } ublox_info; -GpsModel probe(::Device* uart) { +GpsModel probe(Device* uart) { LOG_I(TAG, "Probing for U-blox"); uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00}; @@ -221,28 +205,28 @@ GpsModel probe(::Device* uart) { uart_controller_flush_input(uart); uart_controller_write_bytes(uart, cfg_rate, sizeof(cfg_rate), 500 / portTICK_PERIOD_MS); // Check that the returned response class and message ID are correct - GpsResponse response = getAck(uart, 0x06, 0x08, 750); + GpsResponse response = get_ack(uart, 0x06, 0x08, 750); if (response == GpsResponse::None) { LOG_W(TAG, "No GNSS Module"); - return GpsModel::Unknown; + return GpsModel::GPS_MODEL_UNKNOWN; } else if (response == GpsResponse::FrameErrors) { LOG_W(TAG, "UBlox Frame Errors"); } uint8_t buffer[256]; memset(buffer, 0, sizeof(buffer)); - uint8_t _message_MONVER[8] = { + uint8_t message_monver[8] = { 0xB5, 0x62, // Sync message for UBX protocol 0x0A, 0x04, // Message class and ID (UBX-MON-VER) 0x00, 0x00, // Length of payload (we're asking for an answer, so no payload) 0x00, 0x00 // Checksum }; // Get Ublox gnss module hardware and software info - checksum(_message_MONVER, sizeof(_message_MONVER)); + checksum(message_monver, sizeof(message_monver)); uart_controller_flush_input(uart); - uart_controller_write_bytes(uart, _message_MONVER, sizeof(_message_MONVER), 500); + uart_controller_write_bytes(uart, message_monver, sizeof(message_monver), 500); - uint16_t ack_response_len = getAck(uart, buffer, sizeof(buffer), 0x0A, 0x04, 1200); + uint16_t ack_response_len = get_ack(uart, buffer, sizeof(buffer), 0x0A, 0x04, 1200); if (ack_response_len) { uint16_t position = 0; for (char& i: ublox_info.swVersion) { @@ -294,86 +278,86 @@ GpsModel probe(::Device* uart) { #define DETECTED_MESSAGE "%s detected, using %s Module" if (strncmp(ublox_info.hwVersion, "00040007", 8) == 0) { LOG_I(TAG, DETECTED_MESSAGE, "U-blox 6", "6"); - return GpsModel::UBLOX6; + return GPS_MODEL_UBLOX6; } else if (strncmp(ublox_info.hwVersion, "00070000", 8) == 0) { LOG_I(TAG, DETECTED_MESSAGE, "U-blox 7", "7"); - return GpsModel::UBLOX7; + return GPS_MODEL_UBLOX7; } else if (strncmp(ublox_info.hwVersion, "00080000", 8) == 0) { LOG_I(TAG, DETECTED_MESSAGE, "U-blox 8", "8"); - return GpsModel::UBLOX8; + return GPS_MODEL_UBLOX8; } else if (strncmp(ublox_info.hwVersion, "00190000", 8) == 0) { LOG_I(TAG, DETECTED_MESSAGE, "U-blox 9", "9"); - return GpsModel::UBLOX9; + return GPS_MODEL_UBLOX9; } else if (strncmp(ublox_info.hwVersion, "000A0000", 8) == 0) { LOG_I(TAG, DETECTED_MESSAGE, "U-blox 10", "10"); - return GpsModel::UBLOX10; + return GPS_MODEL_UBLOX10; } } - return GpsModel::Unknown; + return GPS_MODEL_UNKNOWN; } -bool init(::Device* uart, GpsModel model) { +bool init(Device* uart, GpsModel model) { LOG_I(TAG, "U-blox init"); switch (model) { - case GpsModel::UBLOX6: - return initUblox6(uart); - case GpsModel::UBLOX7: - case GpsModel::UBLOX8: - case GpsModel::UBLOX9: - return initUblox789(uart, model); - case GpsModel::UBLOX10: - return initUblox10(uart); + case GPS_MODEL_UBLOX6: + return init_ublox_6(uart); + case GPS_MODEL_UBLOX7: + case GPS_MODEL_UBLOX8: + case GPS_MODEL_UBLOX9: + return init_ublox_789(uart, model); + case GPS_MODEL_UBLOX10: + return init_ublox_10(uart); default: LOG_E(TAG, "Unknown or unsupported U-blox model"); return false; } } -bool initUblox10(::Device* uart) { +bool init_ublox_10(Device* uart) { uint8_t buffer[256]; - kernel::delayMillis(1000); + delay_millis(1000); uart_controller_flush_input(uart); SEND_UBX_PACKET(uart, buffer, 0x06, 0x8A, _message_VALSET_DISABLE_NMEA_RAM, "disable NMEA messages in M10 RAM", 300); - kernel::delayMillis(750); + delay_millis(750); uart_controller_flush_input(uart); SEND_UBX_PACKET(uart, buffer, 0x06, 0x8A, _message_VALSET_DISABLE_NMEA_BBR, "disable NMEA messages in M10 BBR", 300); - kernel::delayMillis(750); + delay_millis(750); uart_controller_flush_input(uart); SEND_UBX_PACKET(uart, buffer, 0x06, 0x8A, _message_VALSET_DISABLE_TXT_INFO_RAM, "disable Info messages for M10 GPS RAM", 300); - kernel::delayMillis(750); + delay_millis(750); uart_controller_flush_input(uart); SEND_UBX_PACKET(uart, buffer, 0x06, 0x8A, _message_VALSET_DISABLE_TXT_INFO_BBR, "disable Info messages for M10 GPS BBR", 300); - kernel::delayMillis(750); + delay_millis(750); SEND_UBX_PACKET(uart, buffer, 0x06, 0x8A, _message_VALSET_PM_RAM, "enable powersave for M10 GPS RAM", 300); - kernel::delayMillis(750); + delay_millis(750); SEND_UBX_PACKET(uart, buffer, 0x06, 0x8A, _message_VALSET_PM_BBR, "enable powersave for M10 GPS BBR", 300); - kernel::delayMillis(750); + delay_millis(750); SEND_UBX_PACKET(uart, buffer, 0x06, 0x8A, _message_VALSET_ITFM_RAM, "enable jam detection M10 GPS RAM", 300); - kernel::delayMillis(750); + delay_millis(750); SEND_UBX_PACKET(uart, buffer, 0x06, 0x8A, _message_VALSET_ITFM_BBR, "enable jam detection M10 GPS BBR", 300); - kernel::delayMillis(750); + delay_millis(750); // Here is where the init commands should go to do further M10 initialization. SEND_UBX_PACKET(uart, buffer, 0x06, 0x8A, _message_VALSET_DISABLE_SBAS_RAM, "disable SBAS M10 GPS RAM", 300); - kernel::delayMillis(750); // will cause a receiver restart so wait a bit + delay_millis(750); // will cause a receiver restart so wait a bit SEND_UBX_PACKET(uart, buffer, 0x06, 0x8A, _message_VALSET_DISABLE_SBAS_BBR, "disable SBAS M10 GPS BBR", 300); - kernel::delayMillis(750); // will cause a receiver restart so wait a bit + delay_millis(750); // will cause a receiver restart so wait a bit // Done with initialization // Enable wanted NMEA messages in BBR layer so they will survive a periodic sleep SEND_UBX_PACKET(uart, buffer, 0x06, 0x8A, _message_VALSET_ENABLE_NMEA_BBR, "enable messages for M10 GPS BBR", 300); - kernel::delayMillis(750); + delay_millis(750); // Enable wanted NMEA messages in RAM layer SEND_UBX_PACKET(uart, buffer, 0x06, 0x8A, _message_VALSET_ENABLE_NMEA_RAM, "enable messages for M10 GPS RAM", 500); - kernel::delayMillis(750); + delay_millis(750); // As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR. // BBR will survive a restart, and power off for a while, but modules with small backup // batteries or super caps will not retain the config for a long power off time. - auto packet_size = makePacket(0x06, 0x09, _message_SAVE_10, sizeof(_message_SAVE_10), buffer); + auto packet_size = make_packet(0x06, 0x09, _message_SAVE_10, sizeof(_message_SAVE_10), buffer); uart_controller_write_bytes(uart, buffer, packet_size, 2000 / portTICK_PERIOD_MS); - if (getAck(uart, 0x06, 0x09, 2000) != GpsResponse::Ok) { + if (get_ack(uart, 0x06, 0x09, 2000) != GpsResponse::Ok) { LOG_W(TAG, "Unable to save GNSS module config"); } else { LOG_I(TAG, "GNSS module configuration saved!"); @@ -381,36 +365,36 @@ bool initUblox10(::Device* uart) { return true; } -bool initUblox789(::Device* uart, GpsModel model) { +bool init_ublox_789(Device* uart, GpsModel model) { uint8_t buffer[256]; - if (model == GpsModel::UBLOX7) { + if (model == GpsModel::GPS_MODEL_UBLOX7) { LOG_D(TAG, "Set GPS+SBAS"); - auto msglen = makePacket(0x06, 0x3e, _message_GNSS_7, sizeof(_message_GNSS_7), buffer); + auto msglen = make_packet(0x06, 0x3e, _message_GNSS_7, sizeof(_message_GNSS_7), buffer); uart_controller_write_bytes(uart, buffer, msglen, 800 / portTICK_PERIOD_MS); } else { // 8,9 - auto msglen = makePacket(0x06, 0x3e, _message_GNSS_8, sizeof(_message_GNSS_8), buffer); + auto msglen = make_packet(0x06, 0x3e, _message_GNSS_8, sizeof(_message_GNSS_8), buffer); uart_controller_write_bytes(uart, buffer, msglen, 800 / portTICK_PERIOD_MS); } - if (getAck(uart, 0x06, 0x3e, 800) == GpsResponse::NotAck) { + if (get_ack(uart, 0x06, 0x3e, 800) == GpsResponse::NotAck) { // It's not critical if the module doesn't acknowledge this configuration. LOG_D(TAG, "reconfigure GNSS - defaults maintained. Is this module GPS-only?"); } else { - if (model == GpsModel::UBLOX7) { + if (model == GpsModel::GPS_MODEL_UBLOX7) { LOG_I(TAG, "GPS+SBAS configured"); } else { // 8,9 LOG_I(TAG, "GPS+SBAS+GLONASS+Galileo configured"); } // Documentation say, we need wait at least 0.5s after reconfiguration of GNSS module, before sending next // commands for the M8 it tends to be more. 1 sec should be enough - kernel::delayMillis(1000); + delay_millis(1000); } uart_controller_flush_input(uart); SEND_UBX_PACKET(uart, buffer, 0x06, 0x02, _message_DISABLE_TXT_INFO, "disable text info messages", 500); - if (model == GpsModel::UBLOX8) { // 8 + if (model == GpsModel::GPS_MODEL_UBLOX8) { // 8 uart_controller_flush_input(uart); SEND_UBX_PACKET(uart, buffer, 0x06, 0x39, _message_JAM_8, "enable interference resistance", 500); @@ -436,7 +420,7 @@ bool initUblox789(::Device* uart, GpsModel model) { SEND_UBX_PACKET(uart, buffer, 0x06, 0x3B, _message_CFG_PM2, "enable powersave details for GPS", 500); // For M8 we want to enable NMEA version 4.10 so we can see the additional satellites. - if (model == GpsModel::UBLOX8) { + if (model == GpsModel::GPS_MODEL_UBLOX8) { uart_controller_flush_input(uart); SEND_UBX_PACKET(uart, buffer, 0x06, 0x17, _message_NMEA, "enable NMEA 4.10", 500); } @@ -445,9 +429,9 @@ bool initUblox789(::Device* uart, GpsModel model) { SEND_UBX_PACKET(uart, buffer, 0x06, 0x3B, _message_CFG_PM2, "enable powersave details for GPS", 500); } - auto packet_size = makePacket(0x06, 0x09, _message_SAVE, sizeof(_message_SAVE), buffer); + auto packet_size = make_packet(0x06, 0x09, _message_SAVE, sizeof(_message_SAVE), buffer); uart_controller_write_bytes(uart, buffer, packet_size, 2000 / portTICK_PERIOD_MS); - if (getAck(uart, 0x06, 0x09, 2000) != GpsResponse::Ok) { + if (get_ack(uart, 0x06, 0x09, 2000) != GpsResponse::Ok) { LOG_W(TAG, "Unable to save GNSS module config"); } else { LOG_I(TAG, "GNSS module configuration saved!"); @@ -455,7 +439,7 @@ bool initUblox789(::Device* uart, GpsModel model) { return true; } -bool initUblox6(::Device* uart) { +bool init_ublox_6(Device* uart) { uint8_t buffer[256]; uart_controller_flush_input(uart); @@ -479,9 +463,9 @@ bool initUblox6(::Device* uart) { SEND_UBX_PACKET(uart, buffer, 0x06, 0x3B, _message_CFG_PM2, "enable powersave details for GPS", 500); SEND_UBX_PACKET(uart, buffer, 0x06, 0x01, _message_AID, "disable UBX-AID", 500); - auto packet_size = makePacket(0x06, 0x09, _message_SAVE, sizeof(_message_SAVE), buffer); + auto packet_size = make_packet(0x06, 0x09, _message_SAVE, sizeof(_message_SAVE), buffer); uart_controller_write_bytes(uart, buffer, packet_size, 2000); - if (getAck(uart, 0x06, 0x09, 2000) != GpsResponse::Ok) { + if (get_ack(uart, 0x06, 0x09, 2000) != GpsResponse::Ok) { LOG_W(TAG, "Unable to save GNSS module config"); } else { LOG_I(TAG, "GNSS module config saved!"); @@ -489,4 +473,4 @@ bool initUblox6(::Device* uart) { return true; } -} // namespace tt::hal::gps::ublox +} diff --git a/Firmware/CMakeLists.txt b/Firmware/CMakeLists.txt index 22578bff..2b80054b 100644 --- a/Firmware/CMakeLists.txt +++ b/Firmware/CMakeLists.txt @@ -95,9 +95,10 @@ else () list(APPEND REQUIRES_LIST Tactility TactilityFreeRtos - hal-device-module lvgl-module crypt-module + gps-module + gps-generic-module SDL2::SDL2-static SDL2-static ) diff --git a/Firmware/Source/Main.cpp b/Firmware/Source/Main.cpp index 8dfa4350..8c902c8c 100644 --- a/Firmware/Source/Main.cpp +++ b/Firmware/Source/Main.cpp @@ -1,6 +1,5 @@ #include -#include #include #ifdef ESP_PLATFORM @@ -9,30 +8,15 @@ #include #endif -#ifdef CONFIG_TT_USE_DEPRECATED_HAL -// Each board project declares this variable -extern const tt::hal::Configuration hardwareConfiguration; -#else -// Legacy placeholder (required until legacy HAL is cleaned up everywhere) -extern const tt::hal::Configuration hardwareConfiguration = {}; -#endif - extern "C" { void app_main() { - static const tt::Configuration config = { - /** - * Auto-select a board based on the ./sdkconfig.board.* file - * that you copied to ./sdkconfig before you opened this project. - */ - .hardware = &hardwareConfiguration - }; #ifdef ESP_PLATFORM tt_init_tactility_c(); // ELF bindings for side-loading on ESP32 #endif - tt::run(config, dts_modules, dts_devices); + tt::run(dts_modules, dts_devices); } } // extern \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md index 8340dbaa..0fc1ebb8 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -7,13 +7,16 @@ These applications are not part of the Tactility operating system's main firmwar "end-users" refers to people who install and/or use Tactility software on their devices. -## Past & Present +## Summary -Formerly, there was a mixed usage of [GPL v3.0](Documentation/LICENSE-GPL-3.0.md) for internal subprojects -and [Apache License v2.0](Documentation/LICENSE-Apache-2.0.md) for subprojects that would be used in external apps. +The main firmware projects (`Firmware/`, `Tactility/`) are licensed under [GPL v3.0](Documentation/LICENSE-GPL-3.0.md) -For future subprojects, [Apache License v2.0](Documentation/LICENSE-Apache-2.0.md) will be chosen for internal subproject. -Existing GPL-licensed projects will retain this license, as it cannot be changed to a more permissive license. +Most drivers have an [Apache License v2.0](Documentation/LICENSE-Apache-2.0.md), with exceptions such as `Drivers/gps-generic-module/`. +Licensing may also differ for subprojects intended for use in external applications. + +Specific aren't generally used directly in external app projects, but if they are, make sure to check their licenses. + +All projects under `Modules/` have an [Apache License v2.0](Documentation/LICENSE-Apache-2.0.md). ## Overview @@ -22,13 +25,13 @@ Below is an overview of the licenses of some of the subprojects. | Project | License | |--------------------|-------------------------| | Tactility | GNU Public License v3.0 | -| TactilityCore | GNU Public License v3.0 | | TactilityC | Apache License v2.0 | | TactilityFreeRTOS | Apache License v2.0 | | TactilityKernel | Apache License v2.0 | | Tests | GNU Public License v3.0 | | Devices/* | GNU Public License v3.0 | | Drivers/* | (varies) | +| Modules/* | Apache License v2.0 | | DevicetreeCompiler | Apache License v2.0 | | Platforms/* | Apache License v2.0 | diff --git a/Modules/hal-device-module/LICENSE-Apache-2.0.md b/Modules/crypt-module/LICENSE-Apache-2.0.md similarity index 100% rename from Modules/hal-device-module/LICENSE-Apache-2.0.md rename to Modules/crypt-module/LICENSE-Apache-2.0.md diff --git a/Modules/gps-module/CMakeLists.txt b/Modules/gps-module/CMakeLists.txt new file mode 100644 index 00000000..1bd923a9 --- /dev/null +++ b/Modules/gps-module/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.20) + +include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") + +file(GLOB_RECURSE SOURCE_FILES "source/*.c*") + +tactility_add_module(gps-module + SRCS ${SOURCE_FILES} + PRIV_INCLUDE_DIRS private/ + INCLUDE_DIRS include/ + REQUIRES TactilityKernel minmea +) diff --git a/Modules/gps-module/LICENSE-Apache-2.0.md b/Modules/gps-module/LICENSE-Apache-2.0.md new file mode 100644 index 00000000..f5f4b8b5 --- /dev/null +++ b/Modules/gps-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Modules/gps-module/devicetree.yaml b/Modules/gps-module/devicetree.yaml new file mode 100644 index 00000000..6bbb2436 --- /dev/null +++ b/Modules/gps-module/devicetree.yaml @@ -0,0 +1,2 @@ +dependencies: + - TactilityKernel diff --git a/Modules/gps-module/include/gps/gps.h b/Modules/gps-module/include/gps/gps.h new file mode 100644 index 00000000..4fbc86c5 --- /dev/null +++ b/Modules/gps-module/include/gps/gps.h @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +#include + +/** + * @brief Supported GPS/GNSS receiver chipsets. + */ +enum GpsModel { + GPS_MODEL_UNKNOWN = 0, + GPS_MODEL_AG3335, + GPS_MODEL_AG3352, + // CASIC, might work with AT6558, Neoway N58 LTE Cat.1, Neoway G2 and Neoway G7A + GPS_MODEL_ATGM336H, + GPS_MODEL_LS20031, + GPS_MODEL_MTK, + GPS_MODEL_MTK_L76B, + GPS_MODEL_MTK_PA1616S, + GPS_MODEL_UBLOX6, + GPS_MODEL_UBLOX7, + GPS_MODEL_UBLOX8, + GPS_MODEL_UBLOX9, + GPS_MODEL_UBLOX10, + GPS_MODEL_UC6580, +}; + +/** @return a human-readable name for the model, e.g. "UBLOX8" or "Unknown" */ +const char* gps_model_to_string(enum GpsModel model); + +/** + * @brief Lifecycle state of a GPS_TYPE device. + */ +enum GpsState { + GPS_STATE_OFF, + GPS_STATE_PENDING_ON, + GPS_STATE_ON, + GPS_STATE_ERROR, + GPS_STATE_PENDING_OFF, +}; + +enum GpsEventType { + GPS_EVENT_UNSUBSCRIBED, // Last event, device wants to destroy itself and unsubscribed the subscriber. + GPS_EVENT_MESSAGE_RMC, + GPS_EVENT_MESSAGE_GGA, +}; + +struct GpsEvent { + enum GpsEventType type; + union { + struct minmea_sentence_rmc rmc; + struct minmea_sentence_gga gga; + } data; +}; + +struct GpsSubscription { + TaskHandle_t task; + + struct GpsEvent event; + + uint32_t sequence; + uint32_t consumed_sequence; + + struct GpsSubscription* next; +}; + +/** + * @brief API for GPS/GNSS receiver drivers. + */ +struct GpsApi { + /** + * @brief Registers a subscriber for GPS events (e.g. RMC/GGA sentences). + * @param[in] device the GPS device + * @param[in,out] sub subscription to register; caller owns the storage and must keep it alive until unsubscribed + */ + error_t (*event_subscribe)(struct Device* device, struct GpsSubscription* sub); + + /** + * @brief Removes a previously registered subscription. + * @param[in] device the GPS device + * @param[in] sub subscription to remove, as passed to event_subscribe + */ + error_t (*event_unsubscribe)(struct Device* device, struct GpsSubscription* sub); + + /** + * @brief Blocks the calling task until a new event arrives for the subscription, or timeout elapses. + * @param[in] device the GPS device + * @param[in,out] sub subscription to wait on + * @param[in] timeout max ticks to wait + * @return ERROR_NONE if an event arrived, ERROR_TIMEOUT if the timeout elapsed + */ + error_t (*event_await)(struct Device* device, struct GpsSubscription* sub, TickType_t timeout); + + /** + * @brief Gets the current lifecycle state. + * @param[in] device the GPS device + */ + enum GpsState (*get_state)(struct Device* device); + + /** + * @brief Gets a human-readable model name for the device, e.g. "UBLOX8". + * @param[in] device the GPS device + * @param[out] model_name buffer to receive the NUL-terminated name + * @param[in] buffer_size size of model_name in bytes + * @return ERROR_NONE on success + */ + error_t (*get_model_name)(struct Device* device, char* model_name, size_t buffer_size); +}; + +/** @copydoc GpsApi::event_subscribe */ +error_t gps_event_subscribe(struct Device* device, struct GpsSubscription* sub); + +/** @copydoc GpsApi::event_unsubscribe */ +error_t gps_event_unsubscribe(struct Device* device, struct GpsSubscription* sub); + +/** @copydoc GpsApi::event_await */ +error_t gps_event_await(struct Device* device, struct GpsSubscription* sub, TickType_t timeout); + +/** @copydoc GpsApi::get_state */ +enum GpsState gps_get_state(struct Device* device); + +/** @copydoc GpsApi::get_model_name */ +error_t gps_get_model_name(struct Device* device, char* model_name, size_t buffer_size); + +extern const struct DeviceType GPS_TYPE; + +#ifdef __cplusplus +} +#endif diff --git a/Modules/hal-device-module/include/tactility/hal_device_module.h b/Modules/gps-module/include/gps/gps_module.h similarity index 75% rename from Modules/hal-device-module/include/tactility/hal_device_module.h rename to Modules/gps-module/include/gps/gps_module.h index 8efca390..384348f0 100644 --- a/Modules/hal-device-module/include/tactility/hal_device_module.h +++ b/Modules/gps-module/include/gps/gps_module.h @@ -5,7 +5,7 @@ extern "C" { #endif -extern struct Module hal_device_module; +extern struct Module gps_module; #ifdef __cplusplus } diff --git a/Modules/gps-module/include/gps/gps_settings.h b/Modules/gps-module/include/gps/gps_settings.h new file mode 100644 index 00000000..23c98f08 --- /dev/null +++ b/Modules/gps-module/include/gps/gps_settings.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include + +/** + * @brief A persisted GPS receiver configuration. + */ +struct GpsConfiguration { + /** UART controller device name, e.g. "uart0" - resolved via device_get_by_name(). */ + char uart_name[32]; + uint32_t baud_rate; + /** GPS_MODEL_UNKNOWN triggers an autoprobe. */ + enum GpsModel model; +}; + +/** + * @brief Persists a new GPS configuration and triggers the ledger to materialize a (not started) + * GPS_TYPE device for it in the device tree. Use device_start()/device_stop() on the resulting + * device to control whether it's actually running. + * @retval ERROR_RESOURCE if the configuration file could not be opened/written + */ +error_t gps_settings_add_configuration(const struct GpsConfiguration* configuration); + +/** + * @brief Removes the persisted GPS configuration at `index` (as seen via + * gps_settings_for_each_configuration()), and triggers the ledger to stop, destruct and remove + * its corresponding GPS_TYPE device from the device tree. + * @retval ERROR_NOT_FOUND if index is out of range + * @retval ERROR_RESOURCE if the configuration file could not be read/written + */ +error_t gps_settings_remove_configuration_at(size_t index); + +/** + * @brief Iterates over all persisted GPS configurations. + * @param[in] context passed through to on_configuration, can be NULL + * @param[in] on_configuration called once per configuration, in file order, with its index + */ +void gps_settings_for_each_configuration(void* context, void (*on_configuration)(const struct GpsConfiguration* configuration, size_t index, void* context)); + +#ifdef __cplusplus +} +#endif diff --git a/Modules/gps-module/private/gps/private/gps_ledger.h b/Modules/gps-module/private/gps/private/gps_ledger.h new file mode 100644 index 00000000..a0fbc757 --- /dev/null +++ b/Modules/gps-module/private/gps/private/gps_ledger.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +/** + * @brief Reconciles the device tree against the persisted GPS configurations (see + * gps/gps_settings.h): constructs+adds (but does not start) a GPS_TYPE device for every + * configuration that doesn't have one yet, and stops+destructs+removes any ledger-owned device + * whose configuration has disappeared. + * + * Only ever touches devices the ledger itself created (tagged DEVICE_FLAG_DYNAMIC) - devicetree- + * declared GPS_TYPE devices (tagged DEVICE_FLAG_DTS) are never constructed, started, stopped, or + * destructed by the ledger. + */ +void gps_ledger_sync(); + +/** + * @brief Stops+destructs+removes every ledger-owned device. Devicetree-declared GPS_TYPE devices + * are left untouched. + */ +void gps_ledger_clear(); diff --git a/Modules/gps-module/source/gps.cpp b/Modules/gps-module/source/gps.cpp new file mode 100644 index 00000000..41ebd110 --- /dev/null +++ b/Modules/gps-module/source/gps.cpp @@ -0,0 +1,57 @@ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +const char* gps_model_to_string(GpsModel model) { + switch (model) { + case GPS_MODEL_AG3335: return "AG3335"; + case GPS_MODEL_AG3352: return "AG3352"; + case GPS_MODEL_ATGM336H: return "ATGM336H"; + case GPS_MODEL_LS20031: return "LS20031"; + case GPS_MODEL_MTK: return "MTK"; + case GPS_MODEL_MTK_L76B: return "MTK_L76B"; + case GPS_MODEL_MTK_PA1616S: return "MTK_PA1616S"; + case GPS_MODEL_UBLOX6: return "UBLOX6"; + case GPS_MODEL_UBLOX7: return "UBLOX7"; + case GPS_MODEL_UBLOX8: return "UBLOX8"; + case GPS_MODEL_UBLOX9: return "UBLOX9"; + case GPS_MODEL_UBLOX10: return "UBLOX10"; + case GPS_MODEL_UC6580: return "UC6580"; + default: return "Unknown"; + } +} + +error_t gps_event_subscribe(Device* device, GpsSubscription* sub) { + const auto* driver = device_get_driver(device); + return static_cast(driver->api)->event_subscribe(device, sub); +} + +error_t gps_event_unsubscribe(Device* device, GpsSubscription* sub) { + const auto* driver = device_get_driver(device); + return static_cast(driver->api)->event_unsubscribe(device, sub); +} + +error_t gps_event_await(Device* device, GpsSubscription* sub, TickType_t timeout) { + const auto* driver = device_get_driver(device); + return static_cast(driver->api)->event_await(device, sub, timeout); +} + +GpsState gps_get_state(Device* device) { + const auto* driver = device_get_driver(device); + return static_cast(driver->api)->get_state(device); +} + +error_t gps_get_model_name(Device* device, char* model_name, size_t buffer_size) { + const auto* driver = device_get_driver(device); + return static_cast(driver->api)->get_model_name(device, model_name, buffer_size); +} + +const DeviceType GPS_TYPE { + .name = "gps" +}; + +#ifdef __cplusplus +} +#endif diff --git a/Modules/gps-module/source/gps_ledger.cpp b/Modules/gps-module/source/gps_ledger.cpp new file mode 100644 index 00000000..2b94978e --- /dev/null +++ b/Modules/gps-module/source/gps_ledger.cpp @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +constexpr auto* TAG = "gps_ledger"; + +struct GpsConfig { + uint32_t baud_rate; + enum GpsModel model; +}; + +// A GPS_TYPE device the ledger constructed for a persisted GpsConfiguration. Device is the first +// member so `reinterpret_cast(device)` is safe when a Device* obtained from the +// device tree needs to be freed. +struct GpsLedgerEntry { + Device device {}; + GpsConfig config {}; + // device->name points into this buffer - must outlive the device (device->name only stores a + // pointer, it doesn't copy). + char name[16] {}; +}; + +// Unique across every device the ledger creates, so device names ("gpsN") never collide. +static uint32_t next_device_index = 0; + +static bool device_matches_configuration(Device* device, const GpsConfiguration& configuration) { + auto* parent = device_get_parent(device); + if (parent == nullptr || strcmp(parent->name, configuration.uart_name) != 0) { + return false; + } + const auto* config = static_cast(device->config); + return config->baud_rate == configuration.baud_rate && config->model == configuration.model; +} + +// Constructs+adds (not started) a GPS_TYPE device wired to `configuration`'s named UART, tagged +// DEVICE_FLAG_DYNAMIC so the ledger recognizes it as its own on a later sync. +static bool create_device(const GpsConfiguration& configuration) { + Device* uart = nullptr; + if (device_get_by_name(configuration.uart_name, &uart) != ERROR_NONE) { + LOG_E(TAG, "Failed to find device %s", configuration.uart_name); + return false; + } + + auto* entry = new(std::nothrow) GpsLedgerEntry(); + if (entry == nullptr) { + device_put(uart); + return false; + } + entry->config = GpsConfig { .baud_rate = configuration.baud_rate, .model = configuration.model }; + snprintf(entry->name, sizeof(entry->name), "gps%u", (unsigned)next_device_index++); + + auto* device = &entry->device; + device->address = 0; + device->name = entry->name; + device->config = &entry->config; + device->parent = nullptr; + device->flags = DEVICE_FLAG_DYNAMIC; + device->internal = nullptr; + + bool ok = false; + if (device_construct(device) == ERROR_NONE) { + device_set_parent(device, uart); + + Driver* driver = driver_find_compatible("tactility,gps-generic"); + if (driver != nullptr) { + device_set_driver(device, driver); + ok = device_add(device) == ERROR_NONE; + if (!ok) { + LOG_E(TAG, "Failed to add %s", device->name); + } + } else { + LOG_E(TAG, "No driver registered for tactility,gps-generic"); + } + + if (!ok) { + device_destruct(device); + } + } else { + LOG_E(TAG, "Failed to construct %s", device->name); + } + + device_put(uart); + + if (!ok) { + delete entry; + } + return ok; +} + +// Stops (if needed), removes and destructs a ledger-owned device, and frees its entry. +static void destroy_device(Device* device) { + if (device_is_ready(device)) { + device_stop(device); + } + device_remove(device); + device_destruct(device); + delete reinterpret_cast(device); +} + +static bool is_ledger_owned(const Device* device) { + return !(device->flags & DEVICE_FLAG_DTS) && (device->flags & DEVICE_FLAG_DYNAMIC); +} + +// Collects the ledger-owned GPS_TYPE devices. device_remove()/device_stop() must not run while +// device_for_each_of_type() holds the device ledger lock, so callers process the result afterwards. +static std::vector collect_owned_devices() { + std::vector owned; + device_for_each_of_type(&GPS_TYPE, &owned, [](Device* device, void* context) { + if (is_ledger_owned(device)) { + static_cast*>(context)->push_back(device); + } + return true; + }); + return owned; +} + +void gps_ledger_sync() { + std::vector configurations; + gps_settings_for_each_configuration(&configurations, [](const GpsConfiguration* configuration, size_t, void* context) { + static_cast*>(context)->push_back(*configuration); + }); + + std::vector matched(configurations.size(), false); + std::vector stale; + + for (auto* device : collect_owned_devices()) { + bool found = false; + for (size_t i = 0; i < configurations.size(); i++) { + if (!matched[i] && device_matches_configuration(device, configurations[i])) { + matched[i] = true; + found = true; + break; + } + } + if (!found) { + stale.push_back(device); + } + } + + // Configuration disappeared - stop, destruct and drop the device that was created for it. + for (auto* device : stale) { + destroy_device(device); + } + + // New configuration - create a (not started) device for it. + for (size_t i = 0; i < configurations.size(); i++) { + if (!matched[i]) { + create_device(configurations[i]); + } + } +} + +void gps_ledger_clear() { + for (auto* device : collect_owned_devices()) { + destroy_device(device); + } +} diff --git a/Modules/gps-module/source/gps_settings.cpp b/Modules/gps-module/source/gps_settings.cpp new file mode 100644 index 00000000..ca46229d --- /dev/null +++ b/Modules/gps-module/source/gps_settings.cpp @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +constexpr auto* TAG = "gps_settings"; + +// Storage key for the persisted configuration file (services would use their own service ID for +// this; gps_settings has no service backing it, so it defines its own). +constexpr auto* GPS_SETTINGS_STORAGE_ID = "gps"; + +// region Configuration persistence + +// Recursively creates every missing directory component of `path` (best-effort - mkdir() failures +// other than "already exists" are surfaced later, when the actual config file open fails). +static void ensure_directory_exists(const char* path) { + char buffer[224]; + std::strncpy(buffer, path, sizeof(buffer) - 1); + buffer[sizeof(buffer) - 1] = '\0'; + + for (char* p = buffer + 1; *p != '\0'; p++) { + if (*p == '/') { + *p = '\0'; + mkdir(buffer, 0777); + *p = '/'; + } + } + mkdir(buffer, 0777); +} + +static bool get_configuration_path(char* out_path, size_t out_path_size) { + return service_paths_get_user_data_path(GPS_SETTINGS_STORAGE_ID, "config.bin", out_path, out_path_size) == ERROR_NONE; +} + +// Holds the lock (if any) that `path` needs for the lifetime of the guard - see file_find_lock(). +class FileLockGuard { + FileMutex mutex; + bool locked; +public: + explicit FileLockGuard(const char* path) { + file_mutex_get(&mutex, path); + file_mutex_lock(&mutex); + locked = true; + } + + ~FileLockGuard() { + unlock(); + } + + void unlock() { + if (locked) { + file_mutex_unlock(&mutex); + locked = false; + } + } +}; + +void gps_settings_for_each_configuration(void* context, void (*on_configuration)(const GpsConfiguration* configuration, size_t index, void* context)) { + char path[224]; + if (!get_configuration_path(path, sizeof(path))) { + return; + } + + FileLockGuard lock(path); + + FILE* file = fopen(path, "rb"); + if (file == nullptr) { + return; // No configurations saved yet + } + + GpsConfiguration configuration; + size_t index = 0; + while (fread(&configuration, sizeof(configuration), 1, file) == 1) { + on_configuration(&configuration, index, context); + index++; + } + + fclose(file); +} + +static void collect_configuration(const GpsConfiguration* configuration, size_t, void* context) { + static_cast*>(context)->push_back(*configuration); +} + +static void load_configurations(std::vector& out) { + gps_settings_for_each_configuration(&out, collect_configuration); +} + +static error_t write_configurations(const std::vector& configurations) { + char directory[224]; + if (service_paths_get_user_data_directory(GPS_SETTINGS_STORAGE_ID, directory, sizeof(directory)) != ERROR_NONE) { + return ERROR_RESOURCE; + } + + char path[256]; + if (!get_configuration_path(path, sizeof(path))) { + return ERROR_RESOURCE; + } + + FileLockGuard lock(path); + + ensure_directory_exists(directory); + + FILE* file = fopen(path, "wb"); + if (file == nullptr) { + LOG_E(TAG, "Failed to open %s for writing", path); + return ERROR_RESOURCE; + } + + bool ok = true; + for (auto& configuration : configurations) { + if (fwrite(&configuration, sizeof(configuration), 1, file) != 1) { + ok = false; + break; + } + } + + fclose(file); + if (!ok) { + return ERROR_RESOURCE; + } + + lock.unlock(); + gps_ledger_sync(); + + return ERROR_NONE; +} + +// endregion + +error_t gps_settings_add_configuration(const GpsConfiguration* configuration) { + std::vector configurations; + load_configurations(configurations); + configurations.push_back(*configuration); + + error_t error = write_configurations(configurations); + if (error != ERROR_NONE) { + return error; + } + + return ERROR_NONE; +} + +error_t gps_settings_remove_configuration_at(size_t index) { + std::vector configurations; + load_configurations(configurations); + + if (index >= configurations.size()) { + return ERROR_NOT_FOUND; + } + + configurations.erase(configurations.begin() + static_cast(index)); + + error_t error = write_configurations(configurations); + if (error != ERROR_NONE) { + return error; + } + + return ERROR_NONE; +} diff --git a/Modules/gps-module/source/module.cpp b/Modules/gps-module/source/module.cpp new file mode 100644 index 00000000..da0a4c82 --- /dev/null +++ b/Modules/gps-module/source/module.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +#include +#include + +extern "C" { + +static error_t start() { + // Materializes devices for configurations persisted in previous sessions. + gps_ledger_sync(); + return ERROR_NONE; +} + +static error_t stop() { + gps_ledger_clear(); + return ERROR_NONE; +} + +Module gps_module = { + .name = "gps", + .start = start, + .stop = stop, + .drivers = nullptr, + .symbols = nullptr, + .internal = nullptr +}; + +} diff --git a/Modules/hal-device-module/CMakeLists.txt b/Modules/hal-device-module/CMakeLists.txt deleted file mode 100644 index e86d74da..00000000 --- a/Modules/hal-device-module/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -cmake_minimum_required(VERSION 3.20) - -include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") - -file(GLOB_RECURSE SOURCE_FILES "source/*.c*") - -list(APPEND REQUIRES_LIST - TactilityKernel - TactilityFreeRtos -) - -if (NOT DEFINED ENV{ESP_IDF_VERSION}) - list(APPEND REQUIRES_LIST freertos_kernel) -endif () - -tactility_add_module(hal-device-module - SRCS ${SOURCE_FILES} - INCLUDE_DIRS include/ - REQUIRES ${REQUIRES_LIST} -) diff --git a/Modules/hal-device-module/README.md b/Modules/hal-device-module/README.md deleted file mode 100644 index 5814a0e7..00000000 --- a/Modules/hal-device-module/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# hal-device-module - -**WARNING: This module contains deprecated code** - -This module is the basis for the old Tactility HAL. -This HAL existed before TactilityKernel. - -The C++ `tt::hal::Device` class is replaced by `struct Device` from TactilityKernel. - -License: [Apache v2.0](LICENSE-Apache-2.0.md) diff --git a/Modules/hal-device-module/include/tactility/drivers/hal_device.h b/Modules/hal-device-module/include/tactility/drivers/hal_device.h deleted file mode 100644 index 6dadcb80..00000000 --- a/Modules/hal-device-module/include/tactility/drivers/hal_device.h +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -enum class HalDeviceType { - HAL_DEVICE_TYPE_I2C, - HAL_DEVICE_TYPE_DISPLAY, - HAL_DEVICE_TYPE_TOUCH, - HAL_DEVICE_TYPE_SDCARD, - HAL_DEVICE_TYPE_KEYBOARD, - HAL_DEVICE_TYPE_ENCODER, - HAL_DEVICE_TYPE_POWER, - HAL_DEVICE_TYPE_GPS, - HAL_DEVICE_TYPE_OTHER -}; - -HalDeviceType hal_device_get_type(struct Device* device); - -void hal_device_for_each_of_type(HalDeviceType type, void* context, bool(*onDevice)(struct Device* device, void* context)); - -extern const struct DeviceType HAL_DEVICE_TYPE; - -#ifdef __cplusplus -} -#endif diff --git a/Modules/hal-device-module/include/tactility/drivers/hal_device.hpp b/Modules/hal-device-module/include/tactility/drivers/hal_device.hpp deleted file mode 100644 index 9b19e5b5..00000000 --- a/Modules/hal-device-module/include/tactility/drivers/hal_device.hpp +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#pragma once - -#include "hal_device.h" - -#include -#include - -namespace tt::hal { - -/** - * @brief Get a tt::hal::Device object from a Kernel device. - * @warning The input device must be of type HAL_DEVICE_TYPE - * @param kernelDevice The kernel device - * @return std::shared_ptr - */ -std::shared_ptr hal_device_get_device(::Device* kernelDevice); - -void hal_device_set_device(::Device* kernelDevice, std::shared_ptr halDevice); - -} diff --git a/Modules/hal-device-module/include/tactility/hal/Device.h b/Modules/hal-device-module/include/tactility/hal/Device.h deleted file mode 100644 index db7e68a6..00000000 --- a/Modules/hal-device-module/include/tactility/hal/Device.h +++ /dev/null @@ -1,144 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#pragma once - -#include -#include -#include -#include -#include -#include - -#include - -typedef ::Device KernelDevice; - -namespace tt::hal { -/** Base class for HAL-related devices. */ -class Device { - -public: - - enum class Type { - I2c, - Display, - Touch, - SdCard, - Keyboard, - Encoder, - Power, - Gps, - Other - }; - - typedef uint32_t Id; - - struct KernelDeviceHolder { - const std::string name; - std::shared_ptr device = std::make_shared(); - - explicit KernelDeviceHolder(std::string name) : name(name) { - device->name = this->name.c_str(); - } - }; - -private: - - Id id; - std::shared_ptr kernelDeviceHolder; - -public: - - Device(); - virtual ~Device() = default; - - /** Unique identifier */ - Id getId() const { return id; } - - /** The type of device */ - virtual Type getType() const = 0; - - /** The part number or hardware name e.g. TdeckTouch, TdeckDisplay, BQ24295, etc. */ - virtual std::string getName() const = 0; - - /** A short description of what this device does. - * e.g. "USB charging controller with I2C interface." - */ - virtual std::string getDescription() const = 0; - - void setKernelDeviceHolder(std::shared_ptr kernelDeviceHolder) { this->kernelDeviceHolder = kernelDeviceHolder; } - - std::shared_ptr getKernelDeviceHolder() const { return kernelDeviceHolder; } -}; - -/** - * Adds a device to the registry. - * @warning This will leak memory if you want to destroy a device and don't call deregisterDevice()! - */ -void registerDevice(const std::shared_ptr& device); - -/** Remove a device from the registry. */ -void deregisterDevice(const std::shared_ptr& device); - -/** Find a single device with a custom filter. Could return nullptr if not found. */ -std::shared_ptr findDevice(const std::function&)>& filterFunction); - -/** Find devices with a custom filter */ -std::vector> findDevices(const std::function&)>& filterFunction); - -/** Find a device in the registry by its name. Could return nullptr if not found. */ -std::shared_ptr findDevice(std::string name); - -/** Find a device in the registry by its identifier. Could return nullptr if not found.*/ -std::shared_ptr findDevice(Device::Id id); - -/** Find 0, 1 or more devices in the registry by type. */ -std::vector> findDevices(Device::Type type); - -/** Get a copy of the entire device registry in its current state. */ -std::vector> getDevices(); - -/** Find devices of a certain type and cast them to the specified class */ -template -std::vector> findDevices(Device::Type type) { - auto devices = findDevices(type); - if (devices.empty()) { - return {}; - } else { - std::vector> result; - result.reserve(devices.size()); - for (auto& device : devices) { - auto target_device = std::static_pointer_cast(device); - assert(target_device != nullptr); - result.push_back(target_device); - } - return result; - } -} - -template -void findDevices(Device::Type type, std::function&)> onDeviceFound) { - auto devices_view = findDevices(type); - for (auto& device : devices_view) { - auto typed_device = std::static_pointer_cast(device); - if (!onDeviceFound(typed_device)) { - break; - } - } -} - -/** Find the first device of the specified type and cast it to the specified class */ -template -std::shared_ptr findFirstDevice(Device::Type type) { - auto devices = findDevices(type); - if (devices.empty()) { - return {}; - } else { - auto& first = devices[0]; - return std::static_pointer_cast(first); - } -} - -/** @return true if there are 1 or more devices of the specified type */ -bool hasDevice(Device::Type type); - -} diff --git a/Modules/hal-device-module/source/drivers/hal_device.cpp b/Modules/hal-device-module/source/drivers/hal_device.cpp deleted file mode 100644 index 0041237f..00000000 --- a/Modules/hal-device-module/source/drivers/hal_device.cpp +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#include -#include -#include - -#include -#include -#include -#include - -#define TAG "HalDevice" - -struct HalDevicePrivate { - std::shared_ptr halDevice; -}; - -#define GET_DATA(device) ((HalDevicePrivate*)device_get_driver_data(device)) - -static enum HalDeviceType getHalDeviceType(tt::hal::Device::Type type) { - switch (type) { - case tt::hal::Device::Type::I2c: - return HalDeviceType::HAL_DEVICE_TYPE_I2C; - case tt::hal::Device::Type::Display: - return HalDeviceType::HAL_DEVICE_TYPE_DISPLAY; - case tt::hal::Device::Type::Touch: - return HalDeviceType::HAL_DEVICE_TYPE_TOUCH; - case tt::hal::Device::Type::SdCard: - return HalDeviceType::HAL_DEVICE_TYPE_SDCARD; - case tt::hal::Device::Type::Keyboard: - return HalDeviceType::HAL_DEVICE_TYPE_KEYBOARD; - case tt::hal::Device::Type::Encoder: - return HalDeviceType::HAL_DEVICE_TYPE_ENCODER; - case tt::hal::Device::Type::Power: - return HalDeviceType::HAL_DEVICE_TYPE_POWER; - case tt::hal::Device::Type::Gps: - return HalDeviceType::HAL_DEVICE_TYPE_GPS; - case tt::hal::Device::Type::Other: - return HalDeviceType::HAL_DEVICE_TYPE_OTHER; - default: - LOG_W(TAG, "Device type %d is not implemented", static_cast(type)); - return HalDeviceType::HAL_DEVICE_TYPE_OTHER; - } -} - -extern "C" { - -HalDeviceType hal_device_get_type(struct Device* device) { - auto type = GET_DATA(device)->halDevice->getType(); - return getHalDeviceType(type); -} - -void hal_device_for_each_of_type(HalDeviceType type, void* context, bool(*onDevice)(struct Device* device, void* context)) { - struct InternalContext { - HalDeviceType typeParam; - void* contextParam; - bool(*onDeviceParam)(struct Device* device, void* context); - }; - - InternalContext internal_context = { - .typeParam = type, - .contextParam = context, - .onDeviceParam = onDevice - }; - - device_for_each_of_type(&HAL_DEVICE_TYPE, &internal_context, [](Device* device, void* context){ - auto* hal_device_private = GET_DATA(device); - auto* internal_context = static_cast(context); - auto hal_device_type = getHalDeviceType(hal_device_private->halDevice->getType()); - if (hal_device_type == internal_context->typeParam) { - if (!internal_context->onDeviceParam(device, internal_context->contextParam)) { - return false; - } - } - return true; - }); -} - -} - -namespace tt::hal { - -std::shared_ptr hal_device_get_device(::Device* device) { - auto* hal_device_private = GET_DATA(device); - return hal_device_private->halDevice; -} - -void hal_device_set_device(::Device* kernelDevice, std::shared_ptr halDevice) { - GET_DATA(kernelDevice)->halDevice = std::move(halDevice); -} - -} - -#pragma region Lifecycle - -static error_t start(Device* device) { - LOG_I(TAG, "start %s", device->name); - auto hal_device_data = new(std::nothrow) HalDevicePrivate(); - if (hal_device_data == nullptr) return ERROR_OUT_OF_MEMORY; - device_set_driver_data(device, hal_device_data); - return ERROR_NONE; -} - -static error_t stop(Device* device) { - LOG_I(TAG, "stop %s", device->name); - delete GET_DATA(device); - return ERROR_NONE; -} - -#pragma endregion - -extern "C" { - -const struct DeviceType HAL_DEVICE_TYPE { - "hal-device" -}; - -extern struct Module hal_device_module; - -Driver hal_device_driver = { - .name = "hal-device", - .compatible = (const char*[]) {"hal-device", nullptr}, - .start_device = start, - .stop_device = stop, - .api = nullptr, - .device_type = &HAL_DEVICE_TYPE, - .owner = &hal_device_module, - .internal = nullptr -}; - -} \ No newline at end of file diff --git a/Modules/hal-device-module/source/hal/Device.cpp b/Modules/hal-device-module/source/hal/Device.cpp deleted file mode 100644 index ceb17b7b..00000000 --- a/Modules/hal-device-module/source/hal/Device.cpp +++ /dev/null @@ -1,145 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#include -#include -#include -#include - -#include - -#include -#include - -namespace tt::hal { - -RecursiveMutex mutex; -static Device::Id nextId = 0; - -constexpr auto* TAG = "Devices"; - -Device::Device() : id(nextId++) {} - -static std::shared_ptr createKernelDeviceHolder(const std::shared_ptr& device) { - auto kernel_device_name = std::format("hal-device-{}", device->getId()); - LOG_I(TAG, "Registering %s with id %u as kernel device %s", device->getName().c_str(), (unsigned)device->getId(), kernel_device_name.c_str()); - auto kernel_device_holder = std::make_shared(kernel_device_name); - auto* kernel_device = kernel_device_holder->device.get(); - check(device_construct(kernel_device) == ERROR_NONE); - check(device_add(kernel_device) == ERROR_NONE); - auto* driver = driver_find_compatible("hal-device"); - check(driver); - device_set_driver(kernel_device, driver); - check(device_start(kernel_device) == ERROR_NONE); - hal_device_set_device(kernel_device, device); - return kernel_device_holder; -} - -static void destroyKernelDeviceHolder(std::shared_ptr& holder) { - auto kernel_device = holder->device.get(); - hal_device_set_device(kernel_device, nullptr); - check(device_stop(kernel_device) == ERROR_NONE); - check(device_remove(kernel_device) == ERROR_NONE); - check(device_destruct(kernel_device) == ERROR_NONE); - holder->device = nullptr; -} - -void registerDevice(const std::shared_ptr& device) { - auto scoped_mutex = mutex.asScopedLock(); - scoped_mutex.lock(); - - if (device->getKernelDeviceHolder() == nullptr) { - // Kernel device - auto kernel_device_holder = createKernelDeviceHolder(device); - device->setKernelDeviceHolder(kernel_device_holder); - } else { - LOG_W(TAG, "Device %s with id %u was already registered", device->getName().c_str(), (unsigned)device->getId()); - } -} - -void deregisterDevice(const std::shared_ptr& device) { - auto scoped_mutex = mutex.asScopedLock(); - scoped_mutex.lock(); - - // Kernel device - auto kernel_device_holder = device->getKernelDeviceHolder(); - if (kernel_device_holder) { - destroyKernelDeviceHolder(kernel_device_holder); - device->setKernelDeviceHolder(nullptr); - } else { - LOG_W(TAG, "Device %s with id %u was not registered", device->getName().c_str(), (unsigned)device->getId()); - } -} - -template -auto toVector(R&& range) { - using T = std::ranges::range_value_t; - std::vector result; - if constexpr (std::ranges::common_range) { - result.reserve(std::ranges::distance(range)); - } - std::ranges::copy(range, std::back_inserter(result)); - return result; -} - -std::vector> findDevices(const std::function&)>& filterFunction) { - auto scoped_mutex = mutex.asScopedLock(); - scoped_mutex.lock(); - - auto devices_view = getDevices() | std::views::filter([&filterFunction](auto& device) { - return filterFunction(device); - }); - return toVector(devices_view); -} - -std::shared_ptr findDevice(const std::function&)>& filterFunction) { - auto scoped_mutex = mutex.asScopedLock(); - scoped_mutex.lock(); - - auto result_set = getDevices() | std::views::filter([&filterFunction](auto& device) { - return filterFunction(device); - }); - if (!result_set.empty()) { - return result_set.front(); - } else { - return nullptr; - } -} - -std::shared_ptr findDevice(std::string name) { - return findDevice([&name](auto& device){ - return device->getName() == name; - }); -} - -std::shared_ptr findDevice(Device::Id id) { - return findDevice([id](auto& device){ - return device->getId() == id; - }); -} - -std::vector> findDevices(Device::Type type) { - return findDevices([type](auto& device) { - return device->getType() == type; - }); -} - -std::vector> getDevices() { - std::vector> devices; - device_for_each_of_type(&HAL_DEVICE_TYPE, &devices ,[](auto* kernelDevice, auto* context) { - auto devices_ptr = static_cast>*>(context); - auto hal_device = hal_device_get_device(kernelDevice); - (*devices_ptr).push_back(hal_device); - return true; - }); - return devices; -} - -bool hasDevice(Device::Type type) { - auto scoped_mutex = mutex.asScopedLock(); - scoped_mutex.lock(); - auto result_set = getDevices() | std::views::filter([&type](auto& device) { - return device->getType() == type; - }); - return !result_set.empty(); -} - -} diff --git a/Modules/hal-device-module/source/module.cpp b/Modules/hal-device-module/source/module.cpp deleted file mode 100644 index 4aef4666..00000000 --- a/Modules/hal-device-module/source/module.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#include -#include - -extern "C" { - -extern Driver hal_device_driver; - -static Driver* const hal_device_drivers[] = { - &hal_device_driver, - nullptr -}; - -Module hal_device_module = { - .name = "hal-device", - .drivers = hal_device_drivers -}; - -} diff --git a/Modules/lvgl-module/include/tactility/lvgl_module.h b/Modules/lvgl-module/include/tactility/lvgl_module.h index 4fd5d9fd..a590a15d 100644 --- a/Modules/lvgl-module/include/tactility/lvgl_module.h +++ b/Modules/lvgl-module/include/tactility/lvgl_module.h @@ -72,7 +72,7 @@ void lvgl_module_configure(struct LvglModuleConfig config); * It is a recursive mutex. * @retval true when a lock was acquired, false otherwise */ -bool lvgl_lock(void); +void lvgl_lock(void); /** * @brief Tries to lock the LVGL mutex with a timeout. diff --git a/Modules/lvgl-module/source/arch/lvgl_esp32.c b/Modules/lvgl-module/source/arch/lvgl_esp32.c index 45140b84..3251bcc1 100644 --- a/Modules/lvgl-module/source/arch/lvgl_esp32.c +++ b/Modules/lvgl-module/source/arch/lvgl_esp32.c @@ -13,19 +13,19 @@ extern void lvgl_devices_detach(); static bool initialized = false; -bool lvgl_lock(void) { - if (!initialized) return true; // We allow (fake) locking because it's safe to do so as LVGL is not running yet - return lvgl_port_lock(portMAX_DELAY); +void lvgl_lock(void) { + if (!initialized) { return; } + lvgl_port_lock(portMAX_DELAY); } bool lvgl_try_lock(uint32_t timeoutTicks) { - if (!initialized) return true; // We allow (fake) locking because it's safe to do so as LVGL is not running yet + if (!initialized) { return false; } // lvgl_port_lock expects milliseconds return lvgl_port_lock(timeoutTicks * portTICK_PERIOD_MS); } void lvgl_unlock(void) { - if (!initialized) return; + if (!initialized) { return; } lvgl_port_unlock(); } diff --git a/Modules/lvgl-module/source/arch/lvgl_posix.c b/Modules/lvgl-module/source/arch/lvgl_posix.c index 3698b686..e887f0bb 100644 --- a/Modules/lvgl-module/source/arch/lvgl_posix.c +++ b/Modules/lvgl-module/source/arch/lvgl_posix.c @@ -13,6 +13,8 @@ #include extern struct LvglModuleConfig lvgl_module_config; +extern void lvgl_devices_attach(); +extern void lvgl_devices_detach(); // Mutex for LVGL drawing static struct RecursiveMutex lvgl_mutex; @@ -37,10 +39,9 @@ static void task_unlock(void) { recursive_mutex_unlock(&task_mutex); } -bool lvgl_lock(void) { - if (!lvgl_mutex_initialised) return false; +void lvgl_lock(void) { + if (!lvgl_mutex_initialised) return; recursive_mutex_lock(&lvgl_mutex); - return true; } bool lvgl_try_lock(uint32_t timeout) { @@ -71,6 +72,9 @@ static void lvgl_task(void* arg) { check(!lvgl_task_is_interrupt_requested()); + // Must run from this task (like on_start below), otherwise the display doesn't work. + lvgl_devices_attach(); + // on_start must be called from the task, otherwise the display doesn't work if (lvgl_module_config.on_start) lvgl_module_config.on_start(); @@ -89,6 +93,8 @@ static void lvgl_task(void* arg) { if (lvgl_module_config.on_stop) lvgl_module_config.on_stop(); + lvgl_devices_detach(); + task_lock(); lvgl_task_handle = NULL; task_unlock(); diff --git a/Tactility/CMakeLists.txt b/Tactility/CMakeLists.txt index a0d87632..b4969936 100644 --- a/Tactility/CMakeLists.txt +++ b/Tactility/CMakeLists.txt @@ -7,12 +7,12 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*) list(APPEND REQUIRES_LIST TactilityKernel TactilityFreeRtos - hal-device-module lvgl-module crypt-module + gps-module + gps-generic-module lv_screenshot minitar - minmea ) if (DEFINED ENV{ESP_IDF_VERSION}) diff --git a/Tactility/Include/Tactility/SystemEvents.h b/Tactility/Include/Tactility/SystemEvents.h index dc2daa3c..3265e0f7 100644 --- a/Tactility/Include/Tactility/SystemEvents.h +++ b/Tactility/Include/Tactility/SystemEvents.h @@ -6,8 +6,6 @@ namespace tt::kernel { enum class SystemEvent { - BootInitHalBegin, - BootInitHalEnd, BootSplash, /** Gained IP address */ NetworkConnected, diff --git a/Tactility/Include/Tactility/Tactility.h b/Tactility/Include/Tactility/Tactility.h index 7078cb94..0e1b55b1 100644 --- a/Tactility/Include/Tactility/Tactility.h +++ b/Tactility/Include/Tactility/Tactility.h @@ -1,11 +1,9 @@ #pragma once -#include "tactility/concurrent/dispatcher.h" -#include "tactility/device.h" -#include "tactility/module.h" +#include +#include +#include #include -#include -#include #include @@ -40,27 +38,12 @@ private: DispatcherHandle_t handle; }; -/** @brief The configuration for the operating system - * It contains the hardware configuration, apps and services - */ -struct Configuration { - /** HAL configuration (drivers) */ - const hal::Configuration* hardware = nullptr; -}; - /** * @brief Main entry point for Tactility. * @param dtsModules List of modules from devicetree, null-terminated, non-null parameter * @param dtsDevices Array that is terminated with DTS_DEVICE_TERMINATOR */ -void run(const Configuration& config, Module* dtsModules[], DtsDevice dtsDevices[]); - -/** - * While technically nullable, this instance is always set if tt_init() succeeds. - * Could return nullptr if init was not called. - * @return the Configuration instance that was passed on to tt_init() if init is successful - */ -const Configuration* getConfiguration(); +void run(Module* dtsModules[], DtsDevice dtsDevices[]); /** Provides access to the dispatcher that runs on the main task. * @warning This dispatcher is used for WiFi and might block for some time during WiFi connection. @@ -68,11 +51,4 @@ const Configuration* getConfiguration(); */ MainDispatcher getMainDispatcher(); -namespace hal { - -/** While technically this configuration is nullable, it's never null after initHeadless() is called. */ -const Configuration* getConfiguration(); - -} // namespace hal - } // namespace tt diff --git a/Tactility/Include/Tactility/file/File.h b/Tactility/Include/Tactility/file/File.h index 0f2371e2..2148cd2a 100644 --- a/Tactility/Include/Tactility/file/File.h +++ b/Tactility/Include/Tactility/file/File.h @@ -45,15 +45,11 @@ struct FileCloser { } }; -typedef std::function(const std::string&)> FindLockFunction; - /** * @param[in] path the path to get a lock for * @return a lock instance (never null) */ -std::shared_ptr getLock(const std::string& path); - -void setFindLockFunction(const FindLockFunction& function); +std::shared_ptr getLock(const std::string& path) __attribute__((deprecated("Use file_mutex.h from TactilityKernel"))); long getSize(FILE* file); diff --git a/Tactility/Include/Tactility/file/FileLock.h b/Tactility/Include/Tactility/file/FileLock.h index a7b61580..60c36f05 100644 --- a/Tactility/Include/Tactility/file/FileLock.h +++ b/Tactility/Include/Tactility/file/FileLock.h @@ -13,8 +13,9 @@ namespace tt::file { /** * @param[in] path the path to find a lock for + * @deprecated * @return a lock instance when a lock was found, otherwise nullptr */ -std::shared_ptr findLock(const std::string& path); +std::shared_ptr findLock(const std::string& path) __attribute__((deprecated("Use file_get_mutex() from TactilityKernel"))); } diff --git a/Tactility/Include/Tactility/hal/Configuration.h b/Tactility/Include/Tactility/hal/Configuration.h deleted file mode 100644 index 8b085bb2..00000000 --- a/Tactility/Include/Tactility/hal/Configuration.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace tt::hal { - -typedef bool (*InitBoot)(); - -typedef std::vector> DeviceVector; - -typedef std::shared_ptr (*CreateDevice)(); - -struct Configuration { - /** - * Used for powering on the peripherals manually. - */ - const InitBoot initBoot = nullptr; - - const std::function createDevices = [] { return DeviceVector(); }; -}; - -} // namespace diff --git a/Tactility/Include/Tactility/hal/display/DisplayDevice.h b/Tactility/Include/Tactility/hal/display/DisplayDevice.h deleted file mode 100644 index 7918bfb7..00000000 --- a/Tactility/Include/Tactility/hal/display/DisplayDevice.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#include - -#include - -namespace tt::hal::touch { -class TouchDevice; -} - -namespace tt::hal::display { - -class DisplayDriver; - -class DisplayDevice : public Device { - -public: - - Type getType() const override { return Type::Display; } - - /** Starts the internal driver */ - virtual bool start() = 0; - virtual bool stop() = 0; - - virtual void setPowerOn(bool turnOn) {} - virtual bool isPoweredOn() const { return true; } - virtual bool supportsPowerControl() const { return false; } - - /** For e-paper screens */ - virtual void requestFullRefresh() {} - - /** Blocks until any frame already handed to this display has physically - * finished drawing. Displays that draw synchronously within their flush - * callback can rely on the default no-op; displays with an asynchronous - * refresh pipeline (e.g. e-paper, where a full refresh can take seconds) - * should override this so callers can safely do something irreversible - * (like cutting power) right after a screen update. */ - virtual void waitForFlushComplete() {} - - /** Could return nullptr if not started */ - virtual std::shared_ptr getTouchDevice() = 0; - - /** Set a value in the range [0, 255] */ - virtual void setBacklightDuty(uint8_t backlightDuty) { /* NO-OP */ } - virtual bool supportsBacklightDuty() const { return false; } - - /** Set a value in the range [0, 255] */ - virtual void setGammaCurve(uint8_t index) { /* NO-OP */ } - virtual uint8_t getGammaCurveCount() const { return 0; } - - virtual bool supportsLvgl() const = 0; - virtual bool startLvgl() = 0; - virtual bool stopLvgl() = 0; - - /** Could return nullptr if not started */ - virtual lv_display_t* getLvglDisplay() const = 0; - - virtual bool supportsDisplayDriver() const = 0; - /** Could return nullptr if not supported */ - virtual std::shared_ptr getDisplayDriver() = 0; -}; - -} // namespace tt::hal::display diff --git a/Tactility/Include/Tactility/hal/display/DisplayDriver.h b/Tactility/Include/Tactility/hal/display/DisplayDriver.h deleted file mode 100644 index 16cafae3..00000000 --- a/Tactility/Include/Tactility/hal/display/DisplayDriver.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include - -namespace tt::hal::display { - -enum class ColorFormat { - Monochrome, // 1 bpp - BGR565, - BGR565Swapped, - RGB565, - RGB565Swapped, - RGB888 -}; - -class DisplayDriver { - -public: - - virtual ~DisplayDriver() = default; - - virtual ColorFormat getColorFormat() const = 0; - virtual uint16_t getPixelWidth() const = 0; - virtual uint16_t getPixelHeight() const = 0; - virtual bool drawBitmap(int xStart, int yStart, int xEnd, int yEnd, const void* pixelData) = 0; - - /** - * Returns direct pointers to the panel's hardware frame buffer(s), if the - * underlying driver supports it (DPI/MIPI-DSI panels only). - * @param[out] outBuffers receives up to 2 frame buffer pointers - * @return number of buffers written to outBuffers (0 if unsupported) - */ - virtual uint8_t getFrameBuffers(void* outBuffers[2]) const { return 0; } -}; - -} \ No newline at end of file diff --git a/Tactility/Include/Tactility/hal/display/KernelDisplayDriver.h b/Tactility/Include/Tactility/hal/display/KernelDisplayDriver.h deleted file mode 100644 index 75765369..00000000 --- a/Tactility/Include/Tactility/hal/display/KernelDisplayDriver.h +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#pragma once - -#include - -#include - -namespace tt::hal::display { - -/** Wraps a TactilityKernel Device of type DISPLAY_TYPE as a DisplayDriver. */ -class KernelDisplayDriver final : public DisplayDriver { - - ::Device* device; - -public: - - explicit KernelDisplayDriver(::Device* device); - - ColorFormat getColorFormat() const override; - uint16_t getPixelWidth() const override; - uint16_t getPixelHeight() const override; - bool drawBitmap(int xStart, int yStart, int xEnd, int yEnd, const void* pixelData) override; - uint8_t getFrameBuffers(void* outBuffers[2]) const override; -}; - -} diff --git a/Tactility/Include/Tactility/hal/encoder/EncoderDevice.h b/Tactility/Include/Tactility/hal/encoder/EncoderDevice.h deleted file mode 100644 index 7880c45a..00000000 --- a/Tactility/Include/Tactility/hal/encoder/EncoderDevice.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include - -#include - -namespace tt::hal::encoder { - -class Display; - -class EncoderDevice : public Device { - -public: - - Type getType() const override { return Type::Encoder; } - - virtual bool startLvgl(lv_display_t* display) = 0; - virtual bool stopLvgl() = 0; - - /** Could return nullptr if not started */ - virtual lv_indev_t* getLvglIndev() = 0; -}; - -} diff --git a/Tactility/Include/Tactility/hal/gpio/Gpio.h b/Tactility/Include/Tactility/hal/gpio/Gpio.h deleted file mode 100644 index 8ef6f2a0..00000000 --- a/Tactility/Include/Tactility/hal/gpio/Gpio.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include - -namespace tt::hal::gpio { - -typedef unsigned int Pin; -constexpr Pin NO_PIN = -1; - -/** @warning The order must match GpioMode from tt_hal_gpio.h */ -enum class Mode { - Disable = 0, - Input, - Output, - OutputOpenDrain, - InputOutput, - InputOutputOpenDrain -}; - -/** Configure a single pin */ -bool configure(Pin pin, Mode mode, bool pullUp, bool pullDown); - -/** Configure a set of pins defined by their bit index */ -bool configureWithPinBitmask(uint64_t pinBitMask, Mode mode, bool pullUp, bool pullDown); - -bool setMode(Pin pin, Mode mode); - -bool getLevel(Pin pin); - -bool setLevel(Pin pin, bool level); - -int getPinCount(); - -} diff --git a/Tactility/Include/Tactility/hal/gps/GpsConfiguration.h b/Tactility/Include/Tactility/hal/gps/GpsConfiguration.h deleted file mode 100644 index bfcea9be..00000000 --- a/Tactility/Include/Tactility/hal/gps/GpsConfiguration.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace tt::hal::gps { - -enum class GpsModel { - Unknown = 0, - AG3335, - AG3352, - ATGM336H, // Casic (might work with AT6558, Neoway N58 LTE Cat.1, Neoway G2, Neoway G7A) - LS20031, - MTK, - MTK_L76B, - MTK_PA1616S, - UBLOX6, - UBLOX7, - UBLOX8, - UBLOX9, - UBLOX10, - UC6580, -}; - -const char* toString(GpsModel model); - -std::vector getModels(); - -struct GpsConfiguration { - char uartName[32]; // e.g. "Internal" or "/dev/ttyUSB0" - uint32_t baudRate; - GpsModel model; // Choosing "Unknown" will result in a probe -}; - -} diff --git a/Tactility/Include/Tactility/hal/gps/GpsDevice.h b/Tactility/Include/Tactility/hal/gps/GpsDevice.h deleted file mode 100644 index c9355e93..00000000 --- a/Tactility/Include/Tactility/hal/gps/GpsDevice.h +++ /dev/null @@ -1,124 +0,0 @@ -#pragma once - -#include -#include "GpsConfiguration.h" -#include "Satellites.h" - -#include -#include - -#include -#include - -namespace tt::hal::gps { - -enum class GpsResponse { - None, - NotAck, - FrameErrors, - Ok, -}; - -class GpsDevice : public Device { - -public: - - typedef int GgaSubscriptionId; - typedef int RmcSubscriptionId; - - enum class State { - PendingOn, - On, - Error, - PendingOff, - Off - }; - -private: - - struct GgaSubscription { - GgaSubscriptionId id; - std::shared_ptr> onData; - }; - - struct RmcSubscription { - RmcSubscriptionId id; - std::shared_ptr> onData; - }; - - const GpsConfiguration configuration; - RecursiveMutex mutex; - std::unique_ptr thread; - bool threadInterrupted = false; - std::vector ggaSubscriptions; - std::vector rmcSubscriptions; - GgaSubscriptionId lastSatelliteSubscriptionId = 0; - RmcSubscriptionId lastRmcSubscriptionId = 0; - GpsModel model = GpsModel::Unknown; - State state = State::Off; - - int32_t threadMain(); - - bool isThreadInterrupted() const; - - void setState(State newState); - -public: - - explicit GpsDevice(GpsConfiguration configuration) : configuration(std::move(configuration)) {} - - ~GpsDevice() override = default; - - Type getType() const override { return Type::Gps; } - - std::string getName() const override { - if (model != GpsModel::Unknown) { - return toString(model); - } else { - return "Unknown GPS"; - } - } - - std::string getDescription() const override { return ""; } - - bool start(); - bool stop(); - - GgaSubscriptionId subscribeGga(const std::function& onData) { - auto lock = mutex.asScopedLock(); - lock.lock(); - ggaSubscriptions.push_back({ - .id = ++lastSatelliteSubscriptionId, - .onData = std::make_shared>(onData) - }); - return lastSatelliteSubscriptionId; - } - - void unsubscribeGga(GgaSubscriptionId subscriptionId) { - auto lock = mutex.asScopedLock(); - lock.lock(); - std::erase_if(ggaSubscriptions, [subscriptionId](auto& subscription) { return subscription.id == subscriptionId; }); - } - - RmcSubscriptionId subscribeRmc(const std::function& onData) { - auto lock = mutex.asScopedLock(); - lock.lock(); - rmcSubscriptions.push_back({ - .id = ++lastRmcSubscriptionId, - .onData = std::make_shared>(onData) - }); - return lastRmcSubscriptionId; - } - - void unsubscribeRmc(RmcSubscriptionId subscriptionId) { - auto lock = mutex.asScopedLock(); - lock.lock(); - std::erase_if(rmcSubscriptions, [subscriptionId](auto& subscription) { return subscription.id == subscriptionId; }); - } - - GpsModel getModel() const; - - State getState() const; -}; - -} diff --git a/Tactility/Include/Tactility/hal/gps/Satellites.h b/Tactility/Include/Tactility/hal/gps/Satellites.h deleted file mode 100644 index cda420e4..00000000 --- a/Tactility/Include/Tactility/hal/gps/Satellites.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include -#include - -#include - -#include -#include - -namespace tt::hal::gps { - -/** Thread-safe storage of recent satellites */ -class SatelliteStorage { - -public: - - static constexpr size_t recordCount = 32; - -private: - - struct SatelliteRecord { - minmea_sat_info data { - .nr = 0, - .elevation = 0, - .azimuth = 0, - .snr = 0 - }; - TickType_t lastUpdated = 0; - bool inUse = false; - }; - - RecursiveMutex mutex; - std::array records; - uint16_t recycleTimeSeconds; - uint16_t recentTimeSeconds; - - SatelliteRecord* findRecord(int number); - - SatelliteRecord* findUnusedRecord(); - - SatelliteRecord* findRecordToRecycle(); - - /** Tries to find an existing record, otherwise return a free one, otherwise return the oldest active one */ - SatelliteRecord* findWithFallback(int number); - -public: - - explicit SatelliteStorage( - uint16_t recycleTimeSeconds = 120, - uint16_t recentTimeSeconds = 60 - ) : recycleTimeSeconds(recycleTimeSeconds), recentTimeSeconds(recentTimeSeconds) {} - - void notify(const minmea_sat_info& info); - - void getRecords(const std::function& onRecord) const; -}; - -} // namespace tt::hal::gps diff --git a/Tactility/Include/Tactility/hal/i2c/I2cDevice.h b/Tactility/Include/Tactility/hal/i2c/I2cDevice.h deleted file mode 100644 index 97ad0249..00000000 --- a/Tactility/Include/Tactility/hal/i2c/I2cDevice.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include -#include - -namespace tt::hal::i2c { - -/** - * Represents an I2C peripheral at a specific port and address. - * It helps to read and write registers. - * - * All read and write calls are thread-safe. - * @deprecated Use the device API from the Kernel project - */ -class I2cDevice : public Device { - -protected: - - ::Device* controller; - uint8_t address; - - static constexpr TickType_t DEFAULT_TIMEOUT = 1000 / portTICK_PERIOD_MS; - - bool read(uint8_t* data, size_t dataSize, TickType_t timeout = DEFAULT_TIMEOUT); - bool write(const uint8_t* data, uint16_t dataSize, TickType_t timeout = DEFAULT_TIMEOUT); - bool writeRead(const uint8_t* writeData, size_t writeDataSize, uint8_t* readData, size_t readDataSize, TickType_t timeout = DEFAULT_TIMEOUT); - bool readRegister8(uint8_t reg, uint8_t& result) const; - bool writeRegister(uint8_t reg, const uint8_t* data, uint16_t dataSize, TickType_t timeout = DEFAULT_TIMEOUT); - bool writeRegister8(uint8_t reg, uint8_t value) const; - bool readRegister12(uint8_t reg, float& out) const; - bool readRegister14(uint8_t reg, float& out) const; - bool readRegister16(uint8_t reg, uint16_t& out) const; - bool bitOn(uint8_t reg, uint8_t bitmask) const; - bool bitOff(uint8_t reg, uint8_t bitmask) const; - bool bitOnByIndex(uint8_t reg, uint8_t index) const { return bitOn(reg, 1 << index); } - bool bitOffByIndex(uint8_t reg, uint8_t index) const { return bitOff(reg, 1 << index); } - -public: - - explicit I2cDevice(::Device* controller, uint32_t address) : controller(controller), address(address) {} - - Type getType() const override { return Type::I2c; } - - ::Device* getController() const { return controller; } - - uint8_t getAddress() const { return address; } -}; - -} // namespace tt::hal::i2c diff --git a/Tactility/Include/Tactility/hal/keyboard/KeyboardDevice.h b/Tactility/Include/Tactility/hal/keyboard/KeyboardDevice.h deleted file mode 100644 index 7c996569..00000000 --- a/Tactility/Include/Tactility/hal/keyboard/KeyboardDevice.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include - -#include - -namespace tt::hal::keyboard { - -class Display; - -class KeyboardDevice : public Device { - -public: - - Type getType() const override { return Type::Keyboard; } - - virtual bool startLvgl(lv_display_t* display) = 0; - virtual bool stopLvgl() = 0; - - /** @return true when the keyboard currently is physically attached */ - virtual bool isAttached() const = 0; - - /** Could return nullptr if not started */ - virtual lv_indev_t* getLvglIndev() = 0; -}; - -} diff --git a/Tactility/Include/Tactility/hal/power/PowerDevice.h b/Tactility/Include/Tactility/hal/power/PowerDevice.h deleted file mode 100644 index 04173422..00000000 --- a/Tactility/Include/Tactility/hal/power/PowerDevice.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace tt::hal::power { - -class PowerDevice : public Device { - -public: - - PowerDevice(); - ~PowerDevice() override; - - Type getType() const override { return Type::Power; } - - enum class MetricType { - IsCharging, // bool - Current, // int32_t, mAh - battery current: either during charging (positive value) or discharging (negative value) - BatteryVoltage, // uint32_t, mV - ChargeLevel, // uint8_t [0, 100] - }; - - union MetricData { - int32_t valueAsInt32 = 0; - uint32_t valueAsUint32; - uint8_t valueAsUint8; - float valueAsFloat; - bool valueAsBool; - }; - - virtual bool supportsMetric(MetricType type) const = 0; - - /** - * @return false when metric is not supported or (temporarily) not available. - */ - virtual bool getMetric(MetricType type, MetricData& data) = 0; - - virtual bool supportsChargeControl() const { return false; } - virtual bool isAllowedToCharge() const { return false; } - virtual void setAllowedToCharge(bool canCharge) { /* NO-OP*/ } - - virtual bool supportsQuickCharge() const { return false; } - virtual bool isQuickChargeEnabled() const { return false; } - virtual void setQuickChargeEnabled(bool enabled) { /* NO-OP */ } - - virtual bool supportsPowerOff() const { return false; } - virtual void powerOff() { /* NO-OP*/ } - -private: - - /** Creates the kernel-level power_supply device that exposes this instance to TactilityKernel. */ - void createPowerSupplyDevice(); - - /** Destroys the kernel-level power_supply device created by createPowerSupplyDevice(). */ - void destroyPowerSupplyDevice(); - - std::string kernelDeviceName; - KernelDevice kernelDevice {}; -}; - -} diff --git a/Tactility/Include/Tactility/hal/touch/KernelTouchDriver.h b/Tactility/Include/Tactility/hal/touch/KernelTouchDriver.h deleted file mode 100644 index 30ff49e2..00000000 --- a/Tactility/Include/Tactility/hal/touch/KernelTouchDriver.h +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#pragma once - -#include - -#include - -namespace tt::hal::touch { - -/** Wraps a TactilityKernel Device of type POINTER_TYPE as a TouchDriver. */ -class KernelTouchDriver final : public TouchDriver { - - ::Device* device; - -public: - - explicit KernelTouchDriver(::Device* device); - - bool getTouchedPoints(uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* pointCount, uint8_t maxPointCount) override; -}; - -} diff --git a/Tactility/Include/Tactility/hal/touch/TouchDevice.h b/Tactility/Include/Tactility/hal/touch/TouchDevice.h deleted file mode 100644 index 1206ca1b..00000000 --- a/Tactility/Include/Tactility/hal/touch/TouchDevice.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include -#include "TouchDriver.h" - -#include - -namespace tt::hal::touch { - -class Display; - -class TouchDevice : public Device { - -public: - - Type getType() const override { return Type::Touch; } - - virtual bool start() = 0; - virtual bool stop() = 0; - - virtual bool supportsLvgl() const = 0; - virtual bool startLvgl(lv_display_t* display) = 0; - virtual bool stopLvgl() = 0; - - /** Could return nullptr if not started */ - virtual lv_indev_t* getLvglIndev() = 0; - - virtual bool supportsTouchDriver() = 0; - - virtual bool supportsCalibration() const { return false; } - - /** Could return nullptr if not supported */ - virtual std::shared_ptr getTouchDriver() = 0; -}; - -} diff --git a/Tactility/Include/Tactility/hal/touch/TouchDriver.h b/Tactility/Include/Tactility/hal/touch/TouchDriver.h deleted file mode 100644 index 58aa2515..00000000 --- a/Tactility/Include/Tactility/hal/touch/TouchDriver.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include - -namespace tt::hal::touch { - -class TouchDriver { - -public: - - /** - * Get the coordinates for the currently touched points on the screen. - * - * @param[in] x array of X coordinates - * @param[in] y array of Y coordinates - * @param[in] strength optional array of strengths (nullable) - * @param[in] pointCount the number of points currently touched on the screen - * @param[in] maxPointCount the maximum number of points that can be touched at once - * - * @return true when touched and coordinates are available - */ - virtual bool getTouchedPoints(uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* pointCount, uint8_t maxPointCount) = 0; -}; - -} diff --git a/Tactility/Include/Tactility/lvgl/LvglSync.h b/Tactility/Include/Tactility/lvgl/LvglSync.h index c3c9841e..f6f7dd68 100644 --- a/Tactility/Include/Tactility/lvgl/LvglSync.h +++ b/Tactility/Include/Tactility/lvgl/LvglSync.h @@ -14,11 +14,11 @@ constexpr TickType_t defaultLockTime = 500 / portTICK_PERIOD_MS; * @warning when passing zero, we wait forever, as this is the default behaviour for esp_lvgl_port, and we want it to remain consistent * @deprecated Use lvgl_lock() or lvgl_try_lock() from lvgl-module instead. */ -bool lock(TickType_t timeout = portMAX_DELAY); +bool lock(TickType_t timeout = portMAX_DELAY) __attribute__((deprecated("Use lvgl_lock() from lvgl-module"))); /** @deprecated Use lvgl_unlock() from lvgl-module instead. */ -void unlock(); +void unlock() __attribute__((deprecated("Use lvgl_unlock() from lvgl-module"))); -std::shared_ptr getSyncLock(); +std::shared_ptr getSyncLock() __attribute__((deprecated("Use lvgl locking functions from lvgl-module"))); } // namespace diff --git a/Tactility/Include/Tactility/service/gps/GpsService.h b/Tactility/Include/Tactility/service/gps/GpsService.h deleted file mode 100644 index 4c1fc6c5..00000000 --- a/Tactility/Include/Tactility/service/gps/GpsService.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace tt::service::gps { - -class GpsService final : public Service { - - struct GpsDeviceRecord { - std::shared_ptr device = nullptr; - hal::gps::GpsDevice::GgaSubscriptionId satelliteSubscriptionId = -1; - hal::gps::GpsDevice::RmcSubscriptionId rmcSubscriptionId = -1; - }; - - minmea_sentence_rmc rmcRecord; - TickType_t rmcTime = 0; - - minmea_sentence_gga ggaRecord; - TickType_t ggaTime = 0; - - RecursiveMutex mutex; - Mutex stateMutex; - std::vector deviceRecords; - std::shared_ptr> statePubSub = std::make_shared>(); - std::unique_ptr paths; - State state = State::Off; - - bool startGpsDevice(GpsDeviceRecord& deviceRecord); - static bool stopGpsDevice(GpsDeviceRecord& deviceRecord); - - /** return nullptr when not found */ - GpsDeviceRecord* findGpsRecord(const std::shared_ptr& record); - - void onGgaSentence(hal::Device::Id deviceId, const minmea_sentence_gga& gga); - void onRmcSentence(hal::Device::Id deviceId, const minmea_sentence_rmc& rmc); - - void setState(State newState); - - void addGpsDevice(const std::shared_ptr& device); - void removeGpsDevice(const std::shared_ptr& device); - - bool getConfigurationFilePath(std::string& output) const; - -public: - - bool onStart(ServiceContext &serviceContext) override; - void onStop(ServiceContext &serviceContext) override; - - bool addGpsConfiguration(hal::gps::GpsConfiguration configuration); - bool removeGpsConfiguration(hal::gps::GpsConfiguration configuration); - bool getGpsConfigurations(std::vector& configurations) const; - - bool startReceiving(); - void stopReceiving(); - State getState() const; - - bool hasCoordinates() const; - bool getCoordinates(minmea_sentence_rmc& rmc) const; - bool getGga(minmea_sentence_gga& gga) const; - - /** @return GPS service pubsub that broadcasts State* objects */ - std::shared_ptr> getStatePubsub() const { return statePubSub; } -}; - -std::shared_ptr findGpsService(); - -} // tt::service::gps diff --git a/Tactility/Include/Tactility/service/gps/GpsState.h b/Tactility/Include/Tactility/service/gps/GpsState.h deleted file mode 100644 index dae2195d..00000000 --- a/Tactility/Include/Tactility/service/gps/GpsState.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -namespace tt::service::gps { - -enum class State { - OnPending, - On, - OffPending, - Off -}; - -} diff --git a/Tactility/Include/Tactility/service/gps/GpsUtil.h b/Tactility/Include/Tactility/service/gps/GpsUtil.h deleted file mode 100644 index 6c312967..00000000 --- a/Tactility/Include/Tactility/service/gps/GpsUtil.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include - -namespace tt::hal::gps { - -/** @return true when the input float is valid (contains non-zero values) */ -inline bool isValid(const minmea_float& inFloat) { return inFloat.value != 0 && inFloat.scale != 0; } - -} diff --git a/Tactility/Private/Tactility/hal/HalPrivate.h b/Tactility/Private/Tactility/hal/HalPrivate.h deleted file mode 100644 index 3e471288..00000000 --- a/Tactility/Private/Tactility/hal/HalPrivate.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "Tactility/hal/Configuration.h" - -namespace tt::hal { - -void init(const Configuration& configuration); - -} // namespace diff --git a/Tactility/Private/Tactility/hal/SdCard.h b/Tactility/Private/Tactility/hal/SdCard.h index d09379a4..6b486ef4 100644 --- a/Tactility/Private/Tactility/hal/SdCard.h +++ b/Tactility/Private/Tactility/hal/SdCard.h @@ -7,14 +7,6 @@ namespace tt::hal::sdcard { -/** - * Attempt to find an SD card that the specified belongs to, - * and returns its lock if the SD card is mounted. Otherwise it returns nullptr. - * @param[in] a path on a file system (e.g. file, directory, etc.) - * @return the lock of a mounted SD card or otherwise null - */ -std::shared_ptr findSdCardLock(const std::string& path); - -void mountAll(); +void startAll(); } diff --git a/Tactility/Private/Tactility/hal/gps/Cas.h b/Tactility/Private/Tactility/hal/gps/Cas.h deleted file mode 100644 index d203ab15..00000000 --- a/Tactility/Private/Tactility/hal/gps/Cas.h +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Source: https://raw.githubusercontent.com/meshtastic/firmware/3b0232de1b6282eacfbff6e50b68fca7e67b8511/src/gps/cas.h - */ -#pragma once - -#include - -// CASIC binary message definitions -// Reference: https://www.icofchina.com/d/file/xiazai/2020-09-22/20f1b42b3a11ac52089caf3603b43fb5.pdf -// ATGM33H-5N: https://www.icofchina.com/pro/mokuai/2016-08-01/4.html -// (https://www.icofchina.com/d/file/xiazai/2016-12-05/b5c57074f4b1fcc62ba8c7868548d18a.pdf) - -// NEMA (Class ID - 0x4e) message IDs -#define CAS_NEMA_GGA 0x00 -#define CAS_NEMA_GLL 0x01 -#define CAS_NEMA_GSA 0x02 -#define CAS_NEMA_GSV 0x03 -#define CAS_NEMA_RMC 0x04 -#define CAS_NEMA_VTG 0x05 -#define CAS_NEMA_GST 0x07 -#define CAS_NEMA_ZDA 0x08 -#define CAS_NEMA_DHV 0x0D - -// Size of a CAS-ACK-(N)ACK message (14 bytes) -#define CAS_ACK_NACK_MSG_SIZE 0x0E - -// CFG-RST (0x06, 0x02) -// Factory reset -constexpr uint8_t _message_CAS_CFG_RST_FACTORY[] = { - 0xFF, 0x03, // Fields to clear - 0x01, // Reset Mode: Controlled Software reset - 0x03 // Startup Mode: Factory -}; - -// CFG_RATE (0x06, 0x01) -// 1HZ update rate, this should always be the case after -// factory reset but update it regardless -constexpr uint8_t _message_CAS_CFG_RATE_1HZ[] = { - 0xE8, 0x03, // Update Rate: 0x03E8 = 1000ms - 0x00, 0x00 // Reserved -}; - -// CFG-NAVX (0x06, 0x07) -// Initial ATGM33H-5N configuration, Updates for Dynamic Mode, Fix Mode, and SV system -// Qwirk: The ATGM33H-5N-31 should only support GPS+BDS, however it will happily enable -// and use GPS+BDS+GLONASS iff the correct CFG_NAVX command is used. -constexpr uint8_t _message_CAS_CFG_NAVX_CONF[] = { - 0x03, 0x01, 0x00, 0x00, // Update Mask: Dynamic Mode, Fix Mode, Nav Settings - 0x03, // Dynamic Mode: Automotive - 0x03, // Fix Mode: Auto 2D/3D - 0x00, // Min SV - 0x00, // Max SVs - 0x00, // Min CNO - 0x00, // Reserved1 - 0x00, // Init 3D fix - 0x00, // Min Elevation - 0x00, // Dr Limit - 0x07, // Nav System: 2^0 = GPS, 2^1 = BDS 2^2 = GLONASS: 2^3 - // 3=GPS+BDS, 7=GPS+BDS+GLONASS - 0x00, 0x00, // Rollover Week - 0x00, 0x00, 0x00, 0x00, // Fix Altitude - 0x00, 0x00, 0x00, 0x00, // Fix Height Error - 0x00, 0x00, 0x00, 0x00, // PDOP Maximum - 0x00, 0x00, 0x00, 0x00, // TDOP Maximum - 0x00, 0x00, 0x00, 0x00, // Position Accuracy Max - 0x00, 0x00, 0x00, 0x00, // Time Accuracy Max - 0x00, 0x00, 0x00, 0x00 // Static Hold Threshold -}; diff --git a/Tactility/Private/Tactility/hal/gps/GpsInit.h b/Tactility/Private/Tactility/hal/gps/GpsInit.h deleted file mode 100644 index 6197a1d7..00000000 --- a/Tactility/Private/Tactility/hal/gps/GpsInit.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include "Tactility/hal/gps/GpsDevice.h" - -struct Device; - -namespace tt::hal::gps { - -/** - * Init sequence on UART for a specific GPS model. - */ -bool init(::Device* uart, GpsModel type); - -} diff --git a/Tactility/Private/Tactility/hal/gps/Probe.h b/Tactility/Private/Tactility/hal/gps/Probe.h deleted file mode 100644 index 6f91b1f8..00000000 --- a/Tactility/Private/Tactility/hal/gps/Probe.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -struct Device; - -namespace tt::hal::gps { - -GpsModel probe(::Device* uart); - -} diff --git a/Tactility/Private/Tactility/hal/gps/Ublox.h b/Tactility/Private/Tactility/hal/gps/Ublox.h deleted file mode 100644 index 64398d32..00000000 --- a/Tactility/Private/Tactility/hal/gps/Ublox.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "Tactility/hal/gps/GpsDevice.h" - -#include -#include - -struct Device; - -namespace tt::hal::gps::ublox { - -void checksum(uint8_t* message, size_t length); - -// From https://github.com/meshtastic/firmware/blob/7648391f91f2b84e367ae2b38220b30936fb45b1/src/gps/GPS.cpp#L128 -uint8_t makePacket(uint8_t classId, uint8_t messageId, const uint8_t* payload, uint8_t payloadSize, uint8_t* bufferOut); - -GpsModel probe(::Device* uart); - -bool init(::Device* uart, GpsModel model); - -} diff --git a/Tactility/Private/Tactility/lvgl/LvglPrivate.h b/Tactility/Private/Tactility/lvgl/LvglPrivate.h deleted file mode 100644 index 0567d168..00000000 --- a/Tactility/Private/Tactility/lvgl/LvglPrivate.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -namespace tt::lvgl { - -void attachDevices(); - -void detachDevices(); - -} diff --git a/Tactility/Source/MountPoints.cpp b/Tactility/Source/MountPoints.cpp index 32a6866b..4f79967d 100644 --- a/Tactility/Source/MountPoints.cpp +++ b/Tactility/Source/MountPoints.cpp @@ -1,7 +1,6 @@ #include "Tactility/MountPoints.h" #include "Tactility/TactilityConfig.h" -#include #include diff --git a/Tactility/Source/SystemEvents.cpp b/Tactility/Source/SystemEvents.cpp index e34cc274..6a761bdd 100644 --- a/Tactility/Source/SystemEvents.cpp +++ b/Tactility/Source/SystemEvents.cpp @@ -24,10 +24,6 @@ static std::list subscriptions; static const char* getEventName(SystemEvent event) { switch (event) { using enum SystemEvent; - case BootInitHalBegin: - return TT_STRINGIFY(BootInitHalBegin); - case BootInitHalEnd: - return TT_STRINGIFY(BootInitHalEnd); case BootSplash: return TT_STRINGIFY(BootSplash); case NetworkConnected: diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 83517aff..53604d95 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -14,14 +14,15 @@ #include #include #include -#include -#include #include #include #include #include #include +#include +#include + #include #include #include @@ -31,7 +32,6 @@ #include #include #include -#include #include #include #include @@ -41,7 +41,8 @@ #endif #include "Tactility/Paths.h" - +#include "Tactility/SystemEvents.h" +#include "Tactility/hal/SdCard.h" #include @@ -49,9 +50,10 @@ namespace tt { constexpr auto* TAG = "Tactility"; -static const Configuration* config_instance = nullptr; static DispatcherHandle_t mainDispatcherHandle = dispatcher_alloc(); +void initFileMutexForLvgl(); + namespace { void mainDispatcherTrampoline(void* context) { @@ -75,7 +77,6 @@ bool MainDispatcher::dispatch(Function function, TickType_t timeout) const { namespace service { // Primary namespace audio { extern const ServiceManifest manifest; } - namespace gps { extern const ServiceManifest manifest; } namespace wifi { extern const ServiceManifest manifest; } #ifdef ESP_PLATFORM namespace development { extern const ServiceManifest manifest; } @@ -183,8 +184,6 @@ static void registerInternalApps() { } if (device_exists_of_type(&DISPLAY_TYPE)) { addAppManifest(app::kerneldisplay::manifest); - } else if (hal::hasDevice(hal::Device::Type::Display)) { - addAppManifest(app::display::manifest); } addAppManifest(app::files::manifest); addAppManifest(app::fileselection::manifest); @@ -322,7 +321,6 @@ static void registerAndStartPrimaryServices() { if (device_exists_of_type(&AUDIO_STREAM_TYPE)) { addService(service::audio::manifest); } - addService(service::gps::manifest); addService(service::wifi::manifest); #ifdef ESP_PLATFORM addService(service::development::manifest); @@ -340,7 +338,8 @@ void createTempDirectory() { auto data_path = getUserDataPath(); auto temp_path = std::format("{}/tmp", data_path); if (!file::isDirectory(temp_path)) { - auto lock = file::getLock(data_path)->asScopedLock(); + auto lockable = file::getLock(data_path); + auto lock = lockable->asScopedLock(); if (lock.lock(1000 / portTICK_PERIOD_MS)) { if (!file::findOrCreateParentDirectory(temp_path, 0777)) { LOG_E(TAG, "Failed to create %s", data_path.c_str()); @@ -366,40 +365,39 @@ void registerApps() { registerInstalledAppsFromFileSystems(); } -void run(const Configuration& config, Module* dtsModules[], DtsDevice dtsDevices[]) { +void run(Module* dtsModules[], DtsDevice dtsDevices[]) { LOG_I(TAG, "Tactility v%s on %s (%s)", TT_VERSION, CONFIG_TT_DEVICE_NAME, CONFIG_TT_DEVICE_ID); - assert(config.hardware); - LOG_I(TAG, "Initializing kernel"); if (kernel_init(dtsModules, dtsDevices) != ERROR_NONE) { LOG_E(TAG, "Failed to initialize kernel"); return; } - // hal-device-module - check(module_construct_add_start(&hal_device_module) == ERROR_NONE); - - // crypt-module - check(module_construct_add_start(&crypt_module) == ERROR_NONE); - - // Assign early so starting services can use it - config_instance = &config; + check(module_ensure_started(&crypt_module) == ERROR_NONE); + check(module_ensure_started(&gps_module) == ERROR_NONE); + check(module_ensure_started(&gps_generic_module) == ERROR_NONE); #ifdef ESP_PLATFORM initEsp(); #endif - file::setFindLockFunction(file::findLock); + settings::initTimeZone(); - hal::init(*config.hardware); + + // Attempt to start all disabled SD cards (some require delayed init) + hal::sdcard::startAll(); + network::ntp::init(); bluetooth::systemStart(); registerAndStartPrimaryServices(); + // Must start right before LVGL + initFileMutexForLvgl(); + lvgl_module_configure((LvglModuleConfig) { - .on_start = lvgl::attachDevices, - .on_stop = lvgl::detachDevices, + .on_start = nullptr, + .on_stop = nullptr, .task_priority = THREAD_PRIORITY_HIGHER, /** Minimum seems to be about 3500. In some scenarios, the WiFi app crashes at 8192, * so we now have 9120 to run in a stable manner. We should figure out a way to avoid this. @@ -409,9 +407,7 @@ void run(const Configuration& config, Module* dtsModules[], DtsDevice dtsDevices .task_affinity = getCpuAffinityConfiguration().graphics #endif }); - check(module_construct(&lvgl_module) == ERROR_NONE); - check(module_add(&lvgl_module) == ERROR_NONE); - lvgl::start(); + check(module_ensure_started(&lvgl_module) == ERROR_NONE); registerAndStartSecondaryServices(); @@ -428,11 +424,6 @@ void run(const Configuration& config, Module* dtsModules[], DtsDevice dtsDevices } } -/** return the configuration or nullptr if it's not initialized */ -const Configuration* getConfiguration() { - return config_instance; -} - MainDispatcher getMainDispatcher() { return MainDispatcher(mainDispatcherHandle); } diff --git a/Tactility/Source/app/AppInstall.cpp b/Tactility/Source/app/AppInstall.cpp index b756a0e4..9e567052 100644 --- a/Tactility/Source/app/AppInstall.cpp +++ b/Tactility/Source/app/AppInstall.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -118,8 +117,10 @@ bool install(const std::string& path) { return false; } - auto target_path_lock = file::getLock(app_parent_path)->asScopedLock(); - auto source_path_lock = file::getLock(path)->asScopedLock(); + auto target_path_lockable = file::getLock(app_parent_path); + auto source_path_lockable = file::getLock(path); + auto target_path_lock = target_path_lockable->asScopedLock(); + auto source_path_lock = source_path_lockable->asScopedLock(); target_path_lock.lock(); source_path_lock.lock(); LOG_I(TAG, "Extracting app from %s to %s", path.c_str(), app_target_path.c_str()); diff --git a/Tactility/Source/app/addgps/AddGps.cpp b/Tactility/Source/app/addgps/AddGps.cpp index f5bf3ba3..fafe6f5d 100644 --- a/Tactility/Source/app/addgps/AddGps.cpp +++ b/Tactility/Source/app/addgps/AddGps.cpp @@ -1,17 +1,19 @@ #include #include #include -#include #include #include -#include -#include "tactility/drivers/uart_controller.h" -#include -#include +#include #include #include +#include +#include + +#include +#include + namespace tt::app::addgps { constexpr auto* TAG = "AddGps"; @@ -29,6 +31,14 @@ class AddGpsApp final : public App { std::array baudRates = { 9600, 19200, 28800, 38400, 57600, 115200 }; const char* baudRatesDropdownValues = "9600\n19200\n28800\n38400\n57600\n115200"; + static std::vector getModelNames() { + std::vector result; + for (int model = GpsModel::GPS_MODEL_UNKNOWN; model <= GpsModel::GPS_MODEL_UC6580; model++) { + result.emplace_back(gps_model_to_string(static_cast(model))); + } + return result; + } + static void onAddGpsCallback(lv_event_t* event) { auto* app = (AddGpsApp*)lv_event_get_user_data(event); app->onAddGps(); @@ -37,38 +47,24 @@ class AddGpsApp final : public App { void onAddGps() { auto selected_baud_index = lv_dropdown_get_selected(baudDropdown); - auto new_configuration = hal::gps::GpsConfiguration { - .uartName = { 0x00 }, - .baudRate = baudRates[selected_baud_index], + GpsConfiguration new_configuration = { + .uart_name = { 0x00 }, + .baud_rate = baudRates[selected_baud_index], // Warning: This assumes that the enum is a regularly indexed one that starts at 0 - .model = (hal::gps::GpsModel)lv_dropdown_get_selected(modelDropdown) + .model = (GpsModel)lv_dropdown_get_selected(modelDropdown) }; - lv_dropdown_get_selected_str(uartDropdown, new_configuration.uartName, sizeof(new_configuration.uartName)); - if (new_configuration.uartName[0] == 0x00) { + lv_dropdown_get_selected_str(uartDropdown, new_configuration.uart_name, sizeof(new_configuration.uart_name)); + if (new_configuration.uart_name[0] == 0x00) { alertdialog::start("Error", "You must select a bus/uart."); return; } - LOG_I(TAG, "Saving: uart=%s, model=%d, baud=%u", new_configuration.uartName, (int)new_configuration.model, (unsigned)new_configuration.baudRate); - - auto service = service::gps::findGpsService(); - std::vector configurations; - if (service != nullptr) { - service->getGpsConfigurations(configurations); - for (auto& stored_configuration: configurations) { - if (strcmp(stored_configuration.uartName, new_configuration.uartName) == 0) { - auto message = std::string("Bus \"{}\" is already in use in another configuration", (const char*)new_configuration.uartName); - app::alertdialog::start("Error", message.c_str()); - return; - } - } - - if (!service->addGpsConfiguration(new_configuration)) { - app::alertdialog::start("Error", "Failed to add configuration"); - } else { - stop(); - } + LOG_I(TAG, "Saving: uart=%s, model=%d, baud=%u", new_configuration.uart_name, (int)new_configuration.model, (unsigned)new_configuration.baud_rate); + if (gps_settings_add_configuration(&new_configuration) != ERROR_NONE) { + alertdialog::start("Error", "Failed to add configuration"); + } else { + stop(); } } @@ -137,7 +133,7 @@ public: modelDropdown = lv_dropdown_create(model_wrapper); - auto model_names = hal::gps::getModels(); + auto model_names = getModelNames(); auto model_options = string::join(model_names, "\n"); lv_dropdown_set_options(modelDropdown, model_options.c_str()); lv_obj_align(modelDropdown, LV_ALIGN_TOP_RIGHT, 0, 0); diff --git a/Tactility/Source/app/apphub/AppHubApp.cpp b/Tactility/Source/app/apphub/AppHubApp.cpp index 7410eead..20979c47 100644 --- a/Tactility/Source/app/apphub/AppHubApp.cpp +++ b/Tactility/Source/app/apphub/AppHubApp.cpp @@ -58,7 +58,8 @@ class AppHubApp final : public App { void onRefreshSuccess() { LOG_I(TAG, "Request success"); - auto lock = lvgl::getSyncLock()->asScopedLock(); + auto lockable = lvgl::getSyncLock(); + auto lock = lockable->asScopedLock(); lock.lock(); showApps(); @@ -66,7 +67,8 @@ class AppHubApp final : public App { void onRefreshError(const char* error) { LOG_E(TAG, "Request failed: %s", error); - auto lock = lvgl::getSyncLock()->asScopedLock(); + auto lockable = lvgl::getSyncLock(); + auto lock = lockable->asScopedLock(); lock.lock(); showRefreshFailedError("Cannot reach server"); diff --git a/Tactility/Source/app/apphub/AppHubEntry.cpp b/Tactility/Source/app/apphub/AppHubEntry.cpp index f9179c01..023d3593 100644 --- a/Tactility/Source/app/apphub/AppHubEntry.cpp +++ b/Tactility/Source/app/apphub/AppHubEntry.cpp @@ -21,7 +21,8 @@ static bool parseEntry(const cJSON* object, AppHubEntry& entry) { } bool parseJson(const std::string& filePath, std::vector& entries) { - auto lock = file::getLock(filePath)->asScopedLock(); + auto lockable = file::getLock(filePath); + auto lock = lockable->asScopedLock(); lock.lock(); auto data = file::readString(filePath); diff --git a/Tactility/Source/app/boot/Boot.cpp b/Tactility/Source/app/boot/Boot.cpp index 0838c680..c7612b14 100644 --- a/Tactility/Source/app/boot/Boot.cpp +++ b/Tactility/Source/app/boot/Boot.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -36,10 +35,6 @@ constexpr auto* TAG = "Boot"; extern const AppManifest manifest; -static std::shared_ptr getHalDisplay() { - return hal::findFirstDevice(hal::Device::Type::Display); -} - class BootApp : public App { // Snapshot of hal::usb::isUsbBootMode(), taken before the boot thread starts and @@ -58,31 +53,7 @@ class BootApp : public App { getCpuAffinityConfiguration().system ); - static void setupHalDisplay() { - const auto hal_display = getHalDisplay(); - if (hal_display == nullptr) { - return; - } - - settings::display::DisplaySettings settings; - if (settings::display::load(settings)) { - if (hal_display->getGammaCurveCount() > 0) { - hal_display->setGammaCurve(settings.gammaCurve); - LOG_I(TAG, "Gamma curve %d", settings.gammaCurve); - } - } else { - settings = settings::display::getDefault(); - } - - if (hal_display->supportsBacklightDuty()) { - LOG_I(TAG, "Backlight %d", settings.backlightDuty); - hal_display->setBacklightDuty(settings.backlightDuty); - } else { - LOG_I(TAG, "No backlight"); - } - } - - static void setupKernelDisplay() { + static void setupDisplay() { auto* display = device_find_first_by_type(&DISPLAY_TYPE); // Boards not yet migrated to the kernel display driver register a placeholder device (so // the devicetree node resolves) with a NULL api - nothing for this function to act on. @@ -161,8 +132,8 @@ class BootApp : public App { // TODO: Support for multiple displays LOG_I(TAG, "Setup display"); - setupHalDisplay(); - setupKernelDisplay(); + setupDisplay(); + LOG_I(TAG, "Prepare file systems"); prepareFileSystems(); #ifdef CONFIG_TT_USER_DATA_LOCATION_SD diff --git a/Tactility/Source/app/chat/ChatApp.cpp b/Tactility/Source/app/chat/ChatApp.cpp index fe95aba7..64d2b2d1 100644 --- a/Tactility/Source/app/chat/ChatApp.cpp +++ b/Tactility/Source/app/chat/ChatApp.cpp @@ -84,7 +84,8 @@ void ChatApp::onReceive(const esp_now_recv_info_t* receiveInfo, const uint8_t* d state.addMessage(msg); { - auto lock = lvgl::getSyncLock()->asScopedLock(); + auto lockable = lvgl::getSyncLock(); + auto lock = lockable->asScopedLock(); lock.lock(); view.displayMessage(msg); } @@ -115,7 +116,8 @@ void ChatApp::sendMessage(const std::string& text) { state.addMessage(msg); { - auto lock = lvgl::getSyncLock()->asScopedLock(); + auto lockable = lvgl::getSyncLock(); + auto lock = lockable->asScopedLock(); lock.lock(); view.displayMessage(msg); } @@ -172,7 +174,8 @@ void ChatApp::switchChannel(const std::string& chatChannel) { saveSettings(settings); { - auto lock = lvgl::getSyncLock()->asScopedLock(); + auto lockable = lvgl::getSyncLock(); + auto lock = lockable->asScopedLock(); lock.lock(); view.refreshMessageList(); } diff --git a/Tactility/Source/app/crashdiagnostics/CrashDiagnostics.cpp b/Tactility/Source/app/crashdiagnostics/CrashDiagnostics.cpp index bfb9e482..3f7fdc4d 100644 --- a/Tactility/Source/app/crashdiagnostics/CrashDiagnostics.cpp +++ b/Tactility/Source/app/crashdiagnostics/CrashDiagnostics.cpp @@ -3,12 +3,12 @@ #include #include #include -#include #include #include #include #include +#include #include namespace tt::app::crashdiagnostics { @@ -36,7 +36,7 @@ public: lv_obj_align(top_label, LV_ALIGN_TOP_MID, 0, 2); auto* bottom_label = lv_label_create(parent); - if (hal::hasDevice(hal::Device::Type::Touch)) { + if (device_has_active_by_type(&POINTER_TYPE)) { lv_label_set_text(bottom_label, "Tap screen to continue"); } else { lv_label_set_text(bottom_label, "Reboot device to continue"); diff --git a/Tactility/Source/app/development/Development.cpp b/Tactility/Source/app/development/Development.cpp index b96241bb..a5b06595 100644 --- a/Tactility/Source/app/development/Development.cpp +++ b/Tactility/Source/app/development/Development.cpp @@ -1,9 +1,8 @@ #ifdef ESP_PLATFORM -#include #include +#include #include -#include #include #include #include @@ -14,6 +13,7 @@ #include #include +#include #include #include @@ -31,9 +31,10 @@ class DevelopmentApp final : public App { std::shared_ptr service; Timer timer = Timer(Timer::Type::Periodic, pdMS_TO_TICKS(1000), [this] { - auto lock = lvgl::getSyncLock()->asScopedLock(); + auto lockable = lvgl::getSyncLock(); + auto lock = lockable->asScopedLock(); // TODO: There's a crash when this is called when the app is being destroyed - if (lock.lock(lvgl::defaultLockTime) && lvgl::isStarted()) { + if (lock.lock(lvgl::defaultLockTime) && module_is_started(&lvgl_module)) { updateViewState(); } }); @@ -157,7 +158,8 @@ public: } void onHide(AppContext& appContext) override { - auto lock = lvgl::getSyncLock()->asScopedLock(); + auto lockable = lvgl::getSyncLock(); + auto lock = lockable->asScopedLock(); // Ensure that the update isn't already happening lock.lock(); timer.stop(); diff --git a/Tactility/Source/app/display/Display.cpp b/Tactility/Source/app/display/Display.cpp deleted file mode 100644 index 5697a850..00000000 --- a/Tactility/Source/app/display/Display.cpp +++ /dev/null @@ -1,310 +0,0 @@ -#include - -#include - -#ifdef ESP_PLATFORM -#include -#endif - -#include -#include -#include -#include - -#include -#include -#include - -namespace tt::app::display { - -constexpr auto* TAG = "Display"; - -static std::shared_ptr getHalDisplay() { - return hal::findFirstDevice(hal::Device::Type::Display); -} - -class HalDisplayApp final : public App { - - settings::display::DisplaySettings displaySettings; - bool displaySettingsUpdated = false; - lv_obj_t* timeoutSwitch = nullptr; - lv_obj_t* timeoutDropdown = nullptr; - lv_obj_t* screensaverDropdown = nullptr; - - static void onBacklightSliderEvent(lv_event_t* event) { - auto* slider = static_cast(lv_event_get_target(event)); - auto* app = static_cast(lv_event_get_user_data(event)); - auto hal_display = getHalDisplay(); - assert(hal_display != nullptr); - - if (hal_display->supportsBacklightDuty()) { - int32_t slider_value = lv_slider_get_value(slider); - app->displaySettings.backlightDuty = static_cast(slider_value); - app->displaySettingsUpdated = true; - hal_display->setBacklightDuty(app->displaySettings.backlightDuty); - } - } - - static void onGammaSliderEvent(lv_event_t* event) { - auto* slider = static_cast(lv_event_get_target(event)); - auto hal_display = hal::findFirstDevice(hal::Device::Type::Display); - auto* app = static_cast(lv_event_get_user_data(event)); - assert(hal_display != nullptr); - - if (hal_display->getGammaCurveCount() > 0) { - int32_t slider_value = lv_slider_get_value(slider); - app->displaySettings.gammaCurve = static_cast(slider_value); - app->displaySettingsUpdated = true; - hal_display->setGammaCurve(app->displaySettings.gammaCurve); - } - } - - static void onOrientationSet(lv_event_t* event) { - auto* app = static_cast(lv_event_get_user_data(event)); - auto* dropdown = static_cast(lv_event_get_target(event)); - uint32_t selected_index = lv_dropdown_get_selected(dropdown); - LOG_I(TAG, "Selected %u", (unsigned)selected_index); - auto selected_orientation = static_cast(selected_index); - if (selected_orientation != app->displaySettings.orientation) { - app->displaySettings.orientation = selected_orientation; - app->displaySettingsUpdated = true; - lv_display_set_rotation(lv_display_get_default(), settings::display::toLvglDisplayRotation(selected_orientation)); - } - } - - static void onTimeoutSwitch(lv_event_t* event) { - auto* app = static_cast(lv_event_get_user_data(event)); - auto* sw = static_cast(lv_event_get_target(event)); - bool enabled = lv_obj_has_state(sw, LV_STATE_CHECKED); - app->displaySettings.backlightTimeoutEnabled = enabled; - app->displaySettingsUpdated = true; - if (app->timeoutDropdown) { - if (enabled) { - lv_obj_clear_state(app->timeoutDropdown, LV_STATE_DISABLED); - if (app->screensaverDropdown) { - lv_obj_clear_state(app->screensaverDropdown, LV_STATE_DISABLED); - } - } else { - lv_obj_add_state(app->timeoutDropdown, LV_STATE_DISABLED); - if (app->screensaverDropdown) { - lv_obj_add_state(app->screensaverDropdown, LV_STATE_DISABLED); - } - } - } - } - - static void onTimeoutChanged(lv_event_t* event) { - auto* app = static_cast(lv_event_get_user_data(event)); - auto* dropdown = static_cast(lv_event_get_target(event)); - uint32_t idx = lv_dropdown_get_selected(dropdown); - // Map dropdown index to ms: 0=15s,1=30s,2=1m,3=2m,4=5m,5=Never - static const uint32_t values_ms[] = {15000, 30000, 60000, 120000, 300000, 0}; - if (idx < (sizeof(values_ms)/sizeof(values_ms[0]))) { - app->displaySettings.backlightTimeoutMs = values_ms[idx]; - app->displaySettingsUpdated = true; - } - } - - static void onScreensaverChanged(lv_event_t* event) { - auto* app = static_cast(lv_event_get_user_data(event)); - auto* dropdown = static_cast(lv_event_get_target(event)); - uint32_t idx = lv_dropdown_get_selected(dropdown); - // Validate index bounds before casting to enum - if (idx >= static_cast(settings::display::ScreensaverType::Count)) { - return; - } - auto selected_type = static_cast(idx); - if (selected_type != app->displaySettings.screensaverType) { - app->displaySettings.screensaverType = selected_type; - app->displaySettingsUpdated = true; - } - } - -public: - - void onShow(AppContext& app, lv_obj_t* parent) override { - displaySettings = settings::display::loadOrGetDefault(); - auto ui_density = lvgl_get_ui_density(); - - lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); - lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT); - - auto hal_display = getHalDisplay(); - assert(hal_display != nullptr); - - lvgl::toolbar_create(parent, app); - - auto* main_wrapper = lv_obj_create(parent); - lv_obj_set_flex_flow(main_wrapper, LV_FLEX_FLOW_COLUMN); - lv_obj_set_width(main_wrapper, LV_PCT(100)); - lv_obj_set_flex_grow(main_wrapper, 1); - - // Backlight slider - - if (hal_display->supportsBacklightDuty()) { - auto* brightness_wrapper = lv_obj_create(main_wrapper); - lv_obj_set_size(brightness_wrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_hor(brightness_wrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(brightness_wrapper, 0, LV_STATE_DEFAULT); - if (ui_density != LVGL_UI_DENSITY_COMPACT) { - lv_obj_set_style_pad_ver(brightness_wrapper, 4, LV_STATE_DEFAULT); - } - - auto* brightness_label = lv_label_create(brightness_wrapper); - lv_label_set_text(brightness_label, "Brightness"); - lv_obj_align(brightness_label, LV_ALIGN_LEFT_MID, 0, 0); - - auto* brightness_slider = lv_slider_create(brightness_wrapper); - lv_obj_set_width(brightness_slider, LV_PCT(50)); - lv_obj_align(brightness_slider, LV_ALIGN_RIGHT_MID, 0, 0); - lv_slider_set_range(brightness_slider, 0, 255); - lv_obj_add_event_cb(brightness_slider, onBacklightSliderEvent, LV_EVENT_VALUE_CHANGED, this); - - lv_slider_set_value(brightness_slider, displaySettings.backlightDuty, LV_ANIM_OFF); - } - - // Gamma slider - - if (hal_display->getGammaCurveCount() > 0) { - auto* gamma_wrapper = lv_obj_create(main_wrapper); - lv_obj_set_size(gamma_wrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_hor(gamma_wrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(gamma_wrapper, 0, LV_STATE_DEFAULT); - if (ui_density != LVGL_UI_DENSITY_COMPACT) { - lv_obj_set_style_pad_ver(gamma_wrapper, 4, LV_STATE_DEFAULT); - } - - auto* gamma_label = lv_label_create(gamma_wrapper); - lv_label_set_text(gamma_label, "Gamma"); - lv_obj_align(gamma_label, LV_ALIGN_LEFT_MID, 0, 0); - lv_obj_set_y(gamma_label, 0); - - auto* gamma_slider = lv_slider_create(gamma_wrapper); - lv_obj_set_width(gamma_slider, LV_PCT(50)); - lv_obj_align(gamma_slider, LV_ALIGN_RIGHT_MID, 0, 0); - lv_slider_set_range(gamma_slider, 0, hal_display->getGammaCurveCount()); - lv_obj_add_event_cb(gamma_slider, onGammaSliderEvent, LV_EVENT_VALUE_CHANGED, this); - - uint8_t curve_index = displaySettings.gammaCurve; - lv_slider_set_value(gamma_slider, curve_index, LV_ANIM_OFF); - } - - // Orientation - - auto* orientation_wrapper = lv_obj_create(main_wrapper); - lv_obj_set_size(orientation_wrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(orientation_wrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(orientation_wrapper, 0, LV_STATE_DEFAULT); - - auto* orientation_label = lv_label_create(orientation_wrapper); - lv_label_set_text(orientation_label, "Orientation"); - lv_obj_align(orientation_label, LV_ALIGN_LEFT_MID, 0, 0); - - auto* orientation_dropdown = lv_dropdown_create(orientation_wrapper); - // Note: order correlates with settings::display::Orientation item order - lv_dropdown_set_options(orientation_dropdown, "Landscape\nPortrait Right\nLandscape Flipped\nPortrait Left"); - lv_obj_align(orientation_dropdown, LV_ALIGN_RIGHT_MID, 0, 0); - lv_obj_add_event_cb(orientation_dropdown, onOrientationSet, LV_EVENT_VALUE_CHANGED, this); - // Set the dropdown to match current orientation enum - lv_dropdown_set_selected(orientation_dropdown, static_cast(displaySettings.orientation)); - - // Screen timeout - - if (hal_display->supportsBacklightDuty()) { - auto* timeout_wrapper = lv_obj_create(main_wrapper); - lv_obj_set_size(timeout_wrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(timeout_wrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(timeout_wrapper, 0, LV_STATE_DEFAULT); - - auto* timeout_label = lv_label_create(timeout_wrapper); - lv_label_set_text(timeout_label, "Auto screen off"); - lv_obj_align(timeout_label, LV_ALIGN_LEFT_MID, 0, 0); - - timeoutSwitch = lv_switch_create(timeout_wrapper); - if (displaySettings.backlightTimeoutEnabled) { - lv_obj_add_state(timeoutSwitch, LV_STATE_CHECKED); - } - lv_obj_align(timeoutSwitch, LV_ALIGN_RIGHT_MID, 0, 0); - lv_obj_add_event_cb(timeoutSwitch, onTimeoutSwitch, LV_EVENT_VALUE_CHANGED, this); - - auto* timeout_select_wrapper = lv_obj_create(main_wrapper); - lv_obj_set_size(timeout_select_wrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(timeout_select_wrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(timeout_select_wrapper, 0, LV_STATE_DEFAULT); - - auto* timeout_value_label = lv_label_create(timeout_select_wrapper); - lv_label_set_text(timeout_value_label, "Timeout"); - lv_obj_align(timeout_value_label, LV_ALIGN_LEFT_MID, 0, 0); - - timeoutDropdown = lv_dropdown_create(timeout_select_wrapper); - lv_dropdown_set_options(timeoutDropdown, "15 seconds\n30 seconds\n1 minute\n2 minutes\n5 minutes\nNever"); - lv_obj_align(timeoutDropdown, LV_ALIGN_RIGHT_MID, 0, 0); - lv_obj_add_event_cb(timeoutDropdown, onTimeoutChanged, LV_EVENT_VALUE_CHANGED, this); - // Initialize dropdown selection from settings - uint32_t ms = displaySettings.backlightTimeoutMs; - uint32_t idx = 2; // default 1 minute - if (ms == 15000) idx = 0; - else if (ms == 30000) - idx = 1; - else if (ms == 60000) - idx = 2; - else if (ms == 120000) - idx = 3; - else if (ms == 300000) - idx = 4; - else if (ms == 0) - idx = 5; - lv_dropdown_set_selected(timeoutDropdown, idx); - if (!displaySettings.backlightTimeoutEnabled) { - lv_obj_add_state(timeoutDropdown, LV_STATE_DISABLED); - } - - // Screensaver type - auto* screensaver_wrapper = lv_obj_create(main_wrapper); - lv_obj_set_size(screensaver_wrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(screensaver_wrapper, 0, LV_STATE_DEFAULT); - lv_obj_set_style_border_width(screensaver_wrapper, 0, LV_STATE_DEFAULT); - - auto* screensaver_label = lv_label_create(screensaver_wrapper); - lv_label_set_text(screensaver_label, "Screensaver"); - lv_obj_align(screensaver_label, LV_ALIGN_LEFT_MID, 0, 0); - - screensaverDropdown = lv_dropdown_create(screensaver_wrapper); - // Note: order correlates with settings::display::ScreensaverType enum order - lv_dropdown_set_options(screensaverDropdown, "None\nBouncing Balls\nMystify\nMatrix Rain\nStackChan"); - lv_obj_align(screensaverDropdown, LV_ALIGN_RIGHT_MID, 0, 0); - lv_obj_add_event_cb(screensaverDropdown, onScreensaverChanged, LV_EVENT_VALUE_CHANGED, this); - lv_dropdown_set_selected(screensaverDropdown, static_cast(displaySettings.screensaverType)); - if (!displaySettings.backlightTimeoutEnabled) { - lv_obj_add_state(screensaverDropdown, LV_STATE_DISABLED); - } - } - } - - void onHide(AppContext& app) override { - if (displaySettingsUpdated) { - // Dispatch it, so file IO doesn't block the UI - const settings::display::DisplaySettings settings_to_save = displaySettings; - getMainDispatcher().dispatch([settings_to_save] { - settings::display::save(settings_to_save); -#ifdef ESP_PLATFORM - // Notify DisplayIdle service to reload settings - auto displayIdle = service::displayidle::findService(); - if (displayIdle) { - displayIdle->reloadSettings(); - } -#endif - }); - } - } -}; - -extern const AppManifest manifest = { - .appId = "Display", - .appName = "Display", - .appIcon = LVGL_ICON_SHARED_DISPLAY_SETTINGS, - .appCategory = Category::Settings, - .createApp = create -}; - -} // namespace diff --git a/Tactility/Source/app/files/View.cpp b/Tactility/Source/app/files/View.cpp index fce5d182..5c5c797a 100644 --- a/Tactility/Source/app/files/View.cpp +++ b/Tactility/Source/app/files/View.cpp @@ -450,7 +450,8 @@ void View::onEjectPressed() { void View::update(size_t start_index) { const bool is_root = (state->getCurrentPath() == "/"); - auto scoped_lockable = lvgl::getSyncLock()->asScopedLock(); + auto sync_lockable = lvgl::getSyncLock(); + auto scoped_lockable = sync_lockable->asScopedLock(); if (!scoped_lockable.lock(lvgl::defaultLockTime)) { LOG_E(TAG, "Mutex acquisition timeout (%s)", "lvgl"); return; @@ -550,14 +551,16 @@ void View::init(const AppContext& appContext, lv_obj_t* parent) { } void View::onDirEntryListScrollBegin() { - auto scoped_lockable = lvgl::getSyncLock()->asScopedLock(); + auto sync_lockable = lvgl::getSyncLock(); + auto scoped_lockable = sync_lockable->asScopedLock(); if (scoped_lockable.lock(lvgl::defaultLockTime)) { lv_obj_add_flag(action_list, LV_OBJ_FLAG_HIDDEN); } } void View::onNavigate() { - auto scoped_lockable = lvgl::getSyncLock()->asScopedLock(); + auto sync_lockable = lvgl::getSyncLock(); + auto scoped_lockable = sync_lockable->asScopedLock(); if (scoped_lockable.lock(lvgl::defaultLockTime)) { lv_obj_add_flag(action_list, LV_OBJ_FLAG_HIDDEN); } diff --git a/Tactility/Source/app/fileselection/View.cpp b/Tactility/Source/app/fileselection/View.cpp index d685dba6..96a9216c 100644 --- a/Tactility/Source/app/fileselection/View.cpp +++ b/Tactility/Source/app/fileselection/View.cpp @@ -155,7 +155,8 @@ void View::onNavigateUpPressed() { } void View::update() { - auto scoped_lockable = lvgl::getSyncLock()->asScopedLock(); + auto sync_lockable = lvgl::getSyncLock(); + auto scoped_lockable = sync_lockable->asScopedLock(); if (scoped_lockable.lock(lvgl::defaultLockTime)) { lv_obj_clean(dir_entry_list); diff --git a/Tactility/Source/app/gpssettings/GpsSettings.cpp b/Tactility/Source/app/gpssettings/GpsSettings.cpp index 7766b1b9..31472fd9 100644 --- a/Tactility/Source/app/gpssettings/GpsSettings.cpp +++ b/Tactility/Source/app/gpssettings/GpsSettings.cpp @@ -1,20 +1,28 @@ + + +#include "tactility/lvgl_module.h" + + #include + #include #include #include #include #include -#include -#include -#include -#include +#include #include +#include #include -#include #include +#include +#include + +#include +#include namespace tt::app::addgps { extern AppManifest manifest; @@ -26,40 +34,21 @@ extern const AppManifest manifest; class GpsSettingsApp final : public App { - static constexpr auto* TAG = "GpsSettings"; + struct DeviceRow { + Device* device; + lv_obj_t* button; + lv_obj_t* buttonLabel; + bool hasConfiguration = false; + size_t configurationIndex = 0; + }; std::unique_ptr timer; - std::shared_ptr appReference = std::make_shared(this); - lv_obj_t* statusWrapper = nullptr; - lv_obj_t* statusLabelWidget = nullptr; - lv_obj_t* statusLatitudeValue = nullptr; - lv_obj_t* statusLongitudeValue = nullptr; - lv_obj_t* statusAltitudeValue = nullptr; - lv_obj_t* statusSpeedValue = nullptr; - lv_obj_t* statusHeadingValue = nullptr; - lv_obj_t* statusSatellitesValue = nullptr; - lv_obj_t* switchWidget = nullptr; - lv_obj_t* spinnerWidget = nullptr; - lv_obj_t* infoContainerWidget = nullptr; - lv_obj_t* gpsConfigWrapper = nullptr; - lv_obj_t* addGpsWrapper = nullptr; - bool hasSetInfo = false; - PubSub::SubscriptionHandle serviceStateSubscription = nullptr; - std::shared_ptr service; - - void onServiceStateChanged() { - auto lock = lvgl::getSyncLock()->asScopedLock(); - if (lock.lock(100 / portTICK_PERIOD_MS)) { - if (!updateTimerState()) { - updateViews(); - } - } - } - - static void onGpsToggledCallback(lv_event_t* event) { - auto* app = (GpsSettingsApp*)lv_event_get_user_data(event); - app->onGpsToggled(event); - } + lv_obj_t* deviceListWrapper = nullptr; + std::vector deviceRows; + std::atomic isShown = false; + bool hasPendingDelete = false; + Device* pendingDeleteDevice = nullptr; + size_t pendingDeleteIndex = 0; static void onAddGpsCallback(lv_event_t* event) { auto* app = (GpsSettingsApp*)lv_event_get_user_data(event); @@ -70,385 +59,243 @@ class GpsSettingsApp final : public App { app::start(addgps::manifest.appId); } - void startReceivingUpdates() { - timer->start(); - updateViews(); - } - - void stopReceivingUpdates() { - timer->stop(); - updateViews(); - } - - void createInfoView(hal::gps::GpsModel model) { - auto* label = lv_label_create(infoContainerWidget); - if (model == hal::gps::GpsModel::Unknown) { - lv_label_set_text(label, "Model: auto-detect"); - } else { - lv_label_set_text_fmt(label, "Model: %s", toString(model)); - } - } - - static void onDeleteConfiguration(lv_event_t* event) { - auto* app = (GpsSettingsApp*)lv_event_get_user_data(event); - + static void onDeviceButtonCallback(lv_event_t* event) { auto* button = lv_event_get_target_obj(event); - auto index_as_voidptr = lv_obj_get_user_data(button); // config index - int index; - // TODO: Find a better way to cast void* to int, or find a different way to pass the index - memcpy(&index, &index_as_voidptr, sizeof(int)); + auto* device = static_cast(lv_obj_get_user_data(button)); - std::vector configurations; - auto gps_service = service::gps::findGpsService(); - if (gps_service && gps_service->getGpsConfigurations(configurations)) { - LOG_I(TAG, "Found service and configs %d %d", index, (int)configurations.size()); - if (index < configurations.size()) { - if (gps_service->removeGpsConfiguration(configurations[index])) { - app->updateViews(); - } else { - alertdialog::start("Error", "Failed to remove configuration"); - } - } - } - } - - void createGpsView(const hal::gps::GpsConfiguration& configuration, int index) { - auto* wrapper = lv_obj_create(gpsConfigWrapper); - lv_obj_set_size(wrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_ROW); - lv_obj_set_style_margin_hor(wrapper, 0, 0); - lv_obj_set_style_margin_bottom(wrapper, 8, 0); - - // Left wrapper - - auto* left_wrapper = lv_obj_create(wrapper); - lv_obj_set_style_border_width(left_wrapper, 0, 0); - lv_obj_set_style_pad_all(left_wrapper, 0, 0); - lv_obj_set_size(left_wrapper, LV_SIZE_CONTENT, LV_SIZE_CONTENT); - lv_obj_set_flex_grow(left_wrapper, 1); - lv_obj_set_flex_flow(left_wrapper, LV_FLEX_FLOW_COLUMN); - - auto* uart_label = lv_label_create(left_wrapper); - lv_label_set_text_fmt(uart_label, "UART: %s", configuration.uartName); - - auto* baud_label = lv_label_create(left_wrapper); - lv_label_set_text_fmt(baud_label, "Baud: %lu", configuration.baudRate); - - auto* model_label = lv_label_create(left_wrapper); - if (configuration.model == hal::gps::GpsModel::Unknown) { - lv_label_set_text(model_label, "Model: auto-detect"); - } else { - lv_label_set_text_fmt(model_label, "Model: %s", toString(configuration.model)); - } - - // Right wrapper - auto* right_wrapper = lv_obj_create(wrapper); - lv_obj_set_style_border_width(right_wrapper, 0, 0); - lv_obj_set_style_pad_all(right_wrapper, 0, 0); - lv_obj_set_size(right_wrapper, LV_SIZE_CONTENT, LV_SIZE_CONTENT); - lv_obj_set_flex_flow(right_wrapper, LV_FLEX_FLOW_COLUMN); - - auto* delete_button = lv_button_create(right_wrapper); - lv_obj_add_event_cb(delete_button, onDeleteConfiguration, LV_EVENT_SHORT_CLICKED, this); - lv_obj_set_user_data(delete_button, reinterpret_cast(index)); - auto* delete_label = lv_label_create(delete_button); - lv_label_set_text_fmt(delete_label, LV_SYMBOL_TRASH); - } - - void updateViews() { - auto lock = lvgl::getSyncLock()->asScopedLock(); - if (lock.lock(100 / portTICK_PERIOD_MS)) { - auto state = service->getState(); - - // Update toolbar - switch (state) { - case service::gps::State::OnPending: - LOG_D(TAG, "OnPending"); - lv_obj_remove_flag(spinnerWidget, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_state(switchWidget, LV_STATE_CHECKED); - lv_obj_add_state(switchWidget, LV_STATE_DISABLED); - lv_obj_remove_flag(statusWrapper, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_flag(gpsConfigWrapper, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_flag(addGpsWrapper, LV_OBJ_FLAG_HIDDEN); - break; - case service::gps::State::On: - LOG_D(TAG, "On"); - lv_obj_add_flag(spinnerWidget, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_state(switchWidget, LV_STATE_CHECKED); - lv_obj_remove_state(switchWidget, LV_STATE_DISABLED); - lv_obj_remove_flag(statusWrapper, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_flag(gpsConfigWrapper, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_flag(addGpsWrapper, LV_OBJ_FLAG_HIDDEN); - break; - case service::gps::State::OffPending: - LOG_D(TAG, "OffPending"); - lv_obj_remove_flag(spinnerWidget, LV_OBJ_FLAG_HIDDEN); - lv_obj_remove_state(switchWidget, LV_STATE_CHECKED); - lv_obj_add_state(switchWidget, LV_STATE_DISABLED); - lv_obj_add_flag(statusWrapper, LV_OBJ_FLAG_HIDDEN); - lv_obj_remove_flag(gpsConfigWrapper, LV_OBJ_FLAG_HIDDEN); - lv_obj_remove_flag(addGpsWrapper, LV_OBJ_FLAG_HIDDEN); - break; - case service::gps::State::Off: - LOG_D(TAG, "Off"); - lv_obj_add_flag(spinnerWidget, LV_OBJ_FLAG_HIDDEN); - lv_obj_remove_state(switchWidget, LV_STATE_CHECKED); - lv_obj_remove_state(switchWidget, LV_STATE_DISABLED); - lv_obj_add_flag(statusWrapper, LV_OBJ_FLAG_HIDDEN); - lv_obj_remove_flag(gpsConfigWrapper, LV_OBJ_FLAG_HIDDEN); - lv_obj_remove_flag(addGpsWrapper, LV_OBJ_FLAG_HIDDEN); - break; - } - - // Update status label and device info - if (state == service::gps::State::On) { - if (!hasSetInfo) { - auto devices = hal::findDevices(hal::Device::Type::Gps); - for (auto& device : devices) { - createInfoView(device->getModel()); - hasSetInfo = true; - } - } - - minmea_sentence_rmc rmc; - char buffer[64]; - if (service->getCoordinates(rmc)) { - lv_label_set_text(statusLabelWidget, "Lock acquired"); - lv_obj_set_style_text_color(statusLabelWidget, lv_color_hex(0x00ff00), 0); - - minmea_float latitude = { rmc.latitude.value, rmc.latitude.scale }; - minmea_float longitude = { rmc.longitude.value, rmc.longitude.scale }; - - double latCoord = minmea_tocoord(&latitude); - double lonCoord = minmea_tocoord(&longitude); - if (isnan(latCoord) || isnan(lonCoord)) { - lv_label_set_text(statusLatitudeValue, "--"); - lv_label_set_text(statusLongitudeValue, "--"); - } else { - const char* latDir = (latCoord >= 0) ? "N" : "S"; - const char* lonDir = (lonCoord >= 0) ? "E" : "W"; - - snprintf(buffer, sizeof(buffer), "%.6f %s", std::abs(latCoord), latDir); - lv_label_set_text(statusLatitudeValue, buffer); - - snprintf(buffer, sizeof(buffer), "%.6f %s", std::abs(lonCoord), lonDir); - lv_label_set_text(statusLongitudeValue, buffer); - } - - float speedKnots = minmea_tofloat(&rmc.speed); - if (!isnan(speedKnots)) { - float speedKmh = speedKnots * 1.852f; - snprintf(buffer, sizeof(buffer), "%.1f km/h", speedKmh); - lv_label_set_text(statusSpeedValue, buffer); - } else { - lv_label_set_text(statusSpeedValue, "--"); - } - - float heading = minmea_tofloat(&rmc.course); - if (!isnan(heading)) { - // Normalize heading to [0, 360) range - heading = fmodf(heading, 360.0f); - if (heading < 0) heading += 360.0f; - const char* dirs[] = {"N", "NE", "E", "SE", "S", "SW", "W", "NW"}; - // Calculate cardinal direction index (0-7) - int idx = (int)((heading + 22.5f) / 45.0f) % 8; - snprintf(buffer, sizeof(buffer), "%.0f° %s", heading, dirs[idx]); - lv_label_set_text(statusHeadingValue, buffer); - } else { - lv_label_set_text(statusHeadingValue, "--"); - } - - } else { - lv_label_set_text(statusLabelWidget, "Acquiring lock..."); - lv_obj_set_style_text_color(statusLabelWidget, lv_color_hex(0xffaa00), 0); - lv_label_set_text(statusLatitudeValue, "--"); - lv_label_set_text(statusLongitudeValue, "--"); - lv_label_set_text(statusSpeedValue, "--"); - lv_label_set_text(statusHeadingValue, "--"); - } - - minmea_sentence_gga gga; - if (service->getGga(gga)) { - float altitude = minmea_tofloat(&gga.altitude); - if (!isnan(altitude)) { - snprintf(buffer, sizeof(buffer), "%.1f m", altitude); - lv_label_set_text(statusAltitudeValue, buffer); - } else { - lv_label_set_text(statusAltitudeValue, "--"); - } - - snprintf(buffer, sizeof(buffer), "%d", gga.satellites_tracked); - lv_label_set_text(statusSatellitesValue, buffer); - } else { - lv_label_set_text(statusAltitudeValue, "--"); - lv_label_set_text(statusSatellitesValue, "--"); - } - - lv_obj_remove_flag(statusLabelWidget, LV_OBJ_FLAG_HIDDEN); + bool running = device_is_ready(device); + // device_start()/device_stop() are potentially blocking calls, so use a dispatcher to not block the UI + getMainDispatcher().dispatch([device, running] { + if (running) { + device_stop(device); } else { - if (hasSetInfo) { - lv_obj_clean(infoContainerWidget); - hasSetInfo = false; - } - - lv_obj_add_flag(statusLabelWidget, LV_OBJ_FLAG_HIDDEN); + device_start(device); } - - if (!lv_obj_has_flag(gpsConfigWrapper, LV_OBJ_FLAG_HIDDEN)) { - lv_obj_clean(gpsConfigWrapper); - std::vector configurations; - auto gps_service = tt::service::gps::findGpsService(); - if (gps_service && gps_service->getGpsConfigurations(configurations)) { - int index = 0; - for (auto& configuration : configurations) { - createGpsView(configuration, index++); - } - } - } else { - lv_obj_clean(gpsConfigWrapper); - } - } + }); } - /** @return true if the views were updated */ - bool updateTimerState() { - bool is_on = service->getState() == service::gps::State::On; - if (is_on && !timer->isRunning()) { - startReceivingUpdates(); - return true; - } else if (!is_on && timer->isRunning()) { - stopReceivingUpdates(); - return true; - } else { + // Finds the persisted configuration backing `device` (matched by its parent UART's name) + // and returns its index into gps_settings_for_each_configuration()'s ordering - the handle + // gps_settings_remove_configuration_at() needs to delete exactly this entry, even if another + // entry happens to have identical field values. + // Devicetree-declared GPS_TYPE devices have no such configuration and never match. + static bool findConfigurationIndexForDevice(Device* device, size_t& outIndex) { + auto* parent = device_get_parent(device); + if (parent == nullptr) { return false; } + + struct Context { + const char* uartName; + size_t* outIndex; + bool found; + } context = { parent->name, &outIndex, false }; + + gps_settings_for_each_configuration(&context, [](const GpsConfiguration* configuration, size_t index, void* untyped_context) { + auto* ctx = static_cast(untyped_context); + if (!ctx->found && strcmp(configuration->uart_name, ctx->uartName) == 0) { + *ctx->outIndex = index; + ctx->found = true; + } + }); + + return context.found; } - void onGpsToggled(lv_event_t* event) { - bool wants_on = lv_obj_has_state(switchWidget, LV_STATE_CHECKED); - auto state = service->getState(); - bool is_on = (state == service::gps::State::On) || (state == service::gps::State::OnPending); + static void onDeleteButtonCallback(lv_event_t* event) { + auto* app = static_cast(lv_event_get_user_data(event)); + auto* button = lv_event_get_target_obj(event); + auto* device = static_cast(lv_obj_get_user_data(button)); + app->onDeleteDevice(device); + } - if (wants_on != is_on) { - // start/stop are potentially blocking calls, so we use a dispatcher to not block the UI - if (wants_on) { - getMainDispatcher().dispatch([this] { - service->startReceiving(); - }); - } else { - getMainDispatcher().dispatch([this] { - service->stopReceiving(); - }); + void onDeleteDevice(Device* device) { + for (auto& row : deviceRows) { + if (row.device == device && row.hasConfiguration) { + pendingDeleteDevice = device; + pendingDeleteIndex = row.configurationIndex; + hasPendingDelete = true; + alertdialog::start("Confirmation", std::string("Do you want to delete ") + device->name + "?", std::vector { "Yes", "No" }); + return; } } } - lv_obj_t* createInfoRow(lv_obj_t* parent, const char* labelText, lv_color_t color) { - lv_obj_t* row = lv_obj_create(parent); - lv_obj_set_size(row, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW); - lv_obj_set_flex_align(row, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START); + void createDeviceRow(Device* device) { + auto* wrapper = lv_obj_create(deviceListWrapper); + lv_obj_set_size(wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(wrapper, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_border_width(wrapper, 0, 0); + lv_obj_set_style_pad_all(wrapper, 0, 0); - lv_obj_set_style_pad_all(row, 0, 0); - lv_obj_set_style_pad_right(row, 10, 0); - lv_obj_set_style_border_width(row, 0, 0); - lv_obj_set_style_bg_opa(row, LV_OPA_TRANSP, 0); + auto* name_label = lv_label_create(wrapper); + char model_name[64]; + if (gps_get_model_name(device, model_name, sizeof(model_name)) == ERROR_NONE) { + lv_label_set_text(name_label, model_name); + } else { + lv_label_set_text(name_label, device->name); + } - lv_obj_t* label = lv_label_create(row); - lv_label_set_text(label, labelText); - lv_obj_set_style_text_color(label, lv_palette_lighten(LV_PALETTE_GREY, 5), 0); + auto* actions_wrapper = lv_obj_create(wrapper); + lv_obj_set_size(actions_wrapper, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_set_flex_flow(actions_wrapper, LV_FLEX_FLOW_ROW); + lv_obj_set_style_border_width(actions_wrapper, 0, 0); + lv_obj_set_style_pad_all(actions_wrapper, 0, 0); + lv_obj_set_style_pad_column(actions_wrapper, 4, 0); - lv_obj_t* value = lv_label_create(row); - lv_label_set_text(value, "--"); - lv_obj_set_style_text_color(value, color, 0); + auto* button = lv_button_create(actions_wrapper); + lv_obj_add_event_cb(button, onDeviceButtonCallback, LV_EVENT_SHORT_CLICKED, this); + lv_obj_set_user_data(button, device); + auto* button_label = lv_label_create(button); + lv_label_set_text(button_label, "Start"); - return value; + DeviceRow row { .device = device, .button = button, .buttonLabel = button_label }; + + // Only devices backed by a persisted configuration (not devicetree-declared ones) can be deleted. + size_t configurationIndex; + if ((device->flags & DEVICE_FLAG_DYNAMIC) && findConfigurationIndexForDevice(device, configurationIndex)) { + auto* delete_button = lv_button_create(actions_wrapper); + lv_obj_add_event_cb(delete_button, onDeleteButtonCallback, LV_EVENT_SHORT_CLICKED, this); + lv_obj_set_user_data(delete_button, device); + auto* delete_label = lv_label_create(delete_button); + lv_label_set_text(delete_label, LVGL_ICON_SHARED_DELETE); + + row.hasConfiguration = true; + row.configurationIndex = configurationIndex; + } + + deviceRows.push_back(row); + } + + // Rebuilds the device list. Only needs to run when the set of devices could've changed + // (on show, and after returning from AddGpsApp) - button state itself is refreshed by the timer. + void rebuildDeviceList() { + lv_obj_clean(deviceListWrapper); + deviceRows.clear(); + + device_for_each_of_type(&GPS_TYPE, this, [](Device* device, void* context) { + static_cast(context)->createDeviceRow(device); + return true; + }); + } + + void updateDeviceStates() { + auto lockable = lvgl::getSyncLock(); + auto lock = lockable->asScopedLock(); + if (lock.lock(100 / portTICK_PERIOD_MS)) { + for (auto& row : deviceRows) { + const char* text = "Start"; + bool enabled = true; + + if (device_is_ready(row.device)) { + switch (gps_get_state(row.device)) { + case GPS_STATE_PENDING_ON: + text = "Starting..."; + enabled = false; + break; + case GPS_STATE_PENDING_OFF: + text = "Stopping..."; + enabled = false; + break; + default: + text = "Stop"; + enabled = true; + break; + } + } + + lv_label_set_text(row.buttonLabel, text); + if (enabled) { + lv_obj_remove_state(row.button, LV_STATE_DISABLED); + } else { + lv_obj_add_state(row.button, LV_STATE_DISABLED); + } + } + } } public: GpsSettingsApp() { + // Runs while the screen is shown - there's no push notification for GPS device state + // changes, so this is the only way this screen finds out about them. timer = std::make_unique(Timer::Type::Periodic, kernel::secondsToTicks(1), [this] { - updateViews(); + updateDeviceStates(); }); - service = service::gps::findGpsService(); } void onShow(AppContext& app, lv_obj_t* parent) override { lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT); + uint8_t margin = (lvgl_get_ui_density() == LVGL_UI_DENSITY_COMPACT) ? 2 : 8; + auto* toolbar = lvgl::toolbar_create(parent, app); + lvgl::toolbar_add_text_button_action(toolbar, LV_SYMBOL_PLUS, onAddGpsCallback, this); + lv_obj_set_style_margin_bottom(toolbar, margin, LV_STATE_DEFAULT); - spinnerWidget = lvgl::toolbar_add_spinner_action(toolbar); - lv_obj_add_flag(spinnerWidget, LV_OBJ_FLAG_HIDDEN); + deviceListWrapper = lv_obj_create(parent); + lv_obj_set_size(deviceListWrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_flex_flow(deviceListWrapper, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_grow(deviceListWrapper, 1); + lv_obj_set_style_border_width(deviceListWrapper, 0, 0); + lv_obj_set_style_pad_hor(deviceListWrapper, margin, 0); + lv_obj_set_style_pad_top(deviceListWrapper, 0, 0); + lv_obj_set_style_pad_bottom(deviceListWrapper, margin, 0); + lv_obj_set_style_pad_row(deviceListWrapper, margin, 0); - switchWidget = lvgl::toolbar_add_switch_action(toolbar); - lv_obj_add_event_cb(switchWidget, onGpsToggledCallback, LV_EVENT_VALUE_CHANGED, this); + rebuildDeviceList(); - auto* main_wrapper = lv_obj_create(parent); - lv_obj_set_flex_flow(main_wrapper, LV_FLEX_FLOW_COLUMN); - lv_obj_set_width(main_wrapper, LV_PCT(100)); - lv_obj_set_flex_grow(main_wrapper, 1); - lv_obj_set_style_border_width(main_wrapper, 0, 0); - lv_obj_set_style_pad_all(main_wrapper, 0, 0); + timer->start(); + updateDeviceStates(); - statusWrapper = lv_obj_create(main_wrapper); - lv_obj_set_width(statusWrapper, LV_PCT(100)); - lv_obj_set_height(statusWrapper, LV_SIZE_CONTENT); - lv_obj_set_flex_flow(statusWrapper, LV_FLEX_FLOW_COLUMN); - lv_obj_set_flex_align(statusWrapper, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); - lv_obj_set_style_pad_all(statusWrapper, 0, 0); - lv_obj_set_style_pad_row(statusWrapper, 8, 0); - lv_obj_set_style_border_width(statusWrapper, 0, 0); - - statusLabelWidget = lv_label_create(statusWrapper); - - infoContainerWidget = lv_obj_create(statusWrapper); - lv_obj_set_size(infoContainerWidget, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_flex_flow(infoContainerWidget, LV_FLEX_FLOW_COLUMN); - lv_obj_set_style_border_width(infoContainerWidget, 0, 0); - lv_obj_set_style_pad_row(infoContainerWidget, 5, 0); - lv_obj_set_style_pad_hor(infoContainerWidget, 10, 0); - hasSetInfo = false; - - statusLatitudeValue = createInfoRow(infoContainerWidget, "Latitude", lv_color_hex(0x00ff00)); - statusLongitudeValue = createInfoRow(infoContainerWidget, "Longitude", lv_color_hex(0x00ff00)); - statusAltitudeValue = createInfoRow(infoContainerWidget, "Altitude", lv_color_hex(0x00ffff)); - statusSpeedValue = createInfoRow(infoContainerWidget, "Speed", lv_color_hex(0xffff00)); - statusHeadingValue = createInfoRow(infoContainerWidget, "Heading", lv_color_hex(0xff88ff)); - statusSatellitesValue = createInfoRow(infoContainerWidget, "Satellites", lv_color_hex(0xffffff)); - - serviceStateSubscription = service->getStatePubsub()->subscribe([this](auto) { - onServiceStateChanged(); - }); - - gpsConfigWrapper = lv_obj_create(main_wrapper); - lv_obj_set_size(gpsConfigWrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_border_width(gpsConfigWrapper, 0, 0); - lv_obj_set_style_margin_all(gpsConfigWrapper, 0, 0); - lv_obj_set_style_pad_bottom(gpsConfigWrapper, 0, 0); - - addGpsWrapper = lv_obj_create(main_wrapper); - lv_obj_set_size(addGpsWrapper, LV_PCT(100), LV_SIZE_CONTENT); - lv_obj_set_style_border_width(addGpsWrapper, 0, 0); - lv_obj_set_style_pad_all(addGpsWrapper, 0, 0); - lv_obj_set_style_margin_top(addGpsWrapper, 0, 0); - lv_obj_set_style_margin_bottom(addGpsWrapper, 8, 0); - - auto* add_gps_button = lv_button_create(addGpsWrapper); - auto* add_gps_label = lv_label_create(add_gps_button); - lv_label_set_text(add_gps_label, "Add GPS"); - lv_obj_add_event_cb(add_gps_button, onAddGpsCallback, LV_EVENT_SHORT_CLICKED, this); - lv_obj_align(add_gps_button, LV_ALIGN_TOP_MID, 0, 0); - - updateTimerState(); - updateViews(); + // Only after deviceListWrapper is fully built: onResult() (Loader thread) checks + // this before touching it, since it can run before or after this onShow() call. + isShown = true; } void onHide(AppContext& app) override { - service->getStatePubsub()->unsubscribe(serviceStateSubscription); - serviceStateSubscription = nullptr; + isShown = false; + timer->stop(); + } + + void onResult(AppContext&, LaunchId, Result result, std::unique_ptr bundle) override { + if (!hasPendingDelete) { + return; + } + hasPendingDelete = false; + + if (result != Result::Ok || bundle == nullptr || alertdialog::getResultIndex(*bundle) != 0) { // 0 = Yes + return; + } + + // This runs on the Loader thread, concurrently with the periodic timer callback + // (updateDeviceStates(), timer daemon thread) and possibly with onShow() (GUI + // thread). Take the same lock updateDeviceStates() uses and hold it across the + // free below, so the timer can never observe pendingDeleteDevice as a dangling + // pointer in deviceRows. + lvgl_lock(); + // Drop the stale row unconditionally (cheap vector op, no LVGL calls) - this is + // what keeps the timer safe regardless of whether onShow() has run yet this cycle. + std::erase_if(deviceRows, [this](const DeviceRow& row) { + return row.device == pendingDeleteDevice; + }); + lvgl_unlock(); + + // gps_settings_remove_configuration_at() frees the underlying Device synchronously - + // do this only after the dangling pointer is already out of deviceRows. + gps_settings_remove_configuration_at(pendingDeleteIndex); + pendingDeleteDevice = nullptr; + + // Only safe to touch deviceListWrapper if onShow() already built it for this show + // cycle - it may not have run yet, in which case it'll rebuild fresh (post-deletion, + // deviceRows already correct) when it does. + lvgl_lock(); + if (isShown) { + rebuildDeviceList(); + } + lvgl_unlock(); } }; diff --git a/Tactility/Source/app/notes/Notes.cpp b/Tactility/Source/app/notes/Notes.cpp index c2790fd9..18b84a3b 100644 --- a/Tactility/Source/app/notes/Notes.cpp +++ b/Tactility/Source/app/notes/Notes.cpp @@ -87,7 +87,8 @@ class NotesApp final : public App { file::getLock(path)->withLock([this, path] { auto data = file::readString(path); if (data != nullptr) { - auto lock = lvgl::getSyncLock()->asScopedLock(); + auto lockable = lvgl::getSyncLock(); + auto lock = lockable->asScopedLock(); lock.lock(); lv_textarea_set_text(uiNoteText, reinterpret_cast(data.get())); lv_label_set_text(uiCurrentFileName, path.c_str()); diff --git a/Tactility/Source/app/poweroff/PowerOff.cpp b/Tactility/Source/app/poweroff/PowerOff.cpp index e9a55988..572f40fd 100644 --- a/Tactility/Source/app/poweroff/PowerOff.cpp +++ b/Tactility/Source/app/poweroff/PowerOff.cpp @@ -4,13 +4,11 @@ #include #include -#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 1ef614cd..b1cf5bfe 100644 --- a/Tactility/Source/app/screenshot/Screenshot.cpp +++ b/Tactility/Source/app/screenshot/Screenshot.cpp @@ -87,7 +87,8 @@ ScreenshotApp::~ScreenshotApp() { } void ScreenshotApp::onTimerTick() { - auto lock = lvgl::getSyncLock()->asScopedLock(); + auto lockable = lvgl::getSyncLock(); + auto lock = lockable->asScopedLock(); if (lock.lock(lvgl::defaultLockTime)) { updateScreenshotMode(); } diff --git a/Tactility/Source/app/systeminfo/SystemInfo.cpp b/Tactility/Source/app/systeminfo/SystemInfo.cpp index 1c7b7f9d..5f1a882a 100644 --- a/Tactility/Source/app/systeminfo/SystemInfo.cpp +++ b/Tactility/Source/app/systeminfo/SystemInfo.cpp @@ -247,7 +247,8 @@ class SystemInfoApp final : public App { Timer memoryTimer = Timer(Timer::Type::Periodic, kernel::millisToTicks(10000), [] { auto app = optApp(); if (app) { - auto lock = lvgl::getSyncLock()->asScopedLock(); + auto lockable = lvgl::getSyncLock(); + auto lock = lockable->asScopedLock(); lock.lock(); app->updateMemory(); } @@ -256,7 +257,8 @@ class SystemInfoApp final : public App { Timer tasksTimer = Timer(Timer::Type::Periodic, kernel::millisToTicks(15000), [] { auto app = optApp(); if (app) { - auto lock = lvgl::getSyncLock()->asScopedLock(); + auto lockable = lvgl::getSyncLock(); + auto lock = lockable->asScopedLock(); lock.lock(); app->updateTasks(); } diff --git a/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp b/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp index d89a3850..5fe79830 100644 --- a/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp +++ b/Tactility/Source/app/timedatesettings/TimeDateSettings.cpp @@ -1,4 +1,4 @@ -#include "tactility/lvgl_module.h" +#include #include diff --git a/Tactility/Source/file/File.cpp b/Tactility/Source/file/File.cpp index 99f99887..1ad42792 100644 --- a/Tactility/Source/file/File.cpp +++ b/Tactility/Source/file/File.cpp @@ -4,7 +4,9 @@ #include #include +#include #include +#include #include namespace tt::hal::sdcard { @@ -15,30 +17,25 @@ namespace tt::file { constexpr auto* TAG = "file"; -class NoLock final : public Lock { - bool lock(TickType_t timeout) const override { return true; } - void unlock() const override { /* NO-OP */ } +class FileMutexLock final : public Lock { + FileMutex mutex; + +public: + explicit FileMutexLock(const std::string& path) { + file_mutex_get(&mutex, path.c_str()); + } + + bool lock(TickType_t timeout) const override { + return file_mutex_try_lock(&mutex, timeout); + } + + void unlock() const override { + file_mutex_unlock(&mutex); + } }; -static std::shared_ptr noLock = std::make_shared(); -static std::function(const std::string&)> findLockFunction = nullptr; - std::shared_ptr getLock(const std::string& path) { - if (findLockFunction == nullptr) { - LOG_W(TAG, "File lock function not set!"); - return noLock; - } - - auto lock = findLockFunction(path); - if (lock == nullptr) { - return noLock; - } - - return lock; -} - -void setFindLockFunction(const FindLockFunction& function) { - findLockFunction = function; + return std::make_shared(path); } std::string getChildPath(const std::string& basePath, const std::string& childPath) { @@ -68,7 +65,8 @@ bool listDirectory( const std::string& path, std::function onEntry ) { - auto lock = getLock(path)->asScopedLock(); + auto lockable = getLock(path); + auto lock = lockable->asScopedLock(); lock.lock(); LOG_I(TAG, "listDir start %s", path.c_str()); @@ -95,7 +93,8 @@ int scandir( ScandirFilter filterMethod, ScandirSort sortMethod ) { - auto lock = getLock(path)->asScopedLock(); + auto lockable = getLock(path); + auto lock = lockable->asScopedLock(); lock.lock(); LOG_I(TAG, "scandir start"); @@ -221,7 +220,8 @@ bool writeString(const std::string& filepath, const std::string& content) { } static bool findOrCreateDirectoryInternal(std::string path, mode_t mode) { - auto lock = getLock(path)->asScopedLock(); + auto lockable = getLock(path); + auto lock = lockable->asScopedLock(); lock.lock(); struct stat dir_stat; @@ -336,32 +336,37 @@ bool deleteRecursively(const std::string& path) { } bool deleteFile(const std::string& path) { - auto lock = getLock(path)->asScopedLock(); + auto lockable = getLock(path); + auto lock = lockable->asScopedLock(); lock.lock(); return remove(path.c_str()) == 0; } bool deleteDirectory(const std::string& path) { - auto lock = getLock(path)->asScopedLock(); + auto lockable = getLock(path); + auto lock = lockable->asScopedLock(); lock.lock(); return rmdir(path.c_str()) == 0; } bool isFile(const std::string& path) { - auto lock = getLock(path)->asScopedLock(); + auto lockable = getLock(path); + auto lock = lockable->asScopedLock(); lock.lock(); return access(path.c_str(), F_OK) == 0; } bool isDirectory(const std::string& path) { - auto lock = getLock(path)->asScopedLock(); + auto lockable = getLock(path); + auto lock = lockable->asScopedLock(); lock.lock(); struct stat stat_result; return stat(path.c_str(), &stat_result) == 0 && S_ISDIR(stat_result.st_mode); } bool readLines(const std::string& filePath, bool stripNewLine, std::function callback) { - auto lock = getLock(filePath)->asScopedLock(); + auto lockable = getLock(filePath); + auto lock = lockable->asScopedLock(); lock.lock(); auto* file = fopen(filePath.c_str(), "r"); diff --git a/Tactility/Source/file/FileLock.cpp b/Tactility/Source/file/FileLock.cpp deleted file mode 100644 index 17715cab..00000000 --- a/Tactility/Source/file/FileLock.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "Tactility/file/FileLock.h" - -#include - -namespace tt::file { - -std::shared_ptr findLock(const std::string& path) { - return hal::sdcard::findSdCardLock(path); -} - -} diff --git a/Tactility/Source/file/FileMutexLvgl.cpp b/Tactility/Source/file/FileMutexLvgl.cpp new file mode 100644 index 00000000..0437a258 --- /dev/null +++ b/Tactility/Source/file/FileMutexLvgl.cpp @@ -0,0 +1,80 @@ +#include +#include +#include +#include +#include +#include + +constexpr auto* TAG = "file_mutex_lvgl"; + +struct Device; +namespace tt { + +static const FileMutex lvgl_mutex = { + .lock = lvgl_lock, + .try_lock = lvgl_try_lock, + .unlock = lvgl_unlock, +}; + +/** + * Finds file systems with a device (e.g. sd card) that is owned by a SPI controller. + * If the SPI controller has a display on the bus, we create an LVGL lock for the file system path. + */ +void initFileMutexForLvgl() { + file_system_for_each(nullptr, [](FileSystem* fs, void* context) { + char mount_path[64]; + if (file_system_get_path(fs, mount_path, sizeof(mount_path)) != ERROR_NONE) { + return true; + } + LOG_D(TAG, "Mount path %s", mount_path); + + // We only care about file system with a Device (owner) + auto* owner = file_system_get_owner(fs); + if (owner == nullptr) { + LOG_D(TAG, "Owner: none"); + return true; + } + + LOG_D(TAG, "Owner: %s", owner->name); + + // Ignore devices without a parent (root) + auto* parent = device_get_parent(owner); + if (parent == nullptr) { + LOG_D(TAG, "Owner: no parent"); + return true; + } + + LOG_D(TAG, "Owner: parent %s", parent->name); + + // If the FileSystem is on a SPI bus and there's more than 1 device, we assume the other one is the display. + auto* type = device_get_type(parent); + if (type != &SPI_CONTROLLER_TYPE || device_get_child_count(parent) <= 1) { + LOG_D(TAG, "Owner parent not SPI controller or not enough children"); + return true; + } + + struct Context { + const char* mountPath; + }; + Context ctx = { .mountPath = mount_path }; + + device_for_each_child(parent, &ctx, [](Device* child, void* context) -> bool { + Context* ctx = static_cast(context); + if (device_get_type(child) == &DISPLAY_TYPE) { + LOG_I(TAG, "Adding file mutex for %s as it shares a bus with a display", ctx->mountPath); + file_mutex_register( + &lvgl_mutex, + ctx->mountPath + ); + return false; + } else { + LOG_D(TAG, "child of parent, %s: not DISPLAY_TYPE", child->name); + } + return true; + }); + + return true; + }); +} + +} diff --git a/Tactility/Source/hal/Hal.cpp b/Tactility/Source/hal/Hal.cpp deleted file mode 100644 index 8ce2ed08..00000000 --- a/Tactility/Source/hal/Hal.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace tt::hal { - -constexpr auto* TAG = "Hal"; - -void registerDevices(const Configuration& configuration) { - LOG_I(TAG, "Registering devices"); - - auto devices = configuration.createDevices(); - for (auto& device : devices) { - registerDevice(device); - - // Register attached devices - if (device->getType() == Device::Type::Display) { - const auto display = std::static_pointer_cast(device); - assert(display != nullptr); - const std::shared_ptr touch = display->getTouchDevice(); - if (touch != nullptr) { - registerDevice(touch); - } - } - } -} - -static void startDisplays() { - LOG_I(TAG, "Starting displays & touch"); - auto displays = hal::findDevices(Device::Type::Display); - for (auto& display : displays) { - LOG_I(TAG, "%s starting", display->getName().c_str()); - if (!display->start()) { - LOG_E(TAG, "%s start failed", display->getName().c_str()); - } else { - LOG_I(TAG, "%s started", display->getName().c_str()); - - if (display->supportsBacklightDuty()) { - LOG_I(TAG, "Setting backlight"); - display->setBacklightDuty(0); - } - - auto touch = display->getTouchDevice(); - if (touch != nullptr) { - LOG_I(TAG, "%s starting", touch->getName().c_str()); - if (!touch->start()) { - LOG_E(TAG, "%s start failed", touch->getName().c_str()); - } else { - LOG_I(TAG, "%s started", touch->getName().c_str()); - } - } - } - } -} - -void init(const Configuration& configuration) { - kernel::publishSystemEvent(kernel::SystemEvent::BootInitHalBegin); - - if (configuration.initBoot != nullptr) { - check(configuration.initBoot(), "Init boot failed"); - } - - registerDevices(configuration); - - sdcard::mountAll(); // Warning: This needs to happen BEFORE displays are initialized on the SPI bus - - startDisplays(); // Warning: SPI displays need to start after SPI SD cards are mounted - - kernel::publishSystemEvent(kernel::SystemEvent::BootInitHalEnd); -} - -const Configuration* getConfiguration() { - return tt::getConfiguration()->hardware; -} - -} // namespace diff --git a/Tactility/Source/hal/display/KernelDisplayDriver.cpp b/Tactility/Source/hal/display/KernelDisplayDriver.cpp deleted file mode 100644 index 5808efbf..00000000 --- a/Tactility/Source/hal/display/KernelDisplayDriver.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#include - -#include - -#include -#include -#include - -namespace tt::hal::display { - -static ColorFormat toColorFormat(DisplayColorFormat format) { - switch (format) { - case DISPLAY_COLOR_FORMAT_MONOCHROME: - return ColorFormat::Monochrome; - case DISPLAY_COLOR_FORMAT_BGR565: - return ColorFormat::BGR565; - case DISPLAY_COLOR_FORMAT_BGR565_SWAPPED: - return ColorFormat::BGR565Swapped; - case DISPLAY_COLOR_FORMAT_RGB565: - return ColorFormat::RGB565; - case DISPLAY_COLOR_FORMAT_RGB565_SWAPPED: - return ColorFormat::RGB565Swapped; - case DISPLAY_COLOR_FORMAT_RGB888: - return ColorFormat::RGB888; - default: - std::unreachable(); - } -} - -KernelDisplayDriver::KernelDisplayDriver(::Device* device) : device(device) { - assert(device_get_type(device) == &DISPLAY_TYPE); -} - -ColorFormat KernelDisplayDriver::getColorFormat() const { - return toColorFormat(display_get_color_format(device)); -} - -uint16_t KernelDisplayDriver::getPixelWidth() const { - return display_get_resolution_x(device); -} - -uint16_t KernelDisplayDriver::getPixelHeight() const { - return display_get_resolution_y(device); -} - -bool KernelDisplayDriver::drawBitmap(int xStart, int yStart, int xEnd, int yEnd, const void* pixelData) { - return display_draw_bitmap(device, xStart, yStart, xEnd, yEnd, pixelData) == ERROR_NONE; -} - -uint8_t KernelDisplayDriver::getFrameBuffers(void* outBuffers[2]) const { - uint8_t count = std::min(display_get_frame_buffer_count(device), 2); - for (uint8_t i = 0; i < count; i++) { - display_get_frame_buffer(device, i, &outBuffers[i]); - } - return count; -} - -} diff --git a/Tactility/Source/hal/gpio/Gpio.cpp b/Tactility/Source/hal/gpio/Gpio.cpp deleted file mode 100644 index 0743c13e..00000000 --- a/Tactility/Source/hal/gpio/Gpio.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include - -#ifdef ESP_PLATFORM -#include -#endif - -namespace tt::hal::gpio { - -#ifdef ESP_PLATFORM - -constexpr gpio_num_t toEspPin(Pin pin) { return static_cast(pin); } - -constexpr gpio_mode_t toEspGpioMode(Mode mode) { - switch (mode) { - case Mode::Input: - return GPIO_MODE_INPUT; - case Mode::Output: - return GPIO_MODE_OUTPUT; - case Mode::OutputOpenDrain: - return GPIO_MODE_OUTPUT_OD; - case Mode::InputOutput: - return GPIO_MODE_INPUT_OUTPUT; - case Mode::InputOutputOpenDrain: - return GPIO_MODE_INPUT_OUTPUT_OD; - case Mode::Disable: - default: - return GPIO_MODE_DISABLE; - } -} - -#endif - -bool getLevel(Pin pin) { -#ifdef ESP_PLATFORM - return gpio_get_level(toEspPin(pin)) == 1; -#else - return false; -#endif -} - -bool setLevel(Pin pin, bool level) { -#ifdef ESP_PLATFORM - return gpio_set_level(toEspPin(pin), level) == ESP_OK; -#else - return true; -#endif -} - -int getPinCount() { -#ifdef ESP_PLATFORM - return GPIO_NUM_MAX; -#else - return 16; -#endif -} - -bool configureWithPinBitmask(uint64_t pinBitMask, Mode mode, bool pullUp, bool pullDown) { -#ifdef ESP_PLATFORM - gpio_config_t sd_gpio_config = { - .pin_bit_mask = pinBitMask, - .mode = toEspGpioMode(mode), - .pull_up_en = pullUp ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE, - .pull_down_en = pullDown ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, - }; - return gpio_config(&sd_gpio_config) == ESP_OK; -#else - return true; -#endif -} - -bool configure(Pin pin, Mode mode, bool pullUp, bool pullDown) { -#ifdef ESP_PLATFORM - return configureWithPinBitmask(BIT64(toEspPin(pin)), mode, pullUp, pullDown); -#else - return true; -#endif -} - -bool setMode(Pin pin, Mode mode) { -#ifdef ESP_PLATFORM - return gpio_set_direction(toEspPin(pin), toEspGpioMode(mode)) == ESP_OK; -#endif - return true; -} - -} diff --git a/Tactility/Source/hal/gps/GpsConfiguration.cpp b/Tactility/Source/hal/gps/GpsConfiguration.cpp deleted file mode 100644 index 1f355dd0..00000000 --- a/Tactility/Source/hal/gps/GpsConfiguration.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "Tactility/hal/gps/GpsConfiguration.h" -#include "Tactility/service/gps/GpsService.h" -#include "Tactility/file/ObjectFile.h" - -#include - -namespace tt::hal::gps { - -const char* toString(GpsModel model) { - using enum GpsModel; - switch (model) { - case AG3335: - return TT_STRINGIFY(AG3335); - case AG3352: - return TT_STRINGIFY(AG3352); - case ATGM336H: - return TT_STRINGIFY(ATGM336H); - case LS20031: - return TT_STRINGIFY(LS20031); - case MTK: - return TT_STRINGIFY(MTK); - case MTK_L76B: - return TT_STRINGIFY(MTK_L76B); - case MTK_PA1616S: - return TT_STRINGIFY(MTK_PA1616S); - case UBLOX6: - return TT_STRINGIFY(UBLOX6); - case UBLOX7: - return TT_STRINGIFY(UBLOX7); - case UBLOX8: - return TT_STRINGIFY(UBLOX8); - case UBLOX9: - return TT_STRINGIFY(UBLOX9); - case UBLOX10: - return TT_STRINGIFY(UBLOX10); - case UC6580: - return TT_STRINGIFY(UC6580); - default: - return TT_STRINGIFY(Unknown); - } -} - -std::vector getModels() { - std::vector result; - for (GpsModel model = GpsModel::Unknown; model <= GpsModel::UC6580; ++(int&)model) { - result.push_back(toString(model)); - } - return result; -} - -} diff --git a/Tactility/Source/hal/gps/GpsDevice.cpp b/Tactility/Source/hal/gps/GpsDevice.cpp deleted file mode 100644 index b6184fa4..00000000 --- a/Tactility/Source/hal/gps/GpsDevice.cpp +++ /dev/null @@ -1,199 +0,0 @@ -#include -#include -#include -#include - -#include -#include - -#include - -namespace tt::hal::gps { - -constexpr uint32_t GPS_UART_BUFFER_SIZE = 256; - -constexpr auto* TAG = "GpsDevice"; - -int32_t GpsDevice::threadMain() { - uint8_t buffer[GPS_UART_BUFFER_SIZE]; - - auto* uart = device_find_by_name(configuration.uartName); - if (uart == nullptr) { - LOG_E(TAG, "Failed to find UART %s", configuration.uartName); - return -1; - } - - struct UartConfig uartConfig = { - .baud_rate = configuration.baudRate, - .data_bits = UART_CONTROLLER_DATA_8_BITS, - .parity = UART_CONTROLLER_PARITY_DISABLE, - .stop_bits = UART_CONTROLLER_STOP_BITS_1 - }; - - error_t error = uart_controller_set_config(uart, &uartConfig); - if (error != ERROR_NONE) { - LOG_E(TAG, "Failed to configure UART %s: %s", configuration.uartName, error_to_string(error)); - return -1; - } - - error = uart_controller_open(uart); - if (error != ERROR_NONE) { - LOG_E(TAG, "Failed to open UART %s: %s", configuration.uartName, error_to_string(error)); - return -1; - } - - GpsModel model = configuration.model; - if (model == GpsModel::Unknown) { - model = probe(uart); - if (model == GpsModel::Unknown) { - LOG_E(TAG, "Probe failed"); - setState(State::Error); - return -1; - } - } - mutex.lock(); - this->model = model; - mutex.unlock(); - - if (!init(uart, model)) { - LOG_E(TAG, "Init failed"); - setState(State::Error); - return -1; - } - - setState(State::On); - - // Reference: https://gpsd.gitlab.io/gpsd/NMEA.html - while (!isThreadInterrupted()) { - size_t bytes_read = 0; - uart_controller_read_until(uart, buffer, GPS_UART_BUFFER_SIZE, '\n', true, &bytes_read, 100 / portTICK_PERIOD_MS); - - // Thread might've been interrupted in the meanwhile - if (isThreadInterrupted()) { - break; - } - - if (bytes_read > 0U) { - - LOG_I(TAG, "[%d] %s", (int)bytes_read, reinterpret_cast(buffer)); - - switch (minmea_sentence_id((char*)buffer, false)) { - case MINMEA_SENTENCE_RMC: - minmea_sentence_rmc rmc_frame; - if (minmea_parse_rmc(&rmc_frame, (char*)buffer)) { - mutex.lock(); - for (auto& subscription : rmcSubscriptions) { - (*subscription.onData)(getId(), rmc_frame); - } - mutex.unlock(); - LOG_D(TAG, "RMC %f lat, %f lon, %f m/s", minmea_tocoord(&rmc_frame.latitude), minmea_tocoord(&rmc_frame.longitude), minmea_tofloat(&rmc_frame.speed)); - } else { - LOG_E(TAG, "RMC parse error: %s", reinterpret_cast(buffer)); - } - break; - case MINMEA_SENTENCE_GGA: - minmea_sentence_gga gga_frame; - if (minmea_parse_gga(&gga_frame, (char*)buffer)) { - mutex.lock(); - for (auto& subscription : ggaSubscriptions) { - (*subscription.onData)(getId(), gga_frame); - } - mutex.unlock(); - LOG_D(TAG, "GGA %f lat, %f lon", minmea_tocoord(&gga_frame.latitude), minmea_tocoord(&gga_frame.longitude)); - } else { - LOG_E(TAG, "GGA parse error: %s", reinterpret_cast(buffer)); - } - break; - default: - break; - } - } - } - - if (uart_controller_close(uart) != ERROR_NONE) { - LOG_W(TAG, "Failed to stop UART %s", configuration.uartName); - } - - return 0; -} - -bool GpsDevice::start() { - auto lock = mutex.asScopedLock(); - lock.lock(); - - if (thread != nullptr && thread->getState() != Thread::State::Stopped) { - LOG_W(TAG, "Already started"); - return true; - } - - threadInterrupted = false; - - LOG_I(TAG, "Starting thread"); - setState(State::PendingOn); - - thread = std::make_unique( - "gps", - 4096, - [this]() { - return this->threadMain(); - } - ); - thread->setPriority(tt::Thread::Priority::High); - thread->start(); - - LOG_I(TAG, "Starting finished"); - return true; -} - -bool GpsDevice::stop() { - auto lock = mutex.asScopedLock(); - lock.lock(); - - setState(State::PendingOff); - - if (thread != nullptr) { - threadInterrupted = true; - - // Detach thread, it will auto-delete when leaving the current scope - auto old_thread = std::move(thread); - - if (old_thread->getState() != Thread::State::Stopped) { - // Unlock so thread can lock - lock.unlock(); - // Wait for thread to finish - old_thread->join(); - // Re-lock to continue logic below - lock.lock(); - } - } - - setState(State::Off); - - return true; -} - -bool GpsDevice::isThreadInterrupted() const { - auto lock = mutex.asScopedLock(); - lock.lock(); - return threadInterrupted; -} - -GpsModel GpsDevice::getModel() const { - auto lock = mutex.asScopedLock(); - lock.lock(); - return model; // Make copy because of thread safety -} - -GpsDevice::State GpsDevice::getState() const { - auto lock = mutex.asScopedLock(); - lock.lock(); - return state; // Make copy because of thread safety -} - -void GpsDevice::setState(State newState) { - auto lock = mutex.asScopedLock(); - lock.lock(); - state = newState; -} - -} // namespace tt::hal::gps diff --git a/Tactility/Source/hal/gps/Probe.cpp b/Tactility/Source/hal/gps/Probe.cpp deleted file mode 100644 index d5a8ca0b..00000000 --- a/Tactility/Source/hal/gps/Probe.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include "Tactility/hal/gps/GpsDevice.h" -#include "Tactility/hal/gps/Ublox.h" -#include - -#include -#include -#include - -#include - -constexpr auto* TAG = "Gps"; - -#define GPS_UART_BUFFER_SIZE 256 - -using namespace tt; -using namespace tt::hal; - -namespace tt::hal::gps { - -/** - * From: https://github.com/meshtastic/firmware/blob/3b0232de1b6282eacfbff6e50b68fca7e67b8511/src/meshUtils.cpp#L40 - */ -char* strnstr(const char* s, const char* find, size_t slen) { - char c; - if ((c = *find++) != '\0') { - char sc; - size_t len; - - len = strlen(find); - do { - do { - if (slen-- < 1 || (sc = *s++) == '\0') - return (nullptr); - } while (sc != c); - if (len > slen) - return (nullptr); - } while (strncmp(s, find, len) != 0); - s--; - } - return ((char*)s); -} - -/** - * From: https://github.com/meshtastic/firmware/blob/f81d3b045dd1b7e3ca7870af3da915ff4399ea98/src/gps/GPS.cpp - */ -GpsResponse getAck(::Device* uart, const char* message, uint32_t waitMillis) { - uint8_t buffer[768] = {0}; - uint8_t b; - int bytesRead = 0; - uint32_t startTimeout = kernel::getMillis() + waitMillis; -#ifdef GPS_DEBUG - std::string debugmsg = ""; -#endif - while (kernel::getMillis() < startTimeout) { - size_t available = 0; - uart_controller_get_available(uart, &available); - if (available > 0) { - uart_controller_read_byte(uart, &b, 1); - -#ifdef GPS_DEBUG - debugmsg += vformat("%c", (b >= 32 && b <= 126) ? b : '.'); -#endif - buffer[bytesRead] = b; - bytesRead++; - if ((bytesRead == 767) || (b == '\r')) { - if (strnstr((char*)buffer, message, bytesRead) != nullptr) { -#ifdef GPS_DEBUG - LOG_D(TAG, "Found: %s", message); // Log the found message -#endif - return GpsResponse::Ok; - } else { - bytesRead = 0; -#ifdef GPS_DEBUG - LOG_D(TAG, "%s", debugmsg.c_str()); -#endif - } - } - } - } - return GpsResponse::None; -} - -/** - * From: https://github.com/meshtastic/firmware/blob/f81d3b045dd1b7e3ca7870af3da915ff4399ea98/src/gps/GPS.cpp - */ -#define PROBE_SIMPLE(UART, CHIP, TOWRITE, RESPONSE, DRIVER, TIMEOUT, ...) \ - do { \ - LOG_I(TAG, "Probing for %s (%s)", CHIP, TOWRITE); \ - uart_controller_flush_input(UART); \ - uart_controller_write_bytes(UART, (const uint8_t*)(TOWRITE "\r\n"), strlen(TOWRITE "\r\n"), TIMEOUT); \ - if (getAck(UART, RESPONSE, TIMEOUT) == GpsResponse::Ok) { \ - LOG_I(TAG, "Probe detected %s %s", CHIP, #DRIVER); \ - return DRIVER; \ - } \ - } while (0) - -/** - * From: https://github.com/meshtastic/firmware/blob/f81d3b045dd1b7e3ca7870af3da915ff4399ea98/src/gps/GPS.cpp - */ -GpsModel probe(::Device* uart) { - // Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices) - uart_controller_write_bytes(uart, (const uint8_t*)"$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n", 40, 500); - kernel::delayMillis(20); - - // Close NMEA sequences on Ublox - uart_controller_write_bytes(uart, (const uint8_t*)"$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n", 29, 500); - uart_controller_write_bytes(uart, (const uint8_t*)"$PUBX,40,GSV,0,0,0,0,0,0*59\r\n", 29, 500); - uart_controller_write_bytes(uart, (const uint8_t*)"$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n", 29, 500); - kernel::delayMillis(20); - - // Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A - PROBE_SIMPLE(uart, "UC6580", "$PDTINFO", "UC6580", GpsModel::UC6580, 500); - PROBE_SIMPLE(uart, "UM600", "$PDTINFO", "UM600", GpsModel::UC6580, 500); - PROBE_SIMPLE(uart, "ATGM336H", "$PCAS06,1*1A", "$GPTXT,01,01,02,HW=ATGM336H", GpsModel::ATGM336H, 500); - - /* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) - based on AT6558 */ - PROBE_SIMPLE(uart, "ATGM332D", "$PCAS06,1*1A", "$GPTXT,01,01,02,HW=ATGM332D", GpsModel::ATGM336H, 500); - - /* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */ - uart_controller_write_bytes(uart, (const uint8_t*)"$PAIR062,2,0*3C\r\n", 17, 500); // GSA OFF to reduce volume - uart_controller_write_bytes(uart, (const uint8_t*)"$PAIR062,3,0*3D\r\n", 17, 500); // GSV OFF to reduce volume - uart_controller_write_bytes(uart, (const uint8_t*)"$PAIR513*3D\r\n", 13, 500); // save configuration - PROBE_SIMPLE(uart, "AG3335", "$PAIR021*39", "$PAIR021,AG3335", GpsModel::AG3335, 500); - PROBE_SIMPLE(uart, "AG3352", "$PAIR021*39", "$PAIR021,AG3352", GpsModel::AG3352, 500); - PROBE_SIMPLE(uart, "LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GpsModel::AG3352, 500); - - PROBE_SIMPLE(uart, "L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GpsModel::MTK, 500); - - // Close all NMEA sentences, valid for L76B MTK platform (Waveshare Pico GPS) - uart_controller_write_bytes(uart, (const uint8_t*)"$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n", 51, 500); - kernel::delayMillis(20); - - PROBE_SIMPLE(uart, "L76B", "$PMTK605*31", "Quectel-L76B", GpsModel::MTK_L76B, 500); - PROBE_SIMPLE(uart, "PA1616S", "$PMTK605*31", "1616S", GpsModel::MTK_PA1616S, 500); - - auto ublox_result = ublox::probe(uart); - if (ublox_result != GpsModel::Unknown) { - return ublox_result; - } else { - LOG_W(TAG, "No GNSS Module"); - return GpsModel::Unknown; - } -} - -} // namespace tt::hal::gps diff --git a/Tactility/Source/hal/gps/Satellites.cpp b/Tactility/Source/hal/gps/Satellites.cpp deleted file mode 100644 index a7d30ca4..00000000 --- a/Tactility/Source/hal/gps/Satellites.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include -#include - -#include - -namespace tt::hal::gps { - -constexpr auto* TAG = "Satellites"; - -constexpr bool hasTimeElapsed(TickType_t now, TickType_t timeInThePast, TickType_t expireTimeInTicks) { - return (TickType_t)(now - timeInThePast) >= expireTimeInTicks; -} - -SatelliteStorage::SatelliteRecord* SatelliteStorage::findRecord(int number) { - auto result = records | std::views::filter([number](auto& record) { - return record.inUse && record.data.nr == number; - }); - - if (!result.empty()) { - return &result.front(); - } else { - return nullptr; - } -} - -SatelliteStorage::SatelliteRecord* SatelliteStorage::findUnusedRecord() { - auto lock = mutex.asScopedLock(); - lock.lock(); - - auto result = records | std::views::filter([](auto& record) { - return !record.inUse; - }); - - if (!result.empty()) { - auto* record = &result.front(); - record->inUse = true; - LOG_D(TAG, "Found unused record"); - return record; - } else { - return nullptr; - } -} - -SatelliteStorage::SatelliteRecord* SatelliteStorage::findRecordToRecycle() { - auto lock = mutex.asScopedLock(); - lock.lock(); - - int candidate_index = -1; - auto candidate_age = kernel::MAX_TICKS; - TickType_t expire_duration = kernel::secondsToTicks(recycleTimeSeconds); - TickType_t now = kernel::getTicks(); - for (int i = 0; i < records.size(); ++i) { - // First try to find a record that is "old enough" - if (hasTimeElapsed(now, records[i].lastUpdated, expire_duration)) { - LOG_D(TAG, "! [%d] %u < %u", i, records[i].lastUpdated, expire_duration); - candidate_index = i; - break; - } - - // Otherwise keep finding the oldest record - if (records[i].inUse && records[i].lastUpdated < candidate_age) { - candidate_index = i; - candidate_age = records[i].lastUpdated; - LOG_D(TAG, "? [%d] %u < %u", i, records[i].lastUpdated, candidate_age); - } - } - - assert(candidate_index != -1); - - LOG_D(TAG, "Recycled record %d", candidate_index); - - return &records[candidate_index]; -} - -SatelliteStorage::SatelliteRecord* SatelliteStorage::findWithFallback(int number) { - auto lock = mutex.asScopedLock(); - lock.lock(); - - if (auto* found_record = findRecord(number)) { - return found_record; - } else if (auto* unused_record = findUnusedRecord()) { - return unused_record; - } else { - return findRecordToRecycle(); - } -} - -void SatelliteStorage::notify(const minmea_sat_info& data) { - auto lock = mutex.asScopedLock(); - lock.lock(); - - auto* record = findWithFallback(data.nr); - if (record != nullptr) { - record->inUse = true; - record->lastUpdated = kernel::getTicks(); - record->data = data; - LOG_D(TAG, "Updated satellite %d: elevation %d, azimuth %d, snr %d", record->data.nr, record->data.elevation, record->data.elevation, record->data.snr); - } -} - -void SatelliteStorage::getRecords(const std::function& onRecord) const { - auto lock = mutex.asScopedLock(); - lock.lock(); - - TickType_t expire_duration = kernel::secondsToTicks(recentTimeSeconds); - TickType_t now = kernel::getTicks(); - - for (auto& record: records) { - if (record.inUse && !hasTimeElapsed(now, record.lastUpdated, expire_duration)) { - onRecord(record.data); - } - } -} - -} // namespace tt::hal::gps diff --git a/Tactility/Source/hal/i2c/I2cDevice.cpp b/Tactility/Source/hal/i2c/I2cDevice.cpp deleted file mode 100644 index a59e455f..00000000 --- a/Tactility/Source/hal/i2c/I2cDevice.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include "Tactility/hal/i2c/I2cDevice.h" - -#include - -namespace tt::hal::i2c { - -bool I2cDevice::read(uint8_t* data, size_t dataSize, TickType_t timeout) { - return i2c_controller_read(controller, address, data, dataSize, timeout) == ERROR_NONE; -} - -bool I2cDevice::write(const uint8_t* data, uint16_t dataSize, TickType_t timeout) { - return i2c_controller_write(controller, address, data, dataSize, timeout) == ERROR_NONE; -} - -bool I2cDevice::writeRead(const uint8_t* writeData, size_t writeDataSize, uint8_t* readData, size_t readDataSize, TickType_t timeout) { - return i2c_controller_write_read(controller, address, writeData, writeDataSize, readData, readDataSize, timeout) == ERROR_NONE; -} - -bool I2cDevice::writeRegister(uint8_t reg, const uint8_t* data, uint16_t dataSize, TickType_t timeout) { - return i2c_controller_write_register(controller, address, reg, data, dataSize, timeout) == ERROR_NONE; -} - -bool I2cDevice::readRegister12(uint8_t reg, float& out) const { - std::uint8_t data[2] = {0}; - if (i2c_controller_read_register(controller, address, reg, data, 2, DEFAULT_TIMEOUT) == ERROR_NONE) { - out = (data[0] & 0x0F) << 8 | data[1]; - return true; - } else { - return false; - } -} - -bool I2cDevice::readRegister14(uint8_t reg, float& out) const { - std::uint8_t data[2] = {0}; - if (i2c_controller_read_register(controller, address, reg, data, 2, DEFAULT_TIMEOUT) == ERROR_NONE) { - out = (data[0] & 0x3F) << 8 | data[1]; - return true; - } else { - return false; - } -} - -bool I2cDevice::readRegister16(uint8_t reg, uint16_t& out) const { - std::uint8_t data[2] = {0}; - if (i2c_controller_read_register(controller, address, reg, data, 2, DEFAULT_TIMEOUT) == ERROR_NONE) { - out = data[0] << 8 | data[1]; - return true; - } else { - return false; - } -} - -bool I2cDevice::readRegister8(uint8_t reg, uint8_t& result) const { - return i2c_controller_write_read(controller, address, ®, 1, &result, 1, DEFAULT_TIMEOUT) == ERROR_NONE; -} - -bool I2cDevice::writeRegister8(uint8_t reg, uint8_t value) const { - return i2c_controller_write_register(controller, address, reg, &value, 1, DEFAULT_TIMEOUT) == ERROR_NONE; -} - -bool I2cDevice::bitOn(uint8_t reg, uint8_t bitmask) const { - uint8_t state; - if (readRegister8(reg, state)) { - state |= bitmask; - return writeRegister8(reg, state); - } else { - return false; - } -} - -bool I2cDevice::bitOff(uint8_t reg, uint8_t bitmask) const { - uint8_t state; - if (readRegister8(reg, state)) { - state &= ~bitmask; - return writeRegister8(reg, state); - } else { - return false; - } -} - -} // namespace tt::hal::i2c diff --git a/Tactility/Source/hal/power/PowerDevice.cpp b/Tactility/Source/hal/power/PowerDevice.cpp deleted file mode 100644 index 9f3085b3..00000000 --- a/Tactility/Source/hal/power/PowerDevice.cpp +++ /dev/null @@ -1,172 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#include - -#include -#include -#include -#include - -#include - -namespace tt::hal::power { - -#define GET_POWER_DEVICE(device) (static_cast(device_get_driver_data(device))) - -static PowerDevice::MetricType toMetricType(PowerSupplyProperty property) { - switch (property) { - case POWER_SUPPLY_PROP_IS_CHARGING: - return PowerDevice::MetricType::IsCharging; - case POWER_SUPPLY_PROP_CURRENT: - return PowerDevice::MetricType::Current; - case POWER_SUPPLY_PROP_VOLTAGE: - return PowerDevice::MetricType::BatteryVoltage; - case POWER_SUPPLY_PROP_CAPACITY: - default: - return PowerDevice::MetricType::ChargeLevel; - } -} - -static int toIntValue(PowerDevice::MetricType type, const PowerDevice::MetricData& data) { - switch (type) { - case PowerDevice::MetricType::IsCharging: - return data.valueAsBool ? 1 : 0; - case PowerDevice::MetricType::Current: - return data.valueAsInt32; - case PowerDevice::MetricType::BatteryVoltage: - return static_cast(data.valueAsUint32); - case PowerDevice::MetricType::ChargeLevel: - default: - return data.valueAsUint8; - } -} - -static bool apiSupportsProperty(::Device* device, PowerSupplyProperty property) { - return GET_POWER_DEVICE(device)->supportsMetric(toMetricType(property)); -} - -static error_t apiGetProperty(::Device* device, PowerSupplyProperty property, PowerSupplyPropertyValue* outValue) { - auto* power_device = GET_POWER_DEVICE(device); - auto metric_type = toMetricType(property); - if (!power_device->supportsMetric(metric_type)) { - return ERROR_NOT_SUPPORTED; - } - PowerDevice::MetricData data; - if (!power_device->getMetric(metric_type, data)) { - return ERROR_NOT_FOUND; - } - outValue->int_value = toIntValue(metric_type, data); - return ERROR_NONE; -} - -static bool apiSupportsChargeControl(::Device* device) { - return GET_POWER_DEVICE(device)->supportsChargeControl(); -} - -static bool apiIsAllowedToCharge(::Device* device) { - return GET_POWER_DEVICE(device)->isAllowedToCharge(); -} - -static error_t apiSetAllowedToCharge(::Device* device, bool allowed) { - auto* power_device = GET_POWER_DEVICE(device); - if (!power_device->supportsChargeControl()) { - return ERROR_NOT_SUPPORTED; - } - power_device->setAllowedToCharge(allowed); - return ERROR_NONE; -} - -static bool apiSupportsQuickCharge(::Device* device) { - return GET_POWER_DEVICE(device)->supportsQuickCharge(); -} - -static bool apiIsQuickChargeEnabled(::Device* device) { - return GET_POWER_DEVICE(device)->isQuickChargeEnabled(); -} - -static error_t apiSetQuickChargeEnabled(::Device* device, bool enabled) { - auto* power_device = GET_POWER_DEVICE(device); - if (!power_device->supportsQuickCharge()) { - return ERROR_NOT_SUPPORTED; - } - power_device->setQuickChargeEnabled(enabled); - return ERROR_NONE; -} - -static bool apiSupportsPowerOff(::Device* device) { - return GET_POWER_DEVICE(device)->supportsPowerOff(); -} - -static error_t apiPowerOff(::Device* device) { - auto* power_device = GET_POWER_DEVICE(device); - if (!power_device->supportsPowerOff()) { - return ERROR_NOT_SUPPORTED; - } - power_device->powerOff(); - return ERROR_NONE; -} - -static error_t startDevice(::Device*) { return ERROR_NONE; } -static error_t stopDevice(::Device*) { return ERROR_NONE; } - -static PowerSupplyApi powerSupplyApi { - .supports_property = apiSupportsProperty, - .get_property = apiGetProperty, - .supports_charge_control = apiSupportsChargeControl, - .is_allowed_to_charge = apiIsAllowedToCharge, - .set_allowed_to_charge = apiSetAllowedToCharge, - .supports_quick_charge = apiSupportsQuickCharge, - .is_quick_charge_enabled = apiIsQuickChargeEnabled, - .set_quick_charge_enabled = apiSetQuickChargeEnabled, - .supports_power_off = apiSupportsPowerOff, - .power_off = apiPowerOff, -}; - -static const char* powerSupplyCompatible[] = { "hal-power-device", nullptr }; - -static Driver powerSupplyDriver { - .name = "hal-power-device", - .compatible = powerSupplyCompatible, - .start_device = startDevice, - .stop_device = stopDevice, - .api = &powerSupplyApi, - .device_type = &POWER_SUPPLY_TYPE, - .owner = nullptr, - .internal = nullptr, -}; - -/** Registers the "hal-power-device" driver with the kernel on first use. */ -static Driver& getPowerSupplyDriver() { - static const bool registered = [] { - check(driver_construct_add(&powerSupplyDriver) == ERROR_NONE); - return true; - }(); - (void)registered; - return powerSupplyDriver; -} - -void PowerDevice::createPowerSupplyDevice() { - kernelDeviceName = std::format("power-supply-{}", getId()); - kernelDevice.name = kernelDeviceName.c_str(); - check(device_construct(&kernelDevice) == ERROR_NONE); - check(device_add(&kernelDevice) == ERROR_NONE); - device_set_driver(&kernelDevice, &getPowerSupplyDriver()); - check(device_start(&kernelDevice) == ERROR_NONE); - device_set_driver_data(&kernelDevice, this); -} - -void PowerDevice::destroyPowerSupplyDevice() { - device_set_driver_data(&kernelDevice, nullptr); - check(device_stop(&kernelDevice) == ERROR_NONE); - check(device_remove(&kernelDevice) == ERROR_NONE); - check(device_destruct(&kernelDevice) == ERROR_NONE); -} - -PowerDevice::PowerDevice() { - createPowerSupplyDevice(); -} - -PowerDevice::~PowerDevice() { - destroyPowerSupplyDevice(); -} - -} diff --git a/Tactility/Source/hal/sdcard/SdCard.cpp b/Tactility/Source/hal/sdcard/SdCard.cpp index 38339593..c895a668 100644 --- a/Tactility/Source/hal/sdcard/SdCard.cpp +++ b/Tactility/Source/hal/sdcard/SdCard.cpp @@ -1,43 +1,9 @@ -#include - #include #include -#include - -#include -#include namespace tt::hal::sdcard { -std::shared_ptr findSdCardLock(const std::string& path) { - struct Ctx { - const std::string& path; - std::shared_ptr result; - }; - Ctx ctx = { path, nullptr }; - - file_system_for_each(&ctx, [](FileSystem* fs, void* context) { - auto* c = static_cast(context); - char mount_path[64]; - if (file_system_get_path(fs, mount_path, sizeof(mount_path)) != ERROR_NONE) return true; - if (!c->path.starts_with(mount_path)) return true; - - auto* owner = file_system_get_owner(fs); - if (owner != nullptr) { - // Check for I2C controller: if it has more than 2 children, assume it's the display - // TODO: Improve this - auto* parent = device_get_parent(owner); - if (parent != nullptr && device_get_child_count(parent) >= 2) { - c->result = lvgl::getSyncLock(); - } - } - return false; - }); - - return ctx.result; -} - -void mountAll() { +void startAll() { device_for_each_of_type(&SDCARD_TYPE, nullptr, [](::Device* device, void*) -> bool { if (!device_is_ready(device)) { if (device_start(device) != ERROR_NONE) { diff --git a/Tactility/Source/hal/touch/KernelTouchDriver.cpp b/Tactility/Source/hal/touch/KernelTouchDriver.cpp deleted file mode 100644 index 7f8de3f8..00000000 --- a/Tactility/Source/hal/touch/KernelTouchDriver.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#include - -#include - -#include - -namespace tt::hal::touch { - -// Bus reads are expected to complete quickly; bound the wait so a stalled controller can't block the LVGL indev poll. -static constexpr TickType_t READ_TIMEOUT = pdMS_TO_TICKS(10); - -KernelTouchDriver::KernelTouchDriver(::Device* device) : device(device) { - assert(device_get_type(device) == &POINTER_TYPE); -} - -bool KernelTouchDriver::getTouchedPoints(uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* pointCount, uint8_t maxPointCount) { - if (pointer_read_data(device, READ_TIMEOUT) != ERROR_NONE) { - return false; - } - return pointer_get_touched_points(device, x, y, strength, pointCount, maxPointCount); -} - -} diff --git a/Tactility/Source/lvgl/Lvgl.cpp b/Tactility/Source/lvgl/Lvgl.cpp deleted file mode 100644 index 601114be..00000000 --- a/Tactility/Source/lvgl/Lvgl.cpp +++ /dev/null @@ -1,205 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -namespace tt::lvgl { - -constexpr auto* TAG = "LVGL"; - -bool isStarted() { - return module_is_started(&lvgl_module); -} - -void attachDevices() { - LOG_I(TAG, "Adding devices"); - - auto lock = getSyncLock()->asScopedLock(); - lock.lock(); - - // Start displays (their related touch devices start automatically within) - - LOG_I(TAG, "Start displays"); - auto hal_displays= hal::findDevices(hal::Device::Type::Display); - for (const auto& display: hal_displays) { - if (display->supportsLvgl()) { - if (display->startLvgl()) { - LOG_I(TAG, "Started %s", display->getName().c_str()); - } else { - LOG_E(TAG, "Start failed for %s", display->getName().c_str()); - } - } - } - - auto* primary_lvgl_display = lv_disp_get_default(); - if (primary_lvgl_display != nullptr) { - LOG_I(TAG, "Set default display rotation"); - auto settings = settings::display::loadOrGetDefault(); - lv_display_rotation_t rotation = settings::display::toLvglDisplayRotation(settings.orientation); - if (rotation != lv_display_get_rotation(primary_lvgl_display)) { - lv_display_set_rotation(primary_lvgl_display, rotation); - } - } - - // Start touch - - if (primary_lvgl_display != nullptr) { - LOG_I(TAG, "Start touch devices"); - auto touch_devices = hal::findDevices(hal::Device::Type::Touch); - for (const auto& touch_device: touch_devices) { - // Start any touch devices that haven't been started yet - if (touch_device->supportsLvgl() && touch_device->getLvglIndev() == nullptr) { - if (touch_device->startLvgl(primary_lvgl_display)) { - LOG_I(TAG, "Started %s", touch_device->getName().c_str()); - } else { - LOG_E(TAG, "Start failed for %s", touch_device->getName().c_str()); - } - } - } - - // Apply touch calibration (kernel POINTER_TYPE model only - see tactility/lvgl_pointer.h) - LOG_I(TAG, "Apply touch calibration"); - auto touch_calibration_settings = settings::touch::loadOrGetDefault(); - if (touch_calibration_settings.enabled && settings::touch::isValid(touch_calibration_settings)) { - auto* pointer_indev = lvgl_pointer_get_default(); - if (pointer_indev != nullptr) { - struct LvglPointerCalibration calibration = { - .x_min = touch_calibration_settings.xMin, - .x_max = touch_calibration_settings.xMax, - .y_min = touch_calibration_settings.yMin, - .y_max = touch_calibration_settings.yMax, - }; - if (lvgl_pointer_set_calibration(pointer_indev, &calibration) != ERROR_NONE) { - LOG_E(TAG, "Failed to apply saved touch calibration"); - } - } - } - - // Start keyboards - LOG_I(TAG, "Start keyboards"); - auto keyboards = hal::findDevices(hal::Device::Type::Keyboard); - for (const auto& keyboard: keyboards) { - if (keyboard->isAttached()) { - if (keyboard->startLvgl(primary_lvgl_display)) { - lv_indev_t* keyboard_indev = keyboard->getLvglIndev(); - hardware_keyboard_set_indev(keyboard_indev); - LOG_I(TAG, "Started %s", keyboard->getName().c_str()); - } else { - LOG_E(TAG, "Start failed for %s", keyboard->getName().c_str()); - } - } - } - - // Start encoders - LOG_I(TAG, "Start encoders"); - auto encoders = hal::findDevices(hal::Device::Type::Encoder); - for (const auto& encoder: encoders) { - if (encoder->startLvgl(primary_lvgl_display)) { - LOG_I(TAG, "Started %s", encoder->getName().c_str()); - } else { - LOG_E(TAG, "Start failed for %s", encoder->getName().c_str()); - } - } - } - - // Restart services - - // We search for the manifest first, because during the initial start() during boot - // the service won't be registered yet. - if (service::findManifestById("Gui") != nullptr) { - if (service::getState("Gui") == SERVICE_STATE_STOPPED) { - service::startService("Gui"); - } else { - LOG_E(TAG, "Gui service is not in Stopped state"); - } - } - - // We search for the manifest first, because during the initial start() during boot - // the service won't be registered yet. - if (service::findManifestById("Statusbar") != nullptr) { - if (service::getState("Statusbar") == SERVICE_STATE_STOPPED) { - service::startService("Statusbar"); - } else { - LOG_E(TAG, "Statusbar service is not in Stopped state"); - } - } -} - -void detachDevices() { - LOG_I(TAG, "Removing devices"); - - auto lock = getSyncLock()->asScopedLock(); - lock.lock(); - - // Stop services that highly depend on LVGL - - service::stopService("Statusbar"); - service::stopService("Gui"); - - // Stop keyboards - - LOG_I(TAG, "Stopping keyboards"); - auto keyboards = hal::findDevices(hal::Device::Type::Keyboard); - for (auto keyboard: keyboards) { - if (keyboard->getLvglIndev() != nullptr) { - keyboard->stopLvgl(); - } - } - - // Stop touch - - LOG_I(TAG, "Stopping touch"); - // The display generally stops their own touch devices, but we'll clean up anything that didn't - auto touch_devices = hal::findDevices(hal::Device::Type::Touch); - for (auto touch_device: touch_devices) { - if (touch_device->getLvglIndev() != nullptr) { - touch_device->stopLvgl(); - } - } - - // Stop encoders - - LOG_I(TAG, "Stopping encoders"); - // The display generally stops their own touch devices, but we'll clean up anything that didn't - auto encoder_devices = hal::findDevices(hal::Device::Type::Encoder); - for (auto encoder_device: encoder_devices) { - if (encoder_device->getLvglIndev() != nullptr) { - encoder_device->stopLvgl(); - } - } - // Stop displays (and their touch devices) - - LOG_I(TAG, "Stopping displays"); - auto displays = hal::findDevices(hal::Device::Type::Display); - for (auto display: displays) { - if (display->supportsLvgl() && display->getLvglDisplay() != nullptr && !display->stopLvgl()) { - LOG_E(TAG, "Failed to detach display from LVGL"); - } - } -} - -void start() { - check(module_start(&lvgl_module) == ERROR_NONE); -} - -void stop() { - check(module_stop(&lvgl_module) == ERROR_NONE); -} - -} // namespace diff --git a/Tactility/Source/lvgl/Toolbar.cpp b/Tactility/Source/lvgl/Toolbar.cpp index ebed4be5..61911487 100644 --- a/Tactility/Source/lvgl/Toolbar.cpp +++ b/Tactility/Source/lvgl/Toolbar.cpp @@ -3,6 +3,8 @@ #include #include +#include "tactility/drivers/pointer.h" + #include #include @@ -154,7 +156,7 @@ lv_obj_t* toolbar_create(lv_obj_t* parent, const std::string& title) { // If we don't have a touch device, we assume there's some other kind of input like a keyboard, an encoder or button control // In that scenario we want to automatically have the close button selected so the user doesn't have to press the widget selection // an extra time for every screen. - if (!hal::hasDevice(hal::Device::Type::Touch)) { + if (!device_has_active_by_type(&POINTER_TYPE)) { lv_group_focus_obj(toolbar->close_button); } diff --git a/Tactility/Source/network/Http.cpp b/Tactility/Source/network/Http.cpp index 9e7d0b67..46748d48 100644 --- a/Tactility/Source/network/Http.cpp +++ b/Tactility/Source/network/Http.cpp @@ -70,7 +70,8 @@ void download( auto bytes_left = client->getContentLength(); - auto lock = file::getLock(downloadFilePath)->asScopedLock(); + auto lockable = file::getLock(downloadFilePath); + auto lock = lockable->asScopedLock(); lock.lock(); LOG_I(TAG, "opening %s", downloadFilePath.c_str()); auto* file = fopen(downloadFilePath.c_str(), "wb"); diff --git a/Tactility/Source/network/HttpdReq.cpp b/Tactility/Source/network/HttpdReq.cpp index 1304b6c4..9af41cd1 100644 --- a/Tactility/Source/network/HttpdReq.cpp +++ b/Tactility/Source/network/HttpdReq.cpp @@ -186,7 +186,8 @@ size_t receiveFile(httpd_req_t* request, size_t length, const std::string& fileP char buffer[BUFFER_SIZE]; size_t bytes_received = 0; - auto lock = file::getLock(filePath)->asScopedLock(); + auto lockable = file::getLock(filePath); + auto lock = lockable->asScopedLock(); lock.lock(); auto* file = fopen(filePath.c_str(), "wb"); diff --git a/Tactility/Source/service/displayidle/DisplayIdle.cpp b/Tactility/Source/service/displayidle/DisplayIdle.cpp index 713ae276..9a3a82d0 100644 --- a/Tactility/Source/service/displayidle/DisplayIdle.cpp +++ b/Tactility/Source/service/displayidle/DisplayIdle.cpp @@ -2,32 +2,28 @@ #include -#include "Screensaver.h" #include "BouncingBallsScreensaver.h" #include "MatrixRainScreensaver.h" #include "MystifyScreensaver.h" +#include "Screensaver.h" #include "StackChanScreensaver.h" -#include -#include -#include -#include -#include #include #include #include #include +#include +#include +#include +#include + namespace tt::service::displayidle { constexpr auto* TAG = "DisplayIdle"; constexpr uint32_t kWakeActivityThresholdMs = 100; -static std::shared_ptr getDisplay() { - return hal::findFirstDevice(hal::Device::Type::Display); -} - void DisplayIdleService::stopScreensaverCb(lv_event_t* e) { auto* self = static_cast(lv_event_get_user_data(e)); lv_event_stop_bubbling(e); @@ -35,8 +31,34 @@ void DisplayIdleService::stopScreensaverCb(lv_event_t* e) { lv_display_trigger_activity(nullptr); } +static void setBacklightBrightness(uint8_t brightness) { + ::Device* display; + if (device_get_first_active_by_type(&DISPLAY_TYPE, &display) == ERROR_NONE) { + ::Device* backlight; + if (display_get_backlight(display, &backlight) == ERROR_NONE) { + device_get(backlight); + backlight_set_brightness(backlight, brightness); + device_put(backlight); + } + device_put(display); + } +} + +static bool hasDisplayWithBacklight() { + ::Device* display; + bool result = false; + if (device_get_first_active_by_type(&DISPLAY_TYPE, &display) == ERROR_NONE) { + ::Device* backlight; + if (display_get_backlight(display, &backlight) == ERROR_NONE) { + result = true; + } + device_put(display); + } + return result; +} + void DisplayIdleService::stopScreensaver() { - if (!lvgl::lock(100)) { + if (!lvgl_try_lock(100)) { // Lock failed - keep flag set to retry on next tick return; } @@ -52,7 +74,7 @@ void DisplayIdleService::stopScreensaver() { lv_obj_delete(screensaverOverlay); screensaverOverlay = nullptr; } - lvgl::unlock(); + lvgl_unlock(); stopScreensaverRequested.store(false, std::memory_order_relaxed); // Reset auto-off state @@ -60,10 +82,10 @@ void DisplayIdleService::stopScreensaver() { backlightOff = false; // Restore backlight if display was dimmed - auto display = getDisplay(); - if (display && wasDimmed) { - display->setBacklightDuty(restoreDuty); + if (wasDimmed) { + setBacklightBrightness(restoreDuty); } + displayDimmed = wasDimmed ? false : displayDimmed; } @@ -124,11 +146,11 @@ void DisplayIdleService::updateScreensaver() { } void DisplayIdleService::tick() { - if (!lvgl::lock(100)) { + if (!lvgl_try_lock(100)) { return; } if (lv_display_get_default() == nullptr) { - lvgl::unlock(); + lvgl_unlock(); return; } @@ -152,10 +174,7 @@ void DisplayIdleService::tick() { screensaver->stop(); screensaver.reset(); } - auto display = getDisplay(); - if (display) { - display->setBacklightDuty(0); - } + setBacklightBrightness(0); backlightOff = true; } else { updateScreensaver(); @@ -163,7 +182,7 @@ void DisplayIdleService::tick() { } } - lvgl::unlock(); + lvgl_unlock(); // Check stop request early for faster response if (stopScreensaverRequested.load(std::memory_order_acquire)) { @@ -171,23 +190,22 @@ void DisplayIdleService::tick() { return; } - auto display = getDisplay(); - if (display != nullptr && display->supportsBacklightDuty()) { + if (hasDisplayWithBacklight()) { if (!cachedDisplaySettings.backlightTimeoutEnabled || cachedDisplaySettings.backlightTimeoutMs == 0) { if (displayDimmed) { - display->setBacklightDuty(cachedDisplaySettings.backlightDuty); + setBacklightBrightness(cachedDisplaySettings.backlightDuty); displayDimmed = false; } } else { if (!displayDimmed && inactive_ms >= cachedDisplaySettings.backlightTimeoutMs) { - if (!lvgl::lock(100)) { + if (!lvgl_try_lock(100)) { return; // Retry on next tick } activateScreensaver(); - lvgl::unlock(); + lvgl_unlock(); // Turn off backlight for "None" screensaver (just black screen) if (cachedDisplaySettings.screensaverType == settings::display::ScreensaverType::None) { - display->setBacklightDuty(0); + setBacklightBrightness(0); } displayDimmed = true; } else if (displayDimmed && (inactive_ms < kWakeActivityThresholdMs)) { @@ -231,7 +249,7 @@ void DisplayIdleService::onStop(ServiceContext& service) { } void DisplayIdleService::startScreensaver() { - if (!lvgl::lock(100)) { + if (!lvgl_try_lock(100)) { return; } @@ -240,12 +258,11 @@ void DisplayIdleService::startScreensaver() { cachedDisplaySettings = settings::display::loadOrGetDefault(); activateScreensaver(); - lvgl::unlock(); + lvgl_unlock(); // Turn off backlight for "None" screensaver - auto display = getDisplay(); - if (display && cachedDisplaySettings.screensaverType == settings::display::ScreensaverType::None) { - display->setBacklightDuty(0); + if (hasDisplayWithBacklight() && cachedDisplaySettings.screensaverType == settings::display::ScreensaverType::None) { + setBacklightBrightness(0); } displayDimmed = true; } diff --git a/Tactility/Source/service/gps/GpsConfiguration.cpp b/Tactility/Source/service/gps/GpsConfiguration.cpp deleted file mode 100644 index ce4719be..00000000 --- a/Tactility/Source/service/gps/GpsConfiguration.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include -#include -#include - -#include -#include - -#include - -using tt::hal::gps::GpsDevice; - -namespace tt::service::gps { - -constexpr auto* TAG = "GpsService"; - -bool GpsService::getConfigurationFilePath(std::string& output) const { - if (paths == nullptr) { - LOG_E(TAG, "Can't add configuration: service not started"); - return false; - } - - if (!file::findOrCreateDirectory(paths->getUserDataDirectory(), 0777)) { - LOG_E(TAG, "Failed to find or create path %s", paths->getUserDataDirectory().c_str()); - return false; - } - - output = paths->getUserDataPath("config.bin"); - return true; -} - -bool GpsService::getGpsConfigurations(std::vector& configurations) const { - std::string path; - if (!getConfigurationFilePath(path)) { - return false; - } - - // If file does not exist, return empty list - if (access(path.c_str(), F_OK) != 0) { - LOG_W(TAG, "No configurations (file not found: %s)", path.c_str()); - return true; - } - - LOG_I(TAG, "Reading configuration file %s", path.c_str()); - auto reader = file::ObjectFileReader(path, sizeof(hal::gps::GpsConfiguration)); - if (!reader.open()) { - LOG_E(TAG, "Failed to open configuration file"); - return false; - } - - hal::gps::GpsConfiguration configuration; - while (reader.hasNext()) { - if (!reader.readNext(&configuration)) { - LOG_E(TAG, "Failed to read configuration"); - reader.close(); - return false; - } else { - configurations.push_back(configuration); - } - } - - return true; -} - -bool GpsService::addGpsConfiguration(hal::gps::GpsConfiguration configuration) { - std::string path; - if (!getConfigurationFilePath(path)) { - return false; - } - - auto appender = file::ObjectFileWriter(path, sizeof(hal::gps::GpsConfiguration), 1, true); - if (!appender.open()) { - LOG_E(TAG, "Failed to open/create configuration file"); - return false; - } - - if (!appender.write(&configuration)) { - LOG_E(TAG, "Failed to add configuration"); - appender.close(); - return false; - } - - appender.close(); - return true; -} - -bool GpsService::removeGpsConfiguration(hal::gps::GpsConfiguration configuration) { - std::string path; - if (!getConfigurationFilePath(path)) { - return false; - } - - std::vector configurations; - if (!getGpsConfigurations(configurations)) { - LOG_E(TAG, "Failed to get gps configurations"); - return false; - } - - auto count = std::erase_if(configurations, [&configuration](auto& item) { - return strcmp(item.uartName, configuration.uartName) == 0 && - item.baudRate == configuration.baudRate && - item.model == configuration.model; - }); - - auto writer = file::ObjectFileWriter(path, sizeof(hal::gps::GpsConfiguration), 1, false); - if (!writer.open()) { - LOG_E(TAG, "Failed to open configuration file"); - return false; - } - - for (auto& configuration : configurations) { - writer.write(&configuration); - } - - writer.close(); - - return count > 0; -} - -} // namespace tt::service::gps diff --git a/Tactility/Source/service/gps/GpsService.cpp b/Tactility/Source/service/gps/GpsService.cpp deleted file mode 100644 index a67685e9..00000000 --- a/Tactility/Source/service/gps/GpsService.cpp +++ /dev/null @@ -1,259 +0,0 @@ -#include - -#include -#include -#include -#include - -#include - -using tt::hal::gps::GpsDevice; - -namespace tt::service::gps { - -constexpr auto* TAG = "GpsService"; -extern const ServiceManifest manifest; - -constexpr bool hasTimeElapsed(TickType_t now, TickType_t timeInThePast, TickType_t expireTimeInTicks) { - return (now - timeInThePast) >= expireTimeInTicks; -} - -GpsService::GpsDeviceRecord* GpsService::findGpsRecord(const std::shared_ptr& device) { - auto lock = mutex.asScopedLock(); - lock.lock(); - - auto result = std::views::filter(deviceRecords, [&device](auto& record) { - return record.device.get() == device.get(); - }); - if (!result.empty()) { - return &result.front(); - } else { - return nullptr; - } -} - -void GpsService::addGpsDevice(const std::shared_ptr& device) { - auto lock = mutex.asScopedLock(); - lock.lock(); - - GpsDeviceRecord record = {.device = device}; - - if (getState() == State::On) { // Ignore during OnPending due to risk of data corruption - startGpsDevice(record); - } - - deviceRecords.push_back(record); -} - -void GpsService::removeGpsDevice(const std::shared_ptr& device) { - auto lock = mutex.asScopedLock(); - lock.lock(); - - GpsDeviceRecord* record = findGpsRecord(device); - - if (getState() == State::On) { // Ignore during OnPending due to risk of data corruption - stopGpsDevice(*record); - } - - std::erase_if(deviceRecords, [&device](auto& reference) { - return reference.device.get() == device.get(); - }); -} - -bool GpsService::onStart(ServiceContext& serviceContext) { - auto lock = mutex.asScopedLock(); - lock.lock(); - paths = serviceContext.getPaths(); - return true; -} - -void GpsService::onStop(ServiceContext& serviceContext) { - if (getState() == State::On) { - stopReceiving(); - } -} - -bool GpsService::startGpsDevice(GpsDeviceRecord& record) { - LOG_I(TAG, "[device %u] starting", (unsigned)record.device->getId()); - - auto lock = mutex.asScopedLock(); - lock.lock(); - - auto device = record.device; - - if (!device->start()) { - LOG_E(TAG, "[device %u] starting failed", (unsigned)record.device->getId()); - return false; - } - - record.satelliteSubscriptionId = device->subscribeGga([this](hal::Device::Id deviceId, auto& record) { - mutex.lock(); - if (record.fix_quality > 0) { - ggaRecord = record; - ggaTime = kernel::getTicks(); - } - onGgaSentence(deviceId, record); - mutex.unlock(); - }); - - record.rmcSubscriptionId = device->subscribeRmc([this](hal::Device::Id deviceId, auto& record) { - mutex.lock(); - if (record.longitude.value != 0 && record.longitude.scale != 0) { - rmcRecord = record; - rmcTime = kernel::getTicks(); - } - onRmcSentence(deviceId, record); - mutex.unlock(); - }); - - return true; -} - -bool GpsService::stopGpsDevice(GpsDeviceRecord& record) { - LOG_I(TAG, "[device %u] stopping", (unsigned)record.device->getId()); - - auto device = record.device; - - device->unsubscribeGga(record.satelliteSubscriptionId); - device->unsubscribeRmc(record.rmcSubscriptionId); - - record.satelliteSubscriptionId = -1; - record.rmcSubscriptionId = -1; - - if (!device->stop()) { - LOG_E(TAG, "[device %u] stopping failed", (unsigned)record.device->getId()); - return false; - } - - return true; -} - -bool GpsService::startReceiving() { - LOG_I(TAG, "Start receiving"); - - if (getState() != State::Off) { - LOG_E(TAG, "Already receiving"); - return false; - } - - setState(State::OnPending); - - auto lock = mutex.asScopedLock(); - lock.lock(); - - deviceRecords.clear(); - - std::vector configurations; - if (!getGpsConfigurations(configurations)) { - LOG_E(TAG, "Failed to get GPS configurations"); - setState(State::Off); - return false; - } - - if (configurations.empty()) { - LOG_E(TAG, "No GPS configurations"); - setState(State::Off); - return false; - } - - for (const auto& configuration: configurations) { - auto device = std::make_shared(configuration); - addGpsDevice(device); - } - - // Reset times before starting devices to avoid race with incoming data - rmcTime = 0; - ggaTime = 0; - - bool started_one_or_more = false; - - for (auto& record: deviceRecords) { - started_one_or_more |= startGpsDevice(record); - } - - if (started_one_or_more) { - setState(State::On); - return true; - } else { - setState(State::Off); - return false; - } -} - -void GpsService::stopReceiving() { - LOG_I(TAG, "Stop receiving"); - - setState(State::OffPending); - - auto lock = mutex.asScopedLock(); - lock.lock(); - - for (auto& record: deviceRecords) { - stopGpsDevice(record); - } - - rmcTime = 0; - ggaTime = 0; - - setState(State::Off); -} - -void GpsService::onGgaSentence(hal::Device::Id deviceId, const minmea_sentence_gga& gga) { - LOG_D(TAG, "[device %u] LAT %f LON %f, satellites: %d", (unsigned)deviceId, minmea_tocoord(&gga.latitude), minmea_tocoord(&gga.longitude), gga.satellites_tracked); -} - -void GpsService::onRmcSentence(hal::Device::Id deviceId, const minmea_sentence_rmc& rmc) { - LOG_D(TAG, "[device %u] LAT %f LON %f, speed: %f", (unsigned)deviceId, minmea_tocoord(&rmc.latitude), minmea_tocoord(&rmc.longitude), minmea_tofloat(&rmc.speed)); -} - -State GpsService::getState() const { - auto lock = stateMutex.asScopedLock(); - lock.lock(); - return state; -} - -void GpsService::setState(State newState) { - auto lock = stateMutex.asScopedLock(); - lock.lock(); - state = newState; - lock.unlock(); - statePubSub->publish(state); -} - -bool GpsService::hasCoordinates() const { - auto lock = mutex.asScopedLock(); - lock.lock(); - return getState() == State::On && rmcTime != 0 && !hasTimeElapsed(kernel::getTicks(), rmcTime, kernel::secondsToTicks(10)); -} - -bool GpsService::getCoordinates(minmea_sentence_rmc& rmc) const { - if (hasCoordinates()) { - rmc = rmcRecord; - return true; - } else { - return false; - } -} - -bool GpsService::getGga(minmea_sentence_gga& gga) const { - auto lock = mutex.asScopedLock(); - lock.lock(); - if (getState() == State::On && ggaTime != 0 && !hasTimeElapsed(kernel::getTicks(), ggaTime, kernel::secondsToTicks(10))) { - gga = ggaRecord; - return true; - } - return false; -} - -std::shared_ptr findGpsService() { - auto service = findServiceById(manifest.id); - assert(service != nullptr); - return std::static_pointer_cast(service); -} - -extern const ServiceManifest manifest = { - .id = "Gps", - .createService = create -}; - -} // namespace tt::service::gps diff --git a/Tactility/Source/service/keyboardidle/KeyboardIdle.cpp b/Tactility/Source/service/keyboardidle/KeyboardIdle.cpp index aef3b083..3ab69161 100644 --- a/Tactility/Source/service/keyboardidle/KeyboardIdle.cpp +++ b/Tactility/Source/service/keyboardidle/KeyboardIdle.cpp @@ -1,16 +1,18 @@ #ifdef ESP_PLATFORM -#include -#include -#include +#include + + +#include #include #include #include #include -#include #include #include +#include +#include namespace tt::service::keyboardidle { @@ -20,18 +22,29 @@ class KeyboardIdleService final : public Service { bool keyboardDimmed = false; settings::keyboard::KeyboardSettings cachedKeyboardSettings; - static std::shared_ptr getKeyboard() { - return hal::findFirstDevice(hal::Device::Type::Keyboard); - } - + // TODO: This only works for the fist active keyboard. Update it so it works for all keyboards with a backlight. static Device* getKeyboardBacklight() { - return device_find_by_name("keyboard_backlight"); + ::Device* keyboard; + if (device_get_first_active_by_type(&KEYBOARD_TYPE, &keyboard) == ERROR_NONE) { + ::Device* backlight = nullptr; + keyboard_get_backlight(keyboard, &backlight); // disregard result + device_put(keyboard); + return backlight; // WARNING: did not increase refcount + } + // TODO: Remove after all drivers are migrated + ::Device* backlight; + if (device_get_by_name("keyboard_backlight", &backlight) != ERROR_NONE) { + return nullptr; + } + + return backlight; } void setKeyboardBacklightBrightness(uint8_t brightness) { Device* backlight = getKeyboardBacklight(); if (backlight != nullptr) { backlight_set_brightness(backlight, brightness); + device_put(backlight); } } @@ -41,14 +54,16 @@ class KeyboardIdleService final : public Service { // Query LVGL inactivity once for both checks uint32_t inactive_ms = 0; - if (lvgl::lock(100)) { + if (lvgl_try_lock(100)) { inactive_ms = lv_display_get_inactive_time(nullptr); - lvgl::unlock(); + lvgl_unlock(); + } else { + // Assume it's not used + inactive_ms = 100; } // Handle keyboard backlight - auto keyboard = getKeyboard(); - if (keyboard != nullptr && keyboard->isAttached()) { + if (device_has_active_by_type(&KEYBOARD_TYPE)) { // If timeout disabled, ensure backlight restored if we had dimmed it if (!cachedKeyboardSettings.backlightTimeoutEnabled || cachedKeyboardSettings.backlightTimeoutMs == 0) { if (keyboardDimmed) { @@ -89,8 +104,7 @@ public: timer = nullptr; } // Ensure keyboard restored on stop - auto keyboard = getKeyboard(); - if (keyboard && keyboardDimmed) { + if (device_has_active_by_type(&KEYBOARD_TYPE) && keyboardDimmed) { setKeyboardBacklightBrightness(cachedKeyboardSettings.backlightEnabled ? cachedKeyboardSettings.backlightBrightness : 0); keyboardDimmed = false; } diff --git a/Tactility/Source/service/statusbar/Statusbar.cpp b/Tactility/Source/service/statusbar/Statusbar.cpp index cb9c8e7d..968c6006 100644 --- a/Tactility/Source/service/statusbar/Statusbar.cpp +++ b/Tactility/Source/service/statusbar/Statusbar.cpp @@ -2,30 +2,32 @@ #include #include -#include -#include -#include +#include #include #include #include #include -#include -#include -#include -#include +#include +#include #include +#include +#include +#include +#include #include #include #include -#include -#include -#include +#include +#include +#include + +#include #include #include -#include +#include namespace tt::service::statusbar { @@ -152,8 +154,7 @@ class StatusbarService final : public Service { } void updateGpsIcon() { - auto gps_state = gps::findGpsService()->getState(); - bool show_icon = (gps_state == gps::State::OnPending) || (gps_state == gps::State::On); + bool show_icon = device_has_active_by_type(&GPS_TYPE); if (gps_last_state != show_icon) { if (show_icon) { lvgl::statusbar_icon_set_image(gps_icon_id, LVGL_ICON_STATUSBAR_LOCATION_ON); @@ -267,15 +268,15 @@ class StatusbarService final : public Service { } void update() { - if (lvgl::isStarted()) { - if (lvgl::lock(100)) { + if (module_is_started(&lvgl_module)) { + if (lvgl_try_lock(100)) { updateGpsIcon(); updateBluetoothIcon(); updateWifiIcon(); updateSdCardIcon(); updatePowerStatusIcon(); updateUsbIcon(); - lvgl::unlock(); + lvgl_unlock(); } } } diff --git a/Tactility/Source/service/webserver/WebServerService.cpp b/Tactility/Source/service/webserver/WebServerService.cpp index 12f2de64..4062da91 100644 --- a/Tactility/Source/service/webserver/WebServerService.cpp +++ b/Tactility/Source/service/webserver/WebServerService.cpp @@ -11,7 +11,6 @@ #include #include -#include #include #include #include diff --git a/Tactility/Source/settings/DisplaySettings.cpp b/Tactility/Source/settings/DisplaySettings.cpp index 1e62ca4e..1ce13cb1 100644 --- a/Tactility/Source/settings/DisplaySettings.cpp +++ b/Tactility/Source/settings/DisplaySettings.cpp @@ -3,8 +3,6 @@ #include #include #include -#include -#include #include #include diff --git a/TactilityC/Include/tt_gps.h b/TactilityC/Include/tt_gps.h deleted file mode 100644 index 2a9e2f93..00000000 --- a/TactilityC/Include/tt_gps.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -bool tt_gps_has_coordinates(); - -bool tt_gps_get_coordinates( - float* longitude, - float* latitude, - float* speed, - float* course, - int* day, - int* month, - int* year -); - -#ifdef __cplusplus -} -#endif diff --git a/TactilityC/Include/tt_hal_device.h b/TactilityC/Include/tt_hal_device.h deleted file mode 100644 index e41e17e7..00000000 --- a/TactilityC/Include/tt_hal_device.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum { - DEVICE_TYPE_I2C, - DEVICE_TYPE_DISPLAY, - DEVICE_TYPE_TOUCH, - DEVICE_TYPE_SDCARD, - DEVICE_TYPE_KEYBOARD, - DEVICE_TYPE_POWER, - DEVICE_TYPE_GPS -} TtDeviceType; - -typedef uint32_t DeviceId; - -/** - * Find one or more devices of a certain type. - * @param[in] type the type to look for - * @param[inout] deviceIds the output ids, which should fit at least maxCount amount of devices - * @param[out] count the resulting number of device ids that were returned - * @param[in] maxCount the maximum number of items that the "deviceIds" output can contain (minimum value is 1) - * @return true if one or more devices were found - */ -bool tt_hal_device_find(TtDeviceType type, DeviceId* deviceIds, uint16_t* count, uint16_t maxCount); - -#ifdef __cplusplus -} -#endif diff --git a/TactilityC/Include/tt_hal_display.h b/TactilityC/Include/tt_hal_display.h deleted file mode 100644 index 1a7af6a0..00000000 --- a/TactilityC/Include/tt_hal_display.h +++ /dev/null @@ -1,100 +0,0 @@ -#pragma once - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef void* DisplayDriverHandle; - -typedef enum { - COLOR_FORMAT_MONOCHROME, // 1 bpp - COLOR_FORMAT_BGR565, - COLOR_FORMAT_BGR565_SWAPPED, - COLOR_FORMAT_RGB565, - COLOR_FORMAT_RGB565_SWAPPED, - COLOR_FORMAT_RGB888 -} ColorFormat; - -/** - * Check if the display driver interface is supported for this device. - * @param[in] displayId the identifier of the display device - * @return true if the driver is supported. - */ -bool tt_hal_display_driver_supported(DeviceId displayId); - -/** - * Allocate a driver object for the specified displayId. - * @warning check whether the driver is supported by calling tt_hal_display_driver_supported() first - * @param[in] displayId the identifier of the display device - * @return the driver handle - */ -DisplayDriverHandle tt_hal_display_driver_alloc(DeviceId displayId); - -/** - * Free the memory for the display driver. - * @param[in] handle the display driver handle - */ -void tt_hal_display_driver_free(DisplayDriverHandle handle); - -/** - * Lock the display device. Call this function before doing any draw calls. - * Certain display devices are on a shared bus (e.g. SPI) so they must run - * mutually exclusive with other devices on the same bus (e.g. SD card) - * @param[in] handle the display driver handle - * @param[in] timeout the maximum amount of ticks to wait for getting a lock - * @return true if the lock was acquired - */ -bool tt_hal_display_driver_lock(DisplayDriverHandle handle, TickType_t timeout); - -/** - * Unlock the display device. Must be called exactly once after locking. - * @param[in] handle the display driver handle - */ -void tt_hal_display_driver_unlock(DisplayDriverHandle handle); - -/** - * @param[in] handle the display driver handle - * @return the native color format for this display - */ -ColorFormat tt_hal_display_driver_get_colorformat(DisplayDriverHandle handle); - -/** - * @param[in] handle the display driver handle - * @return the horizontal resolution of the display - */ -uint16_t tt_hal_display_driver_get_pixel_width(DisplayDriverHandle handle); - -/** - * @param[in] handle the display driver handle - * @return the vertical resolution of the display - */ -uint16_t tt_hal_display_driver_get_pixel_height(DisplayDriverHandle handle); - -/** - * Draw pixels on the screen. Make sure to call the lock function first and unlock afterwards. - * Many draw calls can be done inbetween a single lock and unlock. - * @param[in] handle the display driver handle - * @param[in] xStart the starting x coordinate for rendering the pixel data - * @param[in] yStart the starting y coordinate for rendering the pixel data - * @param[in] xEnd the last x coordinate for rendering the pixel data (absolute pixel value, not relative to xStart!) - * @param[in] yEnd the last y coordinate for rendering the pixel data (absolute pixel value, not relative to yStart!) - * @param[in] pixelData a buffer of pixels. the data is placed as "RowRowRowRow". The size depends on the ColorFormat - */ -void tt_hal_display_driver_draw_bitmap(DisplayDriverHandle handle, int xStart, int yStart, int xEnd, int yEnd, const void* pixelData); - -/** - * Get direct pointers to the display's hardware frame buffer(s), if supported. - * Only available for panels with direct CPU-addressable frame buffers (e.g. MIPI-DSI/DPI). - * @param[in] handle the display driver handle - * @param[out] outBuffers receives up to 2 frame buffer pointers - * @return number of buffers written to outBuffers (0 if unsupported) - */ -uint8_t tt_hal_display_driver_get_frame_buffers(DisplayDriverHandle handle, void* outBuffers[2]); - -#ifdef __cplusplus -} -#endif diff --git a/TactilityC/Include/tt_hal_gpio.h b/TactilityC/Include/tt_hal_gpio.h deleted file mode 100644 index b80bd3eb..00000000 --- a/TactilityC/Include/tt_hal_gpio.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** @deprecated NON-FUNCTIONAL - WILL BE REMOVED SOON */ -typedef unsigned int GpioPin; - -/** @deprecated NON-FUNCTIONAL - WILL BE REMOVED SOON */ -#define GPIO_NO_PIN -1 - -/** @deprecated NON-FUNCTIONAL - WILL BE REMOVED SOON */ -bool tt_hal_gpio_get_level(GpioPin pin); - -/** @deprecated NON-FUNCTIONAL - WILL BE REMOVED SOON */ -int tt_hal_gpio_get_pin_count(); - -#ifdef __cplusplus -} -#endif diff --git a/TactilityC/Include/tt_hal_touch.h b/TactilityC/Include/tt_hal_touch.h deleted file mode 100644 index b115a027..00000000 --- a/TactilityC/Include/tt_hal_touch.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include "tt_hal_device.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef void* TouchDriverHandle; -/** - * Check if the touch driver interface is supported for this device. - * @param[in] touchDeviceId the identifier of the touch device - * @return true if the driver is supported. - */ -bool tt_hal_touch_driver_supported(DeviceId touchDeviceId); - -/** - * Allocate a driver object for the specified touchDeviceId. - * @warning check whether the driver is supported by calling tt_hal_touch_driver_supported() first - * @param[in] touchDeviceId the identifier of the touch device - * @return the driver handle - */ -TouchDriverHandle tt_hal_touch_driver_alloc(DeviceId touchDeviceId); - -/** - * Free the memory for the touch driver. - * @param[in] handle the touch driver handle - */ -void tt_hal_touch_driver_free(TouchDriverHandle handle); - -/** - * Get the coordinates for the currently touched points on the screen. - * - * @param[in] handle the touch driver handle - * @param[in] x array of X coordinates - * @param[in] y array of Y coordinates - * @param[in] strength array of strengths (with the minimum size of maxPointCount) or NULL - * @param[in] pointCount the number of points currently touched on the screen - * @param[in] maxPointCount the maximum number of points that can be touched at once - * - * @return true when touched and coordinates are available - */ -bool tt_hal_touch_driver_get_touched_points(TouchDriverHandle handle, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* pointCount, uint8_t maxPointCount); - -#ifdef __cplusplus -} -#endif diff --git a/TactilityC/Include/tt_hal_uart.h b/TactilityC/Include/tt_hal_uart.h deleted file mode 100644 index 8f787aa0..00000000 --- a/TactilityC/Include/tt_hal_uart.h +++ /dev/null @@ -1,159 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - * WARNING: THIS API IS NON-FUNCTIONAL AND DEPRECATED. - * IT WILL BE REMOVED IN A FUTURE RELEASE ONCE OFFICIAL APPS ARE MIGRATED. -* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - */ - -/** - * @file tt_hal_uart.h - * @brief C HAL interface for UART devices used by Tactility C modules. - * - * This header exposes a minimal, C-compatible UART API that mirrors the higher-level - * C++ UART interface (see Tactility/hal/uart). - * - * General notes: - * - Start the UART before I/O using tt_hal_uart_start(); stop it with tt_hal_uart_stop(). - */ - -typedef void* UartHandle; /**< Opaque handle to an underlying UART instance. */ - -/** - * @brief Get the number of UART devices available on this platform. - * @return Count of discoverable UARTs (0 if none). - */ -size_t tt_hal_uart_get_count(); - -/** - * @brief Get the user-friendly name of a UART by index. - * @param index Zero-based UART index in the range [0, tt_hal_uart_get_count()). - * @param[out] name Destination buffer to receive a null-terminated name. - * @param nameSizeLimit Size in bytes of the destination buffer. The name will be - * truncated to fit and always null-terminated if the size - * is greater than 0. - * @return true if a name was written to the buffer; false if the index is out of range - * or on other failure. - */ -bool tt_hal_uart_get_name(size_t index, char* name, size_t nameSizeLimit); - -/** - * @brief Allocate an opaque UART handle by index. - * - * Allocation does not start the hardware; call tt_hal_uart_start() to begin I/O. - * - * @param index Zero-based UART index. - * @return A valid UartHandle on success; NULL on failure (e.g., invalid index or already in use). - */ -UartHandle tt_hal_uart_alloc(size_t index); - -/** - * @brief Release a previously allocated UART handle and any associated resources. - * @param handle Handle returned by tt_hal_uart_alloc() - */ -void tt_hal_uart_free(UartHandle handle); - -/** - * @brief Start the UART so it can perform I/O. - * @param handle A valid UART handle. - * @return true on success; false on failure. - */ -bool tt_hal_uart_start(UartHandle handle); - -/** - * @brief Query whether the UART has been started. - * @param handle A valid UART handle. - * @return true if started; false otherwise. - */ -bool tt_hal_uart_is_started(UartHandle handle); - -/** - * @brief Stop the UART - * @param handle A valid UART handle. - * @return true on success; false on failure. - */ -bool tt_hal_uart_stop(UartHandle handle); - -/** - * @brief Read up to bufferSize bytes into buffer. - * - * This call may block up to timeout ticks waiting for data. It returns the actual - * number of bytes placed into the buffer, which can be less than bufferSize if - * fewer bytes became available before the timeout expired. - * - * @param handle A valid UART handle. - * @param[out] buffer Destination buffer. - * @param bufferSize Capacity of the destination buffer in bytes. - * @param timeout Maximum time to wait in ticks. Use 0 for non-blocking; use TT_MAX_TICKS - * to wait indefinitely. - * @return The number of bytes read (0 on timeout with no data). Never exceeds bufferSize. - */ -size_t tt_hal_uart_read_bytes(UartHandle handle, char* buffer, size_t bufferSize, TickType_t timeout); - -/** - * @brief Read a single byte. - * - * @param handle A valid UART handle. - * @param[out] output Where to store the read byte. - * @param timeout Maximum time to wait in ticks. Use 0 for non-blocking; use TT_MAX_TICKS - * to wait indefinitely. - * @return true if a byte was read and stored in output; false on timeout or failure. - */ -bool tt_hal_uart_read_byte(UartHandle handle, char* output, TickType_t timeout); - -/** - * @brief Write up to bufferSize bytes from buffer. - * - * This call may block up to timeout ticks waiting for transmit queue space. It returns - * the number of bytes accepted for transmission. - * - * @param handle A valid UART handle. - * @param[in] buffer Source buffer containing bytes to write. - * @param bufferSize Number of bytes to write from buffer. - * @param timeout Maximum time to wait in ticks. Use 0 for non-blocking; use TT_MAX_TICKS - * to wait indefinitely. - * @return The number of bytes written (may be less than bufferSize on timeout). - */ -size_t tt_hal_uart_write_bytes(UartHandle handle, const char* buffer, size_t bufferSize, TickType_t timeout); - -/** - * @brief Get the number of bytes currently available to read without blocking. - * @param handle A valid UART handle. - * @return The count of bytes available in the receive buffer. - */ -size_t tt_hal_uart_available(UartHandle handle); - -/** - * @brief Set the UART baud rate. - * @param handle A valid UART handle. - * @param baud_rate Desired baud rate in bits per second (e.g., 115200). - * @return true on success; false if the rate is unsupported or on error. - */ -bool tt_hal_uart_set_baud_rate(UartHandle handle, size_t baud_rate); - -/** - * @brief Get the current UART baud rate. - * @param handle A valid UART handle. - * @return The configured baud rate in bits per second. - */ -uint32_t tt_hal_uart_get_baud_rate(UartHandle handle); - -/** - * @brief Flush the UART input (receive) buffer, discarding any unread data. - * @param handle A valid UART handle. - */ -void tt_hal_uart_flush_input(UartHandle handle); - -#ifdef __cplusplus -} -#endif diff --git a/TactilityC/Include/tt_lvgl.h b/TactilityC/Include/tt_lvgl.h deleted file mode 100644 index 9d7245d4..00000000 --- a/TactilityC/Include/tt_lvgl.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define TT_LVGL_DEFAULT_LOCK_TIME 500 // 500 ticks = 500 ms - -/** @return true if LVGL is started and active */ -bool tt_lvgl_is_started(); - -/** Start LVGL and related background services */ -void tt_lvgl_start(); - -/** Stop LVGL and related background services */ -void tt_lvgl_stop(); - -/** Lock the LVGL context. Call this before doing LVGL-related operations from a non-LVLG thread */ -bool tt_lvgl_lock(TickType_t timeout); - -/** Unlock the LVGL context */ -void tt_lvgl_unlock(); - -#ifdef __cplusplus -} -#endif diff --git a/TactilityC/Source/tt_gps.cpp b/TactilityC/Source/tt_gps.cpp deleted file mode 100644 index 5131193c..00000000 --- a/TactilityC/Source/tt_gps.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "tt_gps.h" -#include - -using namespace tt::service; - -extern "C" { - -bool tt_gps_has_coordinates() { - auto service = gps::findGpsService(); - return service != nullptr && service->hasCoordinates(); -} - -bool tt_gps_get_coordinates( - float* longitude, - float* latitude, - float* speed, - float* course, - int* day, - int* month, - int* year -) { - auto service = gps::findGpsService(); - - if (service == nullptr) { - return false; - } - - minmea_sentence_rmc rmc; - - if (!service->getCoordinates(rmc)) { - return false; - } - - *longitude = minmea_tocoord(&rmc.longitude); - *latitude = minmea_tocoord(&rmc.latitude); - *speed = minmea_tocoord(&rmc.speed); - *course = minmea_tocoord(&rmc.course); - *day = rmc.date.day; - *month = rmc.date.month; - *year = rmc.date.year; - - return true; -} - -} \ No newline at end of file diff --git a/TactilityC/Source/tt_hal_device.cpp b/TactilityC/Source/tt_hal_device.cpp deleted file mode 100644 index 6a5ad1f4..00000000 --- a/TactilityC/Source/tt_hal_device.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include "tt_hal_device.h" - -#include - -#include - -static tt::hal::Device::Type toTactilityDeviceType(TtDeviceType type) { - switch (type) { - case DEVICE_TYPE_I2C: - return tt::hal::Device::Type::I2c; - case DEVICE_TYPE_DISPLAY: - return tt::hal::Device::Type::Display; - case DEVICE_TYPE_TOUCH: - return tt::hal::Device::Type::Touch; - case DEVICE_TYPE_SDCARD: - return tt::hal::Device::Type::SdCard; - case DEVICE_TYPE_KEYBOARD: - return tt::hal::Device::Type::Keyboard; - case DEVICE_TYPE_POWER: - return tt::hal::Device::Type::Power; - case DEVICE_TYPE_GPS: - return tt::hal::Device::Type::Gps; - default: - check(false, "Device::Type not supported"); - } -} - -extern "C" { - -bool tt_hal_device_find(TtDeviceType type, DeviceId* deviceIds, uint16_t* count, uint16_t maxCount) { - assert(maxCount > 0); - - int16_t currentIndex = -1; - uint16_t maxIndex = maxCount - 1; - - findDevices(toTactilityDeviceType(type), [&](const auto& device) { - currentIndex++; - deviceIds[currentIndex] = device->getId(); - // Continue if there is storage capacity left - return currentIndex < maxIndex; - }); - - *count = currentIndex + 1; - - return currentIndex >= 0; -} - -} diff --git a/TactilityC/Source/tt_hal_display.cpp b/TactilityC/Source/tt_hal_display.cpp deleted file mode 100644 index 6eae3874..00000000 --- a/TactilityC/Source/tt_hal_display.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include "tt_hal_display.h" - -#include - -#include -#include -#include - -static ColorFormat toColorFormat(tt::hal::display::ColorFormat format) { - switch (format) { - case tt::hal::display::ColorFormat::Monochrome: - return COLOR_FORMAT_MONOCHROME; - case tt::hal::display::ColorFormat::BGR565: - return COLOR_FORMAT_BGR565; - case tt::hal::display::ColorFormat::BGR565Swapped: - return COLOR_FORMAT_BGR565_SWAPPED; - case tt::hal::display::ColorFormat::RGB565: - return COLOR_FORMAT_RGB565; - case tt::hal::display::ColorFormat::RGB565Swapped: - return COLOR_FORMAT_RGB565_SWAPPED; - case tt::hal::display::ColorFormat::RGB888: - return COLOR_FORMAT_RGB888; - default: - check(false, "ColorFormat not supported"); - } -} - -struct DriverWrapper { - std::shared_ptr driver; - DriverWrapper(std::shared_ptr driver) : driver(driver) {} -}; - -static std::shared_ptr findValidDisplayDevice(tt::hal::Device::Id id) { - auto device = tt::hal::findDevice(id); - if (device == nullptr || device->getType() != tt::hal::Device::Type::Display) { - return nullptr; - } - return std::reinterpret_pointer_cast(device); -} - -extern "C" { - -bool tt_hal_display_driver_supported(DeviceId id) { - auto display = findValidDisplayDevice(id); - return display != nullptr && display->supportsDisplayDriver(); -} - -DisplayDriverHandle tt_hal_display_driver_alloc(DeviceId id) { - auto display = findValidDisplayDevice(id); - assert(display->supportsDisplayDriver()); - return new DriverWrapper(display->getDisplayDriver()); -} - -void tt_hal_display_driver_free(DisplayDriverHandle handle) { - auto wrapper = static_cast(handle); - delete wrapper; -} - -bool tt_hal_display_driver_lock(DisplayDriverHandle handle, TickType_t timeout) { - // TODO: re-implement with SPI lock - return true; -} - -void tt_hal_display_driver_unlock(DisplayDriverHandle handle) { - // TODO: re-implement with SPI lock -} - -ColorFormat tt_hal_display_driver_get_colorformat(DisplayDriverHandle handle) { - auto wrapper = static_cast(handle); - return toColorFormat(wrapper->driver->getColorFormat()); -} - -uint16_t tt_hal_display_driver_get_pixel_width(DisplayDriverHandle handle) { - auto wrapper = static_cast(handle); - return wrapper->driver->getPixelWidth(); -} - -uint16_t tt_hal_display_driver_get_pixel_height(DisplayDriverHandle handle) { - auto wrapper = static_cast(handle); - return wrapper->driver->getPixelHeight(); -} - -void tt_hal_display_driver_draw_bitmap(DisplayDriverHandle handle, int xStart, int yStart, int xEnd, int yEnd, const void* pixelData) { - auto wrapper = static_cast(handle); - wrapper->driver->drawBitmap(xStart, yStart, xEnd, yEnd, pixelData); -} - -uint8_t tt_hal_display_driver_get_frame_buffers(DisplayDriverHandle handle, void* outBuffers[2]) { - auto wrapper = static_cast(handle); - return wrapper->driver->getFrameBuffers(outBuffers); -} - -} \ No newline at end of file diff --git a/TactilityC/Source/tt_hal_touch.cpp b/TactilityC/Source/tt_hal_touch.cpp deleted file mode 100644 index 9572275d..00000000 --- a/TactilityC/Source/tt_hal_touch.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "tt_hal_touch.h" - -#include -#include "Tactility/hal/touch/TouchDevice.h" -#include "Tactility/hal/touch/TouchDriver.h" - -struct DriverWrapper { - std::shared_ptr driver; - DriverWrapper(std::shared_ptr driver) : driver(driver) {} -}; - -static std::shared_ptr findValidTouchDevice(tt::hal::Device::Id id) { - auto device = tt::hal::findDevice(id); - if (device == nullptr || device->getType() != tt::hal::Device::Type::Touch) { - return nullptr; - } - return std::reinterpret_pointer_cast(device); -} - -extern "C" { - -bool tt_hal_touch_driver_supported(DeviceId id) { - auto touch = findValidTouchDevice(id); - return touch != nullptr && touch->supportsTouchDriver(); -} - -TouchDriverHandle tt_hal_touch_driver_alloc(DeviceId id) { - auto touch = findValidTouchDevice(id); - assert(touch->supportsTouchDriver()); - return new DriverWrapper(touch->getTouchDriver()); -} - -void tt_hal_touch_driver_free(TouchDriverHandle handle) { - DriverWrapper* wrapper = static_cast(handle); - delete wrapper; -} - -bool tt_hal_touch_driver_get_touched_points(TouchDriverHandle handle, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* pointCount, uint8_t maxPointCount) { - DriverWrapper* wrapper = static_cast(handle); - return wrapper->driver->getTouchedPoints(x, y, strength, pointCount, maxPointCount); -} - -} diff --git a/TactilityC/Source/tt_hal_uart.cpp b/TactilityC/Source/tt_hal_uart.cpp deleted file mode 100644 index 13a988fd..00000000 --- a/TactilityC/Source/tt_hal_uart.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "tt_hal_uart.h" - -#include - -extern "C" { - -size_t tt_hal_uart_get_count() { - return 0; -} - -bool tt_hal_uart_get_name(size_t index, char* name, size_t nameSizeLimit) { - return false; -} - -UartHandle tt_hal_uart_alloc(size_t index) { - return nullptr; -} - -void tt_hal_uart_free(UartHandle handle) { -} - -bool tt_hal_uart_start(UartHandle handle) { - return false; -} - -bool tt_hal_uart_is_started(UartHandle handle) { - return false; -} - -bool tt_hal_uart_stop(UartHandle handle) { - return false; -} - -size_t tt_hal_uart_read_bytes(UartHandle handle, char* buffer, size_t bufferSize, TickType_t timeout) { - return 0; -} - -bool tt_hal_uart_read_byte(UartHandle handle, char* output, TickType_t timeout) { - return false; -} - -size_t tt_hal_uart_write_bytes(UartHandle handle, const char* buffer, size_t bufferSize, TickType_t timeout) { - return 0; -} - -size_t tt_hal_uart_available(UartHandle handle) { - return 0; -} - -bool tt_hal_uart_set_baud_rate(UartHandle handle, size_t baud_rate) { - return false; -} - -uint32_t tt_hal_uart_get_baud_rate(UartHandle handle) { - return 0; -} - -void tt_hal_uart_flush_input(UartHandle handle) { -} - -} diff --git a/TactilityC/Source/tt_init.cpp b/TactilityC/Source/tt_init.cpp index 6e9554dc..24326454 100644 --- a/TactilityC/Source/tt_init.cpp +++ b/TactilityC/Source/tt_init.cpp @@ -5,13 +5,7 @@ #include "tt_app_fileselection.h" #include "tt_app_selectiondialog.h" #include "tt_bundle.h" -#include "tt_gps.h" -#include "tt_hal_device.h" -#include "tt_hal_display.h" -#include "tt_hal_touch.h" -#include "tt_hal_uart.h" #include -#include "tt_lvgl.h" #include "tt_lvgl_keyboard.h" #include "tt_lvgl_spinner.h" #include "tt_lvgl_toolbar.h" @@ -299,42 +293,6 @@ const esp_elfsym main_symbols[] { ESP_ELFSYM_EXPORT(tt_bundle_put_bool), ESP_ELFSYM_EXPORT(tt_bundle_put_int32), ESP_ELFSYM_EXPORT(tt_bundle_put_string), - ESP_ELFSYM_EXPORT(tt_gps_has_coordinates), - ESP_ELFSYM_EXPORT(tt_gps_get_coordinates), - ESP_ELFSYM_EXPORT(tt_hal_device_find), - ESP_ELFSYM_EXPORT(tt_hal_display_driver_alloc), - ESP_ELFSYM_EXPORT(tt_hal_display_driver_draw_bitmap), - ESP_ELFSYM_EXPORT(tt_hal_display_driver_free), - ESP_ELFSYM_EXPORT(tt_hal_display_driver_get_colorformat), - ESP_ELFSYM_EXPORT(tt_hal_display_driver_get_pixel_height), - ESP_ELFSYM_EXPORT(tt_hal_display_driver_get_pixel_width), - ESP_ELFSYM_EXPORT(tt_hal_display_driver_lock), - ESP_ELFSYM_EXPORT(tt_hal_display_driver_unlock), - ESP_ELFSYM_EXPORT(tt_hal_display_driver_supported), - ESP_ELFSYM_EXPORT(tt_hal_display_driver_get_frame_buffers), - ESP_ELFSYM_EXPORT(tt_hal_touch_driver_supported), - ESP_ELFSYM_EXPORT(tt_hal_touch_driver_alloc), - ESP_ELFSYM_EXPORT(tt_hal_touch_driver_free), - ESP_ELFSYM_EXPORT(tt_hal_touch_driver_get_touched_points), - ESP_ELFSYM_EXPORT(tt_hal_uart_get_count), - ESP_ELFSYM_EXPORT(tt_hal_uart_get_name), - ESP_ELFSYM_EXPORT(tt_hal_uart_alloc), - ESP_ELFSYM_EXPORT(tt_hal_uart_free), - ESP_ELFSYM_EXPORT(tt_hal_uart_start), - ESP_ELFSYM_EXPORT(tt_hal_uart_is_started), - ESP_ELFSYM_EXPORT(tt_hal_uart_stop), - ESP_ELFSYM_EXPORT(tt_hal_uart_read_bytes), - ESP_ELFSYM_EXPORT(tt_hal_uart_read_byte), - ESP_ELFSYM_EXPORT(tt_hal_uart_write_bytes), - ESP_ELFSYM_EXPORT(tt_hal_uart_available), - ESP_ELFSYM_EXPORT(tt_hal_uart_set_baud_rate), - ESP_ELFSYM_EXPORT(tt_hal_uart_get_baud_rate), - ESP_ELFSYM_EXPORT(tt_hal_uart_flush_input), - ESP_ELFSYM_EXPORT(tt_lvgl_is_started), - ESP_ELFSYM_EXPORT(tt_lvgl_lock), - ESP_ELFSYM_EXPORT(tt_lvgl_unlock), - ESP_ELFSYM_EXPORT(tt_lvgl_start), - ESP_ELFSYM_EXPORT(tt_lvgl_stop), ESP_ELFSYM_EXPORT(tt_lvgl_software_keyboard_show), ESP_ELFSYM_EXPORT(tt_lvgl_software_keyboard_hide), ESP_ELFSYM_EXPORT(tt_lvgl_software_keyboard_is_enabled), diff --git a/TactilityC/Source/tt_lvgl.cpp b/TactilityC/Source/tt_lvgl.cpp deleted file mode 100644 index 261d3eb5..00000000 --- a/TactilityC/Source/tt_lvgl.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include -#include - -extern "C" { - -bool tt_lvgl_is_started() { - return tt::lvgl::isStarted(); -} - -void tt_lvgl_start() { - tt::lvgl::start(); -} - -void tt_lvgl_stop() { - tt::lvgl::stop(); -} - -bool tt_lvgl_lock(TickType_t timeout) { - return tt::lvgl::getSyncLock()->lock(timeout); -} - -void tt_lvgl_unlock() { - tt::lvgl::getSyncLock()->unlock(); -} - -} \ No newline at end of file diff --git a/TactilityKernel/include/tactility/device.h b/TactilityKernel/include/tactility/device.h index 78eea564..ee46d731 100644 --- a/TactilityKernel/include/tactility/device.h +++ b/TactilityKernel/include/tactility/device.h @@ -24,6 +24,19 @@ struct DeviceType { const char* name; }; +typedef uint8_t device_flags_t; + +#ifndef BIT +#define BIT(nr) (1u << (nr)) +#endif + +#define DEVICE_FLAG_DTS BIT(0) /* Instantiated from a dts file */ +#define DEVICE_FLAG_DYNAMIC BIT(1) /* 1 means dynamically allocated */ + +#define DEVICE_FLAG_VIRTUAL BIT(2) /* No physical hardware */ +#define DEVICE_FLAG_REMOVABLE BIT(3) /* May disappear (USB, SDIO, etc.) */ +#define DEVICE_FLAG_HOTPLUG BIT(4) /* Supports hotplug */ + /** Represents a piece of hardware */ struct Device { /** Device address. Can represent an index, a memory address, or some kind of offset */ @@ -38,6 +51,8 @@ struct Device { /** The parent device that this device belongs to. Can be NULL, but only the root device should have a NULL parent. */ struct Device* parent; + device_flags_t flags; + /** * Internal state managed by the kernel. * Device implementers should initialize this to NULL. @@ -388,10 +403,19 @@ error_t device_get_first_by_type(const struct DeviceType* type, struct Device** * @param[in] type non-null device type pointer * @param[out] out_device receives the found device on success; untouched on failure * @retval ERROR_NOT_FOUND if no started device of that type exists - * @retval ERROR_NONE on success; caller must call device_put(*out_device) exactly once + * @retval ERROR_NONE if a started device of that type exists; must call device_put() exactly once afterwards. */ error_t device_get_first_active_by_type(const struct DeviceType* type, struct Device** out_device); +/** + * Check if there is an active device of the provided type. + * + * @param[in] type non-null device type pointer + * @retval ERROR_NOT_FOUND if no started device of that type exists + * @retval ERROR_NONE if a started device of that type exists + */ +bool device_has_active_by_type(const struct DeviceType* type); + /** * Find the first device whose driver matches the given compatible string and atomically take a * reference on it. See device_get_by_name() for why this is preferred over diff --git a/TactilityKernel/include/tactility/drivers/keyboard.h b/TactilityKernel/include/tactility/drivers/keyboard.h index d7ea8699..d902d362 100644 --- a/TactilityKernel/include/tactility/drivers/keyboard.h +++ b/TactilityKernel/include/tactility/drivers/keyboard.h @@ -37,6 +37,16 @@ struct KeyboardApi { * @retval ERROR_NONE when the operation was successful */ error_t (*read_key)(struct Device* device, struct KeyboardKeyData* data); + + /** + * @brief Returns the baclight if the keyboard has one. + * @warning Returns a referenced device. Must call device_put() afterwards. + * @param[in] device the keyboard device + * @param[out] backlight_device the output backlight device + * @retval ERROR_NONE when the backlight_device was set + * @retval ERROR_NOT_SUPPORTED when this device has no backlight + */ + error_t (*get_backlight)(struct Device* device, struct Device** backlight_device); }; /** @@ -44,6 +54,17 @@ struct KeyboardApi { */ error_t keyboard_read_key(struct Device* device, struct KeyboardKeyData* data); +/** + * @brief Returns the backlight if the keyboard has one. + * @warning Returns a referenced device. Must call device_put() afterwards. + * @param[in] device the keyboard device + * @param[out] backlight_device the output backlight device + * @retval ERROR_NONE when the backlight_device was set + * @retval ERROR_NOT_SUPPORTED when this device has no backlight + */ +error_t keyboard_get_backlight(struct Device* device, struct Device** backlight_device); + + extern const struct DeviceType KEYBOARD_TYPE; #ifdef __cplusplus diff --git a/TactilityKernel/include/tactility/filesystem/file_mutex.h b/TactilityKernel/include/tactility/filesystem/file_mutex.h new file mode 100644 index 00000000..cec15187 --- /dev/null +++ b/TactilityKernel/include/tactility/filesystem/file_mutex.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Set of lock/try_lock/unlock callbacks backing a filesystem mount's mutex. + * Any field left null is treated as a no-op by file_mutex_lock/try_lock/unlock. + */ +struct FileMutex { + void (*lock)(); + bool (*try_lock)(uint32_t timeout); + void (*unlock)(); +}; + +/** + * @brief Registers a mutex for a mount path (e.g. "/sdcard") and its descendants. + * @param[in] mutex callbacks to associate with the path; a copy is stored + * @param[in] path mount path this mutex serializes access to + * @note No-op if a mutex is already registered for this exact path. + */ +void file_mutex_register(const struct FileMutex* mutex, const char* path); + +/** + * @brief Looks up the mutex registered for path or one of its ancestor mount paths. + * @param[out] mutex receives the matching mutex, or an all-null (no-op) mutex if none matches + * @param[in] path file or directory path to look up + */ +void file_mutex_get(struct FileMutex* mutex, const char* path); + +/** @brief Locks mutex. No-op if mutex->lock is null. */ +void file_mutex_lock(const struct FileMutex* mutex); + +/** + * @brief Attempts to lock mutex within timeout. + * @return true if locked (or mutex->try_lock is null), false on timeout + */ +bool file_mutex_try_lock(const struct FileMutex* mutex, TickType_t timeout); + +/** @brief Unlocks mutex. No-op if mutex->unlock is null. */ +void file_mutex_unlock(const struct FileMutex* mutex); + +#ifdef __cplusplus +} +#endif diff --git a/TactilityKernel/include/tactility/module.h b/TactilityKernel/include/tactility/module.h index 339ae7f5..5bd03c56 100644 --- a/TactilityKernel/include/tactility/module.h +++ b/TactilityKernel/include/tactility/module.h @@ -122,6 +122,22 @@ error_t module_stop(struct Module* module); */ error_t module_construct_add_start(struct Module* module); +/** + * @brief Tries to ensure the module is in a started state. + * Calls module_construct if needed, calls module_start if needed. + * @param module the module + * @return ERROR_NONE if module is in a started state + */ +error_t module_ensure_started(struct Module* module); + +/** + * @brief Tries to ensure the module is in a started state. + * Calls module_stop if needed, calls module_destruct if needed. + * @param module the module + * @return ERROR_NONE if module is in a destructed state + */ +error_t module_ensure_destructed(struct Module* module); + /** * @brief Check if the module is started. * Can be used when module isn't constructed yet. diff --git a/TactilityKernel/source/device.cpp b/TactilityKernel/source/device.cpp index 8f2aba03..eaa6c67a 100644 --- a/TactilityKernel/source/device.cpp +++ b/TactilityKernel/source/device.cpp @@ -585,6 +585,20 @@ error_t device_get_first_active_by_type(const DeviceType* type, Device** out_dev return error; } +bool device_has_active_by_type(const struct DeviceType* type) { + ledger_lock(); + bool found = false; + for (auto* device : ledger.devices) { + auto* driver = device->internal->driver; + if (driver != nullptr && driver->device_type == type && device->internal->state.started) { + found = true; + break; + } + } + ledger_unlock(); + return found; +} + error_t device_get_first_by_compatible(const char* compatible, Device** out_device) { ledger_lock(); Device* found = nullptr; diff --git a/TactilityKernel/source/drivers/keyboard.cpp b/TactilityKernel/source/drivers/keyboard.cpp index 30759027..34c2b101 100644 --- a/TactilityKernel/source/drivers/keyboard.cpp +++ b/TactilityKernel/source/drivers/keyboard.cpp @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include #include #define KEYBOARD_DRIVER_API(driver) ((struct KeyboardApi*)driver->api) @@ -11,6 +12,16 @@ error_t keyboard_read_key(Device* device, KeyboardKeyData* data) { return KEYBOARD_DRIVER_API(driver)->read_key(device, data); } +error_t keyboard_get_backlight(Device* device, Device** backlight_device) { + const auto* driver = device_get_driver(device); + + if (KEYBOARD_DRIVER_API(driver)->get_backlight == nullptr) { + return ERROR_NOT_SUPPORTED; + } + + return KEYBOARD_DRIVER_API(driver)->get_backlight(device, backlight_device); +} + const DeviceType KEYBOARD_TYPE { .name = "keyboard" }; diff --git a/TactilityKernel/source/filesystem/file_mutex.cpp b/TactilityKernel/source/filesystem/file_mutex.cpp new file mode 100644 index 00000000..a8bbc9f9 --- /dev/null +++ b/TactilityKernel/source/filesystem/file_mutex.cpp @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include +#include +#include + +static const FileMutex no_mutex = { + .lock = nullptr, + .try_lock = nullptr, + .unlock = nullptr, +}; + +struct FileMutexEntry { + std::string path; + FileMutex mutex; +}; + +static std::vector mutex_entries; + +extern "C" { + +void file_mutex_register(const FileMutex* mutex, const char* path) { + // Skip if entry for path exists + for (auto& entry : mutex_entries) { + if (entry.path == path) { + return; + } + } + + // Store a copy of the entry + mutex_entries.push_back({ + .path = path, + .mutex = *mutex + }); +} + +void file_mutex_get(FileMutex* mutex, const char* path) { + std::string path_string = path; + for (auto& entry : mutex_entries) { + // Match the mount path itself, or a descendant (e.g. "/sdcard" registered, "/sdcard/config.json" requested). + bool is_match = path_string == entry.path || + (entry.path == "/" && !path_string.empty() && path_string[0] == '/') || + (path_string.rfind(entry.path, 0) == 0 && path_string[entry.path.size()] == '/'); + if (is_match) { + memcpy(mutex, &entry.mutex, sizeof(FileMutex)); + return; + } + } + + *mutex = no_mutex; +} + +void file_mutex_lock(const FileMutex* mutex) { + if (mutex->lock) { + mutex->lock(); + } +} + +bool file_mutex_try_lock(const FileMutex* mutex, TickType_t timeout) { + if (mutex->try_lock) { + return mutex->try_lock(timeout); + } + return true; +} + +void file_mutex_unlock(const FileMutex* mutex) { + if (mutex->unlock) { + mutex->unlock(); + } +} + +} diff --git a/TactilityKernel/source/module.cpp b/TactilityKernel/source/module.cpp index 7f2bf741..cd006053 100644 --- a/TactilityKernel/source/module.cpp +++ b/TactilityKernel/source/module.cpp @@ -28,22 +28,39 @@ static ModuleLedger ledger; extern "C" { error_t module_construct(Module* module) { + if (module->internal != nullptr) { + LOG_E(TAG, "Module %s was already constructed", module->name); + return ERROR_INVALID_STATE; + } module->internal = new (std::nothrow) ModuleInternal(); if (module->internal == nullptr) return ERROR_OUT_OF_MEMORY; return ERROR_NONE; } error_t module_destruct(Module* module) { - delete static_cast(module->internal); + if (module->internal == nullptr) { + LOG_E(TAG, "Module %s was already destructed", module->name); + return ERROR_INVALID_STATE; + } + delete module->internal; module->internal = nullptr; return ERROR_NONE; } error_t module_add(Module* module) { mutex_lock(&ledger.mutex); - ledger.modules.push_back(module); + bool exists = false; + for (auto* ledger_module : ledger.modules) { + if (ledger_module == module) { + exists = true; + break; + } + } + if (!exists) { + ledger.modules.push_back(module); + } mutex_unlock(&ledger.mutex); - return ERROR_NONE; + return exists ? ERROR_INVALID_STATE : ERROR_NONE; } error_t module_remove(Module* module) { @@ -57,8 +74,8 @@ error_t module_start(Module* module) { LOG_I(TAG, "start %s", module->name); auto* internal = module->internal; - if (internal == nullptr) return ERROR_INVALID_STATE; - if (internal->started) return ERROR_NONE; + if (internal == nullptr) { return ERROR_INVALID_STATE; } + if (internal->started) { return ERROR_NONE; } if (module->start != nullptr) { auto error = module->start(); @@ -90,8 +107,8 @@ error_t module_stop(Module* module) { LOG_I(TAG, "stop %s", module->name); auto* internal = module->internal; - if (internal == nullptr) return ERROR_INVALID_STATE; - if (!internal->started) return ERROR_NONE; + if (internal == nullptr) { return ERROR_INVALID_STATE; } + if (!internal->started) { return ERROR_NONE; } if (module->drivers != nullptr && internal->drivers_ready) { size_t count = 0; @@ -117,12 +134,41 @@ error_t module_stop(Module* module) { error_t module_construct_add_start(Module* module) { error_t error = module_construct(module); - if (error != ERROR_NONE) return error; + if (error != ERROR_NONE) { return error; } error = module_add(module); - if (error != ERROR_NONE) return error; + if (error != ERROR_NONE) { return error; } return module_start(module); } +error_t module_ensure_started(Module* module) { + if (module->internal == nullptr) { + error_t result = module_construct(module); + if (result != ERROR_NONE) { return result; } + } + + if (!module->internal->started) { + error_t result = module_start(module); + if (result != ERROR_NONE) { return result; } + } + + return ERROR_NONE; +} + +error_t module_ensure_destructed(Module* module) { + if (module->internal != nullptr) { + error_t result; + if (module->internal->started) { + result = module_stop(module); + if (result != ERROR_NONE) { return result; } + } + + result = module_destruct(module); + if (result != ERROR_NONE) { return result; } + } + + return ERROR_NONE; +} + bool module_resolve_symbol(Module* module, const char* symbol_name, uintptr_t* symbol_address) { if (!module_is_started(module)) return false; auto* symbol_ptr = module->symbols; diff --git a/Tests/Tactility/CMakeLists.txt b/Tests/Tactility/CMakeLists.txt index c31fd46e..2681a2ba 100644 --- a/Tests/Tactility/CMakeLists.txt +++ b/Tests/Tactility/CMakeLists.txt @@ -14,9 +14,10 @@ target_link_libraries(TactilityTests PRIVATE Tactility TactilityKernel platform-posix - hal-device-module lvgl-module crypt-module + gps-module + gps-generic-module lvgl SDL2::SDL2-static SDL2-static ) diff --git a/Tests/Tactility/Source/HalDeviceTest.cpp b/Tests/Tactility/Source/HalDeviceTest.cpp deleted file mode 100644 index 015323d2..00000000 --- a/Tests/Tactility/Source/HalDeviceTest.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "doctest.h" -#include - -#include - -using namespace tt; - -class TestDevice final : public hal::Device { - -private: - - hal::Device::Type type; - std::string name; - std::string description; - -public: - - TestDevice(hal::Device::Type type, std::string name, std::string description) : - type(type), - name(std::move(name)), - description(std::move(description)) - {} - - TestDevice() : TestDevice(hal::Device::Type::Power, "PowerMock", "PowerMock description") {} - - ~TestDevice() final = default; - - Type getType() const final { return type; } - std::string getName() const final { return name; } - std::string getDescription() const final { return description; } -}; - -class DeviceAutoRegistration { - - std::shared_ptr device; - -public: - - explicit DeviceAutoRegistration(std::shared_ptr inDevice) : device(std::move(inDevice)) { - hal::registerDevice(device); - } - - ~DeviceAutoRegistration() { - hal::deregisterDevice(device); - } -}; - -/** We add 3 tests into 1 to ensure cleanup happens */ -TEST_CASE("registering and deregistering a device works") { - auto device = std::make_shared(); - - // Pre-registration - CHECK_EQ(hal::findDevice(device->getId()), nullptr); - - // Registration - hal::registerDevice(device); - auto found_device = hal::findDevice(device->getId()); - CHECK_NE(found_device, nullptr); - CHECK_EQ(found_device->getId(), device->getId()); - - // Deregistration - hal::deregisterDevice(device); - CHECK_EQ(hal::findDevice(device->getId()), nullptr); - found_device = nullptr; // to decrease use count - CHECK_EQ(device.use_count(), 1); -} - -TEST_CASE("find device by id") { - auto device = std::make_shared(); - DeviceAutoRegistration auto_registration(device); - - auto found_device = hal::findDevice(device->getId()); - CHECK_NE(found_device, nullptr); - CHECK_EQ(found_device->getId(), device->getId()); -} - -TEST_CASE("find device by name") { - auto device = std::make_shared(); - DeviceAutoRegistration auto_registration(device); - - auto found_device = hal::findDevice(device->getName()); - CHECK_NE(found_device, nullptr); - CHECK_EQ(found_device->getId(), device->getId()); -} - -TEST_CASE("find device by type") { - // Headless mode shouldn't have a display, so we want to create one to find only our own display as unique device - // We first verify the initial assumption that there is no display: - auto unexpected_display = hal::findFirstDevice(hal::Device::Type::Display); - CHECK_EQ(unexpected_display, nullptr); - - auto device = std::make_shared(hal::Device::Type::Display, "DisplayMock", ""); - DeviceAutoRegistration auto_registration(device); - - auto found_device = hal::findFirstDevice(hal::Device::Type::Display); - CHECK_NE(found_device, nullptr); - CHECK_EQ(found_device->getId(), device->getId()); -} diff --git a/Tests/Tactility/Source/Main.cpp b/Tests/Tactility/Source/Main.cpp index 6a4d923f..17931e2b 100644 --- a/Tests/Tactility/Source/Main.cpp +++ b/Tests/Tactility/Source/Main.cpp @@ -7,7 +7,6 @@ #include #include -#include #include typedef struct { @@ -29,7 +28,7 @@ void test_task(void* parameter) { // overrides context.setOption("no-breaks", true); // don't break in the debugger when assertions fail - Module* dts_modules[] = { &platform_posix_module, &hal_device_module, nullptr }; + Module* dts_modules[] = { &platform_posix_module, nullptr }; DtsDevice dts_devices[] = { DTS_DEVICE_TERMINATOR }; check(kernel_init(dts_modules, dts_devices) == ERROR_NONE);