diff --git a/CMakeLists.txt b/CMakeLists.txt index bce5ea474..e558fec3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,9 +96,9 @@ 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(Drivers/gps-module) # FreeRTOS set(FREERTOS_CONFIG_FILE_DIRECTORY ${PROJECT_SOURCE_DIR}/Devices/simulator/Source CACHE STRING "") diff --git a/Devices/lilygo-tdeck-plus/source/module.cpp b/Devices/lilygo-tdeck-plus/source/module.cpp index 6caf5ef67..f03d41e5b 100644 --- a/Devices/lilygo-tdeck-plus/source/module.cpp +++ b/Devices/lilygo-tdeck-plus/source/module.cpp @@ -1,74 +1,79 @@ -#include #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 = 0; + +void init_gps_configuration() { + bool has_configuration = false; + gps_service_for_each_configuration(&has_configuration, [](const GpsConfiguration*, size_t, void* context) { + *static_cast(context) = true; }); - // 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); + if (!has_configuration) { + GpsConfiguration configuration = { .uart_name = "uart0", .baud_rate = 38400, .model = GpsModel::GPS_MODEL_UBLOX10 }; + if (gps_service_add_configuration(&configuration) == ERROR_NONE) { + LOG_I(TAG, "Configured internal GPS"); + } else { + LOG_E(TAG, "Failed to configure internal GPS"); } - 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_gps_configuration(); + init_trackball(); + }); return ERROR_NONE; } static error_t stop() { + tt::kernel::unsubscribeSystemEvent(tdeck_boot_splash_subscription); + tdeck_boot_splash_subscription = 0; return ERROR_NONE; } @@ -76,8 +81,6 @@ Module lilygo_tdeck_plus_module = { .name = "lilygo-tdeck-plus", .start = start, .stop = stop, - .symbols = nullptr, - .internal = nullptr }; } diff --git a/Devices/simulator/Source/Main.cpp b/Devices/simulator/Source/Main.cpp index 464607a29..85e834eab 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 28ad34b1b..000000000 --- a/Devices/simulator/Source/Simulator.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "hal/SdlDisplay.h" -#include "hal/SdlKeyboard.h" -#include "hal/SimulatorPower.h" - - -#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 000000000..ca39fbca7 --- /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/Modules/hal-device-module/include/tactility/hal_device_module.h b/Devices/simulator/Source/drivers/sdl_display.h similarity index 50% rename from Modules/hal-device-module/include/tactility/hal_device_module.h rename to Devices/simulator/Source/drivers/sdl_display.h index 8efca3909..9e91c20a8 100644 --- a/Modules/hal-device-module/include/tactility/hal_device_module.h +++ b/Devices/simulator/Source/drivers/sdl_display.h @@ -5,7 +5,12 @@ extern "C" { #endif -extern struct Module hal_device_module; +#include + +struct SdlDisplayConfig { + uint16_t horizontal_resolution; + uint16_t vertical_resolution; +}; #ifdef __cplusplus } diff --git a/Devices/simulator/Source/drivers/sdl_input.cpp b/Devices/simulator/Source/drivers/sdl_input.cpp new file mode 100644 index 000000000..86aea8c81 --- /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 000000000..8e7158efa --- /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 000000000..09be781c0 --- /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 000000000..33b48bc14 --- /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 99f8aa657..000000000 --- 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 876bf70d3..000000000 --- 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 09ebc757c..000000000 --- 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 84450bc07..000000000 --- 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 f2b6bc919..000000000 --- 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 3d1008a83..393ba5d49 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 2ff6ed2a7..172d58124 100644 --- a/Documentation/README.md +++ b/Documentation/README.md @@ -77,7 +77,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 +100,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 +163,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 f418b7563..ae2d7b0d3 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-module/CMakeLists.txt b/Drivers/gps-module/CMakeLists.txt new file mode 100644 index 000000000..d7fc4f41a --- /dev/null +++ b/Drivers/gps-module/CMakeLists.txt @@ -0,0 +1,11 @@ +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} + INCLUDE_DIRS include/ + REQUIRES TactilityKernel minmea +) diff --git a/Drivers/gps-module/devicetree.yaml b/Drivers/gps-module/devicetree.yaml new file mode 100644 index 000000000..6bbb24367 --- /dev/null +++ b/Drivers/gps-module/devicetree.yaml @@ -0,0 +1,2 @@ +dependencies: + - TactilityKernel diff --git a/Drivers/gps-module/include/tactility/drivers/gps.h b/Drivers/gps-module/include/tactility/drivers/gps.h new file mode 100644 index 000000000..e1950e778 --- /dev/null +++ b/Drivers/gps-module/include/tactility/drivers/gps.h @@ -0,0 +1,112 @@ +// 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, 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, +}; + +/** + * @brief Configuration for a GPS_TYPE device. + * @warning Set device_set_parent() to the UART_CONTROLLER_TYPE device this receiver is wired to + * before starting - the driver reads/writes through its parent. + */ +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; +}; + +/** + * @brief API for GPS/GNSS receiver drivers. + */ +struct GpsApi { + /** + * @brief Gets the most recently parsed RMC (position/velocity/time) sentence. + * @param[in] device the GPS device + * @param[out] out the parsed sentence + * @param[in] max_age the maximum acceptable age of the cached sentence + * @retval ERROR_NONE when a sentence younger than max_age was copied into out + * @retval ERROR_NOT_FOUND when no RMC sentence has ever been parsed + * @retval ERROR_TIMEOUT when the cached sentence is older than max_age + */ + error_t (*get_rmc)(struct Device* device, struct minmea_sentence_rmc* out, TickType_t max_age); + + /** + * @brief Gets the most recently parsed GGA (fix data) sentence. + * @see GpsApi::get_rmc + */ + error_t (*get_gga)(struct Device* device, struct minmea_sentence_gga* out, TickType_t max_age); + + /** + * @brief Gets the model in use - the autodetected model when configured with GPS_MODEL_UNKNOWN. + * @param[in] device the GPS device + */ + enum GpsModel (*get_model)(struct Device* device); + + /** + * @brief Gets the current lifecycle state. + * @param[in] device the GPS device + */ + enum GpsState (*get_state)(struct Device* device); +}; + +/** @copydoc GpsApi::get_rmc */ +error_t gps_get_rmc(struct Device* device, struct minmea_sentence_rmc* out, TickType_t max_age); + +/** @copydoc GpsApi::get_gga */ +error_t gps_get_gga(struct Device* device, struct minmea_sentence_gga* out, TickType_t max_age); + +/** @copydoc GpsApi::get_model */ +enum GpsModel gps_get_model(struct Device* device); + +/** @copydoc GpsApi::get_state */ +enum GpsState gps_get_state(struct Device* device); + +extern const struct DeviceType GPS_TYPE; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/gps-module/include/tactility/gps_service.h b/Drivers/gps-module/include/tactility/gps_service.h new file mode 100644 index 000000000..1c23f5843 --- /dev/null +++ b/Drivers/gps-module/include/tactility/gps_service.h @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include +#include + +#include + +struct Module; + +#define GPS_SERVICE_ID "gps" + +/** + * @brief Aggregate receive state of the GPS service (across all configured receivers). + */ +enum GpsServiceState { + GPS_SERVICE_STATE_ON_PENDING, + GPS_SERVICE_STATE_ON, + GPS_SERVICE_STATE_OFF_PENDING, + GPS_SERVICE_STATE_OFF, +}; + +/** + * @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. + * @retval ERROR_RESOURCE if the configuration file could not be opened/written + */ +error_t gps_service_add_configuration(const struct GpsConfiguration* configuration); + +/** + * @brief Removes a persisted GPS configuration that matches by value. + * @retval ERROR_NOT_FOUND if no matching configuration was found + * @retval ERROR_RESOURCE if the configuration file could not be read/written + */ +error_t gps_service_remove_configuration(const struct GpsConfiguration* configuration); + +/** + * @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_service_for_each_configuration(void* context, void (*on_configuration)(const struct GpsConfiguration* configuration, size_t index, void* context)); + +/** + * @brief Iterates over the GPS_TYPE devices currently constructed by gps_service_start_receiving(). + */ +void gps_service_for_each_device(void* context, void (*on_device)(struct Device* device, void* context)); + +/** + * @brief Constructs and starts a GPS_TYPE device for every persisted configuration and begins receiving. + * @retval ERROR_INVALID_STATE if already receiving + * @retval ERROR_NOT_FOUND if there are no persisted configurations, or none of their UART devices could be found + */ +error_t gps_service_start_receiving(void); + +/** Stops and destroys every GPS_TYPE device constructed by gps_service_start_receiving(). */ +void gps_service_stop_receiving(void); + +enum GpsServiceState gps_service_get_state(void); + +/** @return true when a coordinate fix is available and is not older than 10 seconds */ +bool gps_service_has_coordinates(void); + +/** @copydoc gps_service_has_coordinates */ +bool gps_service_get_coordinates(struct minmea_sentence_rmc* out); + +/** @return true when GGA fix data is available and is not older than 10 seconds */ +bool gps_service_get_gga(struct minmea_sentence_gga* out); + +extern struct Module gps_module; + +#ifdef __cplusplus +} +#endif diff --git a/Tactility/Private/Tactility/hal/gps/Cas.h b/Drivers/gps-module/source/cas_messages.h similarity index 100% rename from Tactility/Private/Tactility/hal/gps/Cas.h rename to Drivers/gps-module/source/cas_messages.h diff --git a/Drivers/gps-module/source/gps.cpp b/Drivers/gps-module/source/gps.cpp new file mode 100644 index 000000000..926043d4e --- /dev/null +++ b/Drivers/gps-module/source/gps.cpp @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: Apache-2.0 +#include + +#include "init.h" +#include "probe.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +constexpr auto* TAG = "Gps"; +#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(10); + +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; + bool has_rmc; + minmea_sentence_rmc rmc; + TickType_t rmc_time; + bool has_gga; + minmea_sentence_gga gga; + TickType_t gga_time; +}; + +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)); + auto* uart = device_get_parent(device); + check(device_get_type(uart) == &UART_CONTROLLER_TYPE); + + const auto* config = GET_CONFIG(device); + + 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((char*)buffer, false)) { + case MINMEA_SENTENCE_RMC: { + minmea_sentence_rmc rmc_frame; + if (minmea_parse_rmc(&rmc_frame, (char*)buffer)) { + recursive_mutex_lock(&internal->mutex); + internal->has_rmc = true; + internal->rmc = rmc_frame; + internal->rmc_time = get_ticks(); + recursive_mutex_unlock(&internal->mutex); + } 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)) { + recursive_mutex_lock(&internal->mutex); + internal->has_gga = true; + internal->gga = gga_frame; + internal->gga_time = get_ticks(); + recursive_mutex_unlock(&internal->mutex); + } 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); + } + + set_state(internal, GpsState::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 = GpsState::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 = GpsState::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_W(TAG, "GPS thread for %s did not stop in time", device->name); + } + 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_get_rmc(Device* device, minmea_sentence_rmc* out, TickType_t max_age) { + auto* internal = static_cast(device_get_driver_data(device)); + + recursive_mutex_lock(&internal->mutex); + error_t result; + if (!internal->has_rmc) { + result = ERROR_NOT_FOUND; + } else if (get_ticks() - internal->rmc_time > max_age) { + result = ERROR_TIMEOUT; + } else { + *out = internal->rmc; + result = ERROR_NONE; + } + recursive_mutex_unlock(&internal->mutex); + return result; +} + +static error_t gps_api_get_gga(Device* device, minmea_sentence_gga* out, TickType_t max_age) { + auto* internal = static_cast(device_get_driver_data(device)); + + recursive_mutex_lock(&internal->mutex); + error_t result; + if (!internal->has_gga) { + result = ERROR_NOT_FOUND; + } else if (get_ticks() - internal->gga_time > max_age) { + result = ERROR_TIMEOUT; + } else { + *out = internal->gga; + result = ERROR_NONE; + } + recursive_mutex_unlock(&internal->mutex); + return result; +} + +static GpsModel gps_api_get_model(Device* device) { + auto* internal = static_cast(device_get_driver_data(device)); + recursive_mutex_lock(&internal->mutex); + auto model = internal->model; + recursive_mutex_unlock(&internal->mutex); + return model; +} + +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; +} + +// endregion + +const char* gps_model_to_string(enum 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_get_rmc(Device* device, minmea_sentence_rmc* out, TickType_t max_age) { + const auto* driver = device_get_driver(device); + return static_cast(driver->api)->get_rmc(device, out, max_age); +} + +error_t gps_get_gga(Device* device, minmea_sentence_gga* out, TickType_t max_age) { + const auto* driver = device_get_driver(device); + return static_cast(driver->api)->get_gga(device, out, max_age); +} + +enum GpsModel gps_get_model(Device* device) { + const auto* driver = device_get_driver(device); + return static_cast(driver->api)->get_model(device); +} + +enum GpsState gps_get_state(Device* device) { + const auto* driver = device_get_driver(device); + return static_cast(driver->api)->get_state(device); +} + +const DeviceType GPS_TYPE { + .name = "gps" +}; + +static const GpsApi gps_api = { + .get_rmc = gps_api_get_rmc, + .get_gga = gps_api_get_gga, + .get_model = gps_api_get_model, + .get_state = gps_api_get_state, +}; + +extern Module gps_module; + +Driver gps_driver = { + .name = "gps", + .compatible = (const char*[]) { "generic,gps", nullptr }, + .start_device = start, + .stop_device = stop, + .api = &gps_api, + .device_type = &GPS_TYPE, + .owner = &gps_module +}; diff --git a/Drivers/gps-module/source/gps_response.h b/Drivers/gps-module/source/gps_response.h new file mode 100644 index 000000000..13d0413a1 --- /dev/null +++ b/Drivers/gps-module/source/gps_response.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +// Internal-only result of waiting for a chip's ACK/NACK response during probing/initialization. +// Not part of the public API (see tactility/drivers/gps.h) - callers only ever see GpsState/GpsModel. +enum class GpsResponse { + None, + NotAck, + FrameErrors, + Ok, +}; diff --git a/Drivers/gps-module/source/gps_service.cpp b/Drivers/gps-module/source/gps_service.cpp new file mode 100644 index 000000000..a148bde58 --- /dev/null +++ b/Drivers/gps-module/source/gps_service.cpp @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include "gps_service_internal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +constexpr auto* TAG = "GpsService"; + +// A dynamically-constructed GPS_TYPE device, one per active GpsConfiguration. Device is the first +// member so `reinterpret_cast(device)` is never needed - callers just keep the +// Device* and, once done with it, `delete` via a GpsDeviceEntry* they already have. +struct GpsDeviceEntry { + Device device {}; + GpsConfig config {}; +}; + +struct GpsServiceData { + RecursiveMutex mutex {}; + std::vector devices; + GpsServiceState state = GPS_SERVICE_STATE_OFF; +}; + +static GpsServiceData* get_data() { + auto* instance = service_manager_find_instance(GPS_SERVICE_ID); + if (instance == nullptr) { + return nullptr; + } + return static_cast(service_instance_get_data(instance)); +} + +static void set_state(GpsServiceData* data, GpsServiceState state) { + recursive_mutex_lock(&data->mutex); + data->state = state; + recursive_mutex_unlock(&data->mutex); +} + +// 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_SERVICE_ID, "config.bin", out_path, out_path_size) == ERROR_NONE; +} + +void gps_service_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; + } + + 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_service_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_SERVICE_ID, directory, sizeof(directory)) != ERROR_NONE) { + return ERROR_RESOURCE; + } + ensure_directory_exists(directory); + + char path[256]; + if (!get_configuration_path(path, sizeof(path))) { + return ERROR_RESOURCE; + } + + 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); + return ok ? ERROR_NONE : ERROR_RESOURCE; +} + +static bool configurations_equal(const GpsConfiguration& a, const GpsConfiguration& b) { + return strcmp(a.uart_name, b.uart_name) == 0 && + a.baud_rate == b.baud_rate && + a.model == b.model; +} + +error_t gps_service_add_configuration(const GpsConfiguration* configuration) { + std::vector configurations; + load_configurations(configurations); + configurations.push_back(*configuration); + return write_configurations(configurations); +} + +error_t gps_service_remove_configuration(const GpsConfiguration* configuration) { + std::vector configurations; + load_configurations(configurations); + + size_t original_size = configurations.size(); + std::erase_if(configurations, [configuration](const GpsConfiguration& item) { + return configurations_equal(item, *configuration); + }); + + if (configurations.size() == original_size) { + return ERROR_NOT_FOUND; + } + + return write_configurations(configurations); +} + +// endregion + +// region Receiving + +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; + + if (device_construct(device) != ERROR_NONE) { + LOG_E(TAG, "Failed to construct %s", name); + 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; +} + +error_t gps_service_start_receiving() { + auto* data = get_data(); + if (data == nullptr) { + return ERROR_INVALID_STATE; + } + + recursive_mutex_lock(&data->mutex); + if (data->state != GpsServiceState::GPS_SERVICE_STATE_OFF) { + recursive_mutex_unlock(&data->mutex); + return ERROR_INVALID_STATE; + } + data->state = GpsServiceState::GPS_SERVICE_STATE_ON_PENDING; + recursive_mutex_unlock(&data->mutex); + + std::vector configurations; + load_configurations(configurations); + + if (configurations.empty()) { + LOG_E(TAG, "No GPS configurations"); + set_state(data, GpsServiceState::GPS_SERVICE_STATE_OFF); + return ERROR_NOT_FOUND; + } + + static uint32_t next_device_index = 0; + bool started_one_or_more = false; + + for (auto& configuration : configurations) { + 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); + continue; + } + + auto* entry = new(std::nothrow) GpsDeviceEntry(); + if (entry == nullptr) { + device_put(uart); + continue; + } + entry->config = GpsConfig { .baud_rate = configuration.baud_rate, .model = configuration.model }; + + char name[16]; + snprintf(name, sizeof(name), "gps%u", (unsigned)next_device_index++); + + bool started = construct_add_start(&entry->device, uart, name, &entry->config, "generic,gps"); + device_put(uart); + + if (started) { + recursive_mutex_lock(&data->mutex); + data->devices.push_back(entry); + recursive_mutex_unlock(&data->mutex); + started_one_or_more = true; + } else { + delete entry; + } + } + + if (!started_one_or_more) { + set_state(data, GPS_SERVICE_STATE_OFF); + return ERROR_RESOURCE; + } + + set_state(data, GPS_SERVICE_STATE_ON); + return ERROR_NONE; +} + +void gps_service_stop_receiving() { + auto* data = get_data(); + if (data == nullptr) { + return; + } + + recursive_mutex_lock(&data->mutex); + if (data->state != GPS_SERVICE_STATE_ON) { + recursive_mutex_unlock(&data->mutex); + return; + } + data->state = GPS_SERVICE_STATE_OFF_PENDING; + + for (auto* entry : data->devices) { + device_stop(&entry->device); + device_remove(&entry->device); + device_destruct(&entry->device); + delete entry; + } + data->devices.clear(); + + data->state = GPS_SERVICE_STATE_OFF; + recursive_mutex_unlock(&data->mutex); +} + +void gps_service_for_each_device(void* context, void (*on_device)(Device* device, void* context)) { + auto* data = get_data(); + if (data == nullptr) { + return; + } + + recursive_mutex_lock(&data->mutex); + for (auto* entry : data->devices) { + on_device(&entry->device, context); + } + recursive_mutex_unlock(&data->mutex); +} + +GpsServiceState gps_service_get_state() { + auto* data = get_data(); + if (data == nullptr) { + return GPS_SERVICE_STATE_OFF; + } + recursive_mutex_lock(&data->mutex); + auto state = data->state; + recursive_mutex_unlock(&data->mutex); + return state; +} + +bool gps_service_get_coordinates(minmea_sentence_rmc* out) { + auto* data = get_data(); + if (data == nullptr) { + return false; + } + + recursive_mutex_lock(&data->mutex); + bool found = false; + for (auto* entry : data->devices) { + if (gps_get_rmc(&entry->device, out, seconds_to_ticks(10)) == ERROR_NONE) { + found = true; + break; + } + } + recursive_mutex_unlock(&data->mutex); + return found; +} + +bool gps_service_has_coordinates() { + minmea_sentence_rmc rmc; + return gps_service_get_coordinates(&rmc); +} + +bool gps_service_get_gga(minmea_sentence_gga* out) { + auto* data = get_data(); + if (data == nullptr) { + return false; + } + + recursive_mutex_lock(&data->mutex); + bool found = false; + for (auto* entry : data->devices) { + if (gps_get_gga(&entry->device, out, seconds_to_ticks(10)) == ERROR_NONE) { + found = true; + break; + } + } + recursive_mutex_unlock(&data->mutex); + return found; +} + +// endregion + +// region ServiceManifest + +static void* create_service(const ServiceManifest*) { + auto* data = new(std::nothrow) GpsServiceData(); + if (data == nullptr) { + return nullptr; + } + recursive_mutex_construct(&data->mutex); + return data; +} + +static void destroy_service(const ServiceManifest*, void* data) { + auto* service_data = static_cast(data); + recursive_mutex_destruct(&service_data->mutex); + delete service_data; +} + +static void on_stop(ServiceInstance*, void* data) { + auto* service_data = static_cast(data); + if (service_data->state != GpsServiceState::GPS_SERVICE_STATE_OFF) { + gps_service_stop_receiving(); + } +} + +static const ServiceManifest gps_service_manifest = { + .id = GPS_SERVICE_ID, + .create_service = create_service, + .destroy_service = destroy_service, + .on_start = nullptr, + .on_stop = on_stop, +}; + +error_t gps_service_register() { + return service_manager_add(&gps_service_manifest, true); +} + +// endregion diff --git a/Drivers/gps-module/source/gps_service_internal.h b/Drivers/gps-module/source/gps_service_internal.h new file mode 100644 index 000000000..b29ac3166 --- /dev/null +++ b/Drivers/gps-module/source/gps_service_internal.h @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +// Registers the GPS service manifest with the kernel service manager (auto-started). Called once +// from module.cpp's Module::start(). +error_t gps_service_register(); diff --git a/Tactility/Source/hal/gps/GpsInit.cpp b/Drivers/gps-module/source/init.cpp similarity index 73% rename from Tactility/Source/hal/gps/GpsInit.cpp rename to Drivers/gps-module/source/init.cpp index f3501f4cf..0a1f3b72c 100644 --- a/Tactility/Source/hal/gps/GpsInit.cpp +++ b/Drivers/gps-module/source/init.cpp @@ -1,31 +1,31 @@ -#include -#include -#include -#include +// SPDX-License-Identifier: Apache-2.0 +#include "init.h" +#include "cas_messages.h" +#include "gps_response.h" +#include "ublox.h" #include +#include #include #include #include +#include #include -namespace tt::hal::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(); +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_ACK_NACK_MSG_SIZE] = {0}; - uint8_t bufferPos = 0; - TickType_t waitTicks = pdMS_TO_TICKS(waitMillis); + 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(_message_CAS_CFG_NAVX_CONF), _message_CAS_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(_message_CAS_CFG_RATE_1HZ), _message_CAS_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-module/source/init.h b/Drivers/gps-module/source/init.h new file mode 100644 index 000000000..c51f0a93a --- /dev/null +++ b/Drivers/gps-module/source/init.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +#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-module/source/module.cpp b/Drivers/gps-module/source/module.cpp new file mode 100644 index 000000000..c8d06b1c6 --- /dev/null +++ b/Drivers/gps-module/source/module.cpp @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "gps_service_internal.h" + +#include +#include +#include +#include +#include + +constexpr auto* TAG = "GpsModule"; + +extern "C" { + +extern Driver gps_driver; + +static Driver* const gps_drivers[] = { + &gps_driver, + nullptr +}; + +static error_t start() { + error_t error = gps_service_register(); + if (error != ERROR_NONE) { + LOG_E(TAG, "Failed to register GPS service: %s", error_to_string(error)); + return error; + } + return ERROR_NONE; +} + +Module gps_module = { + .name = "gps", + .start = start, + .stop = nullptr, + .drivers = gps_drivers, + .symbols = nullptr, + .internal = nullptr +}; + +} diff --git a/Tactility/Source/hal/gps/Probe.cpp b/Drivers/gps-module/source/probe.cpp similarity index 70% rename from Tactility/Source/hal/gps/Probe.cpp rename to Drivers/gps-module/source/probe.cpp index d5a8ca0bb..3022bc5e0 100644 --- a/Tactility/Source/hal/gps/Probe.cpp +++ b/Drivers/gps-module/source/probe.cpp @@ -1,26 +1,22 @@ -#include "Tactility/hal/gps/GpsDevice.h" -#include "Tactility/hal/gps/Ublox.h" -#include +// SPDX-License-Identifier: Apache-2.0 +#include "probe.h" +#include "gps_response.h" +#include "ublox.h" +#include #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) { +static char* probe_strnstr(const char* s, const char* find, size_t slen) { char c; if ((c = *find++) != '\0') { char sc; @@ -43,36 +39,24 @@ char* strnstr(const char* s, const char* find, size_t slen) { /** * From: https://github.com/meshtastic/firmware/blob/f81d3b045dd1b7e3ca7870af3da915ff4399ea98/src/gps/GPS.cpp */ -GpsResponse getAck(::Device* uart, const char* message, uint32_t waitMillis) { +static GpsResponse get_ack(Device* uart, const char* message, uint32_t wait_millis) { 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) { + 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); -#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 + 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 { - bytesRead = 0; -#ifdef GPS_DEBUG - LOG_D(TAG, "%s", debugmsg.c_str()); -#endif + bytes_read = 0; } } } @@ -88,7 +72,7 @@ GpsResponse getAck(::Device* uart, const char* message, uint32_t waitMillis) { 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) { \ + if (get_ack(UART, RESPONSE, TIMEOUT) == GpsResponse::Ok) { \ LOG_I(TAG, "Probe detected %s %s", CHIP, #DRIVER); \ return DRIVER; \ } \ @@ -97,50 +81,48 @@ GpsResponse getAck(::Device* uart, const char* message, uint32_t waitMillis) { /** * From: https://github.com/meshtastic/firmware/blob/f81d3b045dd1b7e3ca7870af3da915ff4399ea98/src/gps/GPS.cpp */ -GpsModel probe(::Device* uart) { +GpsModel gps_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); + delay_millis(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); + delay_millis(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); + 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::ATGM336H, 500); + 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 */ 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, "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::MTK, 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 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); + delay_millis(20); - PROBE_SIMPLE(uart, "L76B", "$PMTK605*31", "Quectel-L76B", GpsModel::MTK_L76B, 500); - PROBE_SIMPLE(uart, "PA1616S", "$PMTK605*31", "1616S", GpsModel::MTK_PA1616S, 500); + 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 = ublox::probe(uart); - if (ublox_result != GpsModel::Unknown) { + auto ublox_result = gps_ublox::probe(uart); + if (ublox_result != GpsModel::GPS_MODEL_UNKNOWN) { return ublox_result; } else { LOG_W(TAG, "No GNSS Module"); - return GpsModel::Unknown; + return GpsModel::GPS_MODEL_UNKNOWN; } } - -} // namespace tt::hal::gps diff --git a/Drivers/gps-module/source/probe.h b/Drivers/gps-module/source/probe.h new file mode 100644 index 000000000..e72457215 --- /dev/null +++ b/Drivers/gps-module/source/probe.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 +#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/Tactility/Source/hal/gps/Ublox.cpp b/Drivers/gps-module/source/ublox.cpp similarity index 72% rename from Tactility/Source/hal/gps/Ublox.cpp rename to Drivers/gps-module/source/ublox.cpp index bed6861f8..1870fef06 100644 --- a/Tactility/Source/hal/gps/Ublox.cpp +++ b/Drivers/gps-module/source/ublox.cpp @@ -1,26 +1,29 @@ -#include -#include -#include +// SPDX-License-Identifier: Apache-2.0 +#include "ublox.h" +#include "gps_response.h" +#include "ublox_messages.h" +#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 +42,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 +82,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 +93,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 +109,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 +127,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 +186,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 +194,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 +202,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 +275,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 +362,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 +417,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 +426,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 +436,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 +460,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 +470,4 @@ bool initUblox6(::Device* uart) { return true; } -} // namespace tt::hal::gps::ublox +} diff --git a/Drivers/gps-module/source/ublox.h b/Drivers/gps-module/source/ublox.h new file mode 100644 index 000000000..398bff527 --- /dev/null +++ b/Drivers/gps-module/source/ublox.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +#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-module/source/ublox_messages.h similarity index 99% rename from Tactility/Private/Tactility/hal/gps/UbloxMessages.h rename to Drivers/gps-module/source/ublox_messages.h index 52092e9c6..84bff1494 100644 --- a/Tactility/Private/Tactility/hal/gps/UbloxMessages.h +++ b/Drivers/gps-module/source/ublox_messages.h @@ -1,8 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 #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/Firmware/CMakeLists.txt b/Firmware/CMakeLists.txt index 22578bff7..457d65ec5 100644 --- a/Firmware/CMakeLists.txt +++ b/Firmware/CMakeLists.txt @@ -95,9 +95,9 @@ else () list(APPEND REQUIRES_LIST Tactility TactilityFreeRtos - hal-device-module lvgl-module crypt-module + gps-module SDL2::SDL2-static SDL2-static ) diff --git a/Modules/hal-device-module/CMakeLists.txt b/Modules/hal-device-module/CMakeLists.txt deleted file mode 100644 index e86d74da2..000000000 --- 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/LICENSE-Apache-2.0.md b/Modules/hal-device-module/LICENSE-Apache-2.0.md deleted file mode 100644 index f5f4b8b5e..000000000 --- a/Modules/hal-device-module/LICENSE-Apache-2.0.md +++ /dev/null @@ -1,195 +0,0 @@ -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/hal-device-module/README.md b/Modules/hal-device-module/README.md deleted file mode 100644 index 5814a0e74..000000000 --- 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 6dadcb805..000000000 --- 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 9b19e5b53..000000000 --- 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 db7e68a60..000000000 --- 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 0041237fd..000000000 --- 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 ceb17b7b4..000000000 --- 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 4aef46661..000000000 --- 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/source/arch/lvgl_posix.c b/Modules/lvgl-module/source/arch/lvgl_posix.c index 3698b6867..8eaa482c7 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; @@ -71,6 +73,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 +94,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 a0d876326..efce7a6b2 100644 --- a/Tactility/CMakeLists.txt +++ b/Tactility/CMakeLists.txt @@ -7,12 +7,11 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*) list(APPEND REQUIRES_LIST TactilityKernel TactilityFreeRtos - hal-device-module lvgl-module crypt-module + gps-module lv_screenshot minitar - minmea ) if (DEFINED ENV{ESP_IDF_VERSION}) diff --git a/Tactility/Include/Tactility/hal/gps/GpsConfiguration.h b/Tactility/Include/Tactility/hal/gps/GpsConfiguration.h deleted file mode 100644 index bfcea9bec..000000000 --- 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 4c9a08823..000000000 --- a/Tactility/Include/Tactility/hal/gps/GpsDevice.h +++ /dev/null @@ -1,135 +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; - }; - - ::Device* uartDevice; - uint32_t baudRate; - GpsModel model; - - RecursiveMutex mutex; - std::unique_ptr thread; - bool threadInterrupted = false; - std::vector ggaSubscriptions; - std::vector rmcSubscriptions; - GgaSubscriptionId lastSatelliteSubscriptionId = 0; - RmcSubscriptionId lastRmcSubscriptionId = 0; - State state = State::Off; - - int32_t threadMain(); - - bool isThreadInterrupted() const; - - void setState(State newState); - -public: - - explicit GpsDevice( - ::Device* uartDevice, - uint32_t baudRate, - GpsModel model // Choosing "Unknown" will result in a probe - ) : uartDevice(uartDevice), baudRate(baudRate), model(model) { - assert(uartDevice != nullptr); - device_get(uartDevice); - }; - - ~GpsDevice() override { - device_put(uartDevice); - } - - 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 cda420e4d..000000000 --- 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/service/gps/GpsService.h b/Tactility/Include/Tactility/service/gps/GpsService.h deleted file mode 100644 index 4c1fc6c57..000000000 --- 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 dae2195dd..000000000 --- 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 6c3129674..000000000 --- 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/gps/GpsInit.h b/Tactility/Private/Tactility/hal/gps/GpsInit.h deleted file mode 100644 index 6197a1d7e..000000000 --- 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 6f91b1f82..000000000 --- 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 64398d327..000000000 --- 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/Source/MountPoints.cpp b/Tactility/Source/MountPoints.cpp index 32a6866bd..4f79967d3 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/Tactility.cpp b/Tactility/Source/Tactility.cpp index 31050c1d4..50f3bd356 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include #include #include @@ -74,7 +74,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; } @@ -319,7 +318,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); @@ -372,12 +370,12 @@ void run(Module* dtsModules[], DtsDevice dtsDevices[]) { 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); + // gps-module + check(module_construct_add_start(&gps_module) == ERROR_NONE); + #ifdef ESP_PLATFORM initEsp(); #endif diff --git a/Tactility/Source/app/AppInstall.cpp b/Tactility/Source/app/AppInstall.cpp index b756a0e43..bc7e70d82 100644 --- a/Tactility/Source/app/AppInstall.cpp +++ b/Tactility/Source/app/AppInstall.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include diff --git a/Tactility/Source/app/addgps/AddGps.cpp b/Tactility/Source/app/addgps/AddGps.cpp index f5bf3ba3b..7eb979ac1 100644 --- a/Tactility/Source/app/addgps/AddGps.cpp +++ b/Tactility/Source/app/addgps/AddGps.cpp @@ -1,12 +1,13 @@ #include #include #include -#include #include #include -#include -#include "tactility/drivers/uart_controller.h" +#include +#include +#include + #include #include #include @@ -29,6 +30,26 @@ class AddGpsApp final : public App { std::array baudRates = { 9600, 19200, 28800, 38400, 57600, 115200 }; const char* baudRatesDropdownValues = "9600\n19200\n28800\n38400\n57600\n115200"; + struct DuplicateCheckContext { + const char* uartName; + bool found; + }; + + static void onCheckDuplicateUart(const GpsConfiguration* configuration, size_t, void* context) { + auto* ctx = static_cast(context); + if (strcmp(configuration->uart_name, ctx->uartName) == 0) { + ctx->found = true; + } + } + + 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 +58,33 @@ 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); + LOG_I(TAG, "Saving: uart=%s, model=%d, baud=%u", new_configuration.uart_name, (int)new_configuration.model, (unsigned)new_configuration.baud_rate); - 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; - } - } + DuplicateCheckContext duplicate_check = { .uartName = new_configuration.uart_name, .found = false }; + gps_service_for_each_configuration(&duplicate_check, onCheckDuplicateUart); + if (duplicate_check.found) { + auto message = std::string("Bus \"") + new_configuration.uart_name + "\" is already in use in another configuration"; + app::alertdialog::start("Error", message.c_str()); + return; + } - if (!service->addGpsConfiguration(new_configuration)) { - app::alertdialog::start("Error", "Failed to add configuration"); - } else { - stop(); - } + if (gps_service_add_configuration(&new_configuration) != ERROR_NONE) { + app::alertdialog::start("Error", "Failed to add configuration"); + } else { + stop(); } } @@ -137,7 +153,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/crashdiagnostics/CrashDiagnostics.cpp b/Tactility/Source/app/crashdiagnostics/CrashDiagnostics.cpp index bfb9e482e..3f7fdc4d4 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/gpssettings/GpsSettings.cpp b/Tactility/Source/app/gpssettings/GpsSettings.cpp index 7766b1b96..0d6170e21 100644 --- a/Tactility/Source/app/gpssettings/GpsSettings.cpp +++ b/Tactility/Source/app/gpssettings/GpsSettings.cpp @@ -5,10 +5,10 @@ #include #include #include -#include -#include #include +#include +#include #include #include @@ -29,7 +29,6 @@ class GpsSettingsApp final : public App { static constexpr auto* TAG = "GpsSettings"; 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; @@ -44,17 +43,6 @@ class GpsSettingsApp final : public App { 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); @@ -70,22 +58,12 @@ 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) { + void createInfoView(GpsModel model) { auto* label = lv_label_create(infoContainerWidget); - if (model == hal::gps::GpsModel::Unknown) { + if (model == GpsModel::GPS_MODEL_UNKNOWN) { lv_label_set_text(label, "Model: auto-detect"); } else { - lv_label_set_text_fmt(label, "Model: %s", toString(model)); + lv_label_set_text_fmt(label, "Model: %s", gps_model_to_string(model)); } } @@ -98,21 +76,21 @@ class GpsSettingsApp final : public App { // 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)); - 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"); - } + std::vector configurations; + gps_service_for_each_configuration(&configurations, [](const GpsConfiguration* configuration, size_t, void* context) { + static_cast*>(context)->push_back(*configuration); + }); + + if (index < (int)configurations.size()) { + if (gps_service_remove_configuration(&configurations[index]) == ERROR_NONE) { + app->updateViews(); + } else { + alertdialog::start("Error", "Failed to remove configuration"); } } } - void createGpsView(const hal::gps::GpsConfiguration& configuration, int index) { + void createGpsView(const 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); @@ -129,16 +107,16 @@ class GpsSettingsApp final : public App { 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); + lv_label_set_text_fmt(uart_label, "UART: %s", configuration.uart_name); auto* baud_label = lv_label_create(left_wrapper); - lv_label_set_text_fmt(baud_label, "Baud: %lu", configuration.baudRate); + lv_label_set_text_fmt(baud_label, "Baud: %lu", configuration.baud_rate); auto* model_label = lv_label_create(left_wrapper); - if (configuration.model == hal::gps::GpsModel::Unknown) { + if (configuration.model == GpsModel::GPS_MODEL_UNKNOWN) { lv_label_set_text(model_label, "Model: auto-detect"); } else { - lv_label_set_text_fmt(model_label, "Model: %s", toString(configuration.model)); + lv_label_set_text_fmt(model_label, "Model: %s", gps_model_to_string(configuration.model)); } // Right wrapper @@ -158,11 +136,11 @@ class GpsSettingsApp final : public App { void updateViews() { auto lock = lvgl::getSyncLock()->asScopedLock(); if (lock.lock(100 / portTICK_PERIOD_MS)) { - auto state = service->getState(); + auto state = gps_service_get_state(); // Update toolbar switch (state) { - case service::gps::State::OnPending: + case GpsServiceState::GPS_SERVICE_STATE_ON_PENDING: LOG_D(TAG, "OnPending"); lv_obj_remove_flag(spinnerWidget, LV_OBJ_FLAG_HIDDEN); lv_obj_add_state(switchWidget, LV_STATE_CHECKED); @@ -171,7 +149,7 @@ class GpsSettingsApp final : public App { lv_obj_add_flag(gpsConfigWrapper, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(addGpsWrapper, LV_OBJ_FLAG_HIDDEN); break; - case service::gps::State::On: + case GpsServiceState::GPS_SERVICE_STATE_ON: LOG_D(TAG, "On"); lv_obj_add_flag(spinnerWidget, LV_OBJ_FLAG_HIDDEN); lv_obj_add_state(switchWidget, LV_STATE_CHECKED); @@ -180,7 +158,7 @@ class GpsSettingsApp final : public App { lv_obj_add_flag(gpsConfigWrapper, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(addGpsWrapper, LV_OBJ_FLAG_HIDDEN); break; - case service::gps::State::OffPending: + case GpsServiceState::GPS_SERVICE_STATE_OFF_PENDING: LOG_D(TAG, "OffPending"); lv_obj_remove_flag(spinnerWidget, LV_OBJ_FLAG_HIDDEN); lv_obj_remove_state(switchWidget, LV_STATE_CHECKED); @@ -189,7 +167,7 @@ class GpsSettingsApp final : public App { lv_obj_remove_flag(gpsConfigWrapper, LV_OBJ_FLAG_HIDDEN); lv_obj_remove_flag(addGpsWrapper, LV_OBJ_FLAG_HIDDEN); break; - case service::gps::State::Off: + case GpsServiceState::GPS_SERVICE_STATE_OFF: LOG_D(TAG, "Off"); lv_obj_add_flag(spinnerWidget, LV_OBJ_FLAG_HIDDEN); lv_obj_remove_state(switchWidget, LV_STATE_CHECKED); @@ -201,18 +179,17 @@ class GpsSettingsApp final : public App { } // Update status label and device info - if (state == service::gps::State::On) { + if (state == GpsServiceState::GPS_SERVICE_STATE_ON) { if (!hasSetInfo) { - auto devices = hal::findDevices(hal::Device::Type::Gps); - for (auto& device : devices) { - createInfoView(device->getModel()); - hasSetInfo = true; - } + gps_service_for_each_device(this, [](Device* device, void* context) { + static_cast(context)->createInfoView(gps_get_model(device)); + }); + hasSetInfo = true; } minmea_sentence_rmc rmc; char buffer[64]; - if (service->getCoordinates(rmc)) { + if (gps_service_get_coordinates(&rmc)) { lv_label_set_text(statusLabelWidget, "Lock acquired"); lv_obj_set_style_text_color(statusLabelWidget, lv_color_hex(0x00ff00), 0); @@ -268,7 +245,7 @@ class GpsSettingsApp final : public App { } minmea_sentence_gga gga; - if (service->getGga(gga)) { + if (gps_service_get_gga(&gga)) { float altitude = minmea_tofloat(&gga.altitude); if (!isnan(altitude)) { snprintf(buffer, sizeof(buffer), "%.1f m", altitude); @@ -296,13 +273,13 @@ class GpsSettingsApp final : public App { 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++); - } + std::vector configurations; + gps_service_for_each_configuration(&configurations, [](const GpsConfiguration* configuration, size_t, void* context) { + static_cast*>(context)->push_back(*configuration); + }); + int index = 0; + for (auto& configuration : configurations) { + createGpsView(configuration, index++); } } else { lv_obj_clean(gpsConfigWrapper); @@ -310,34 +287,20 @@ class GpsSettingsApp final : public App { } } - /** @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 { - return false; - } - } - 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); + auto state = gps_service_get_state(); + bool is_on = (state == GpsServiceState::GPS_SERVICE_STATE_ON) || (state == GpsServiceState::GPS_SERVICE_STATE_ON_PENDING); 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(); + gps_service_start_receiving(); }); } else { getMainDispatcher().dispatch([this] { - service->stopReceiving(); + gps_service_stop_receiving(); }); } } @@ -368,10 +331,11 @@ class GpsSettingsApp final : public App { public: GpsSettingsApp() { + // Runs continuously while the screen is shown - there's no push notification for GPS + // service 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(); }); - service = service::gps::findGpsService(); } void onShow(AppContext& app, lv_obj_t* parent) override { @@ -419,10 +383,6 @@ public: 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); @@ -442,13 +402,12 @@ public: 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(); + timer->start(); updateViews(); } void onHide(AppContext& app) override { - service->getStatePubsub()->unsubscribe(serviceStateSubscription); - serviceStateSubscription = nullptr; + timer->stop(); } }; diff --git a/Tactility/Source/hal/gps/GpsConfiguration.cpp b/Tactility/Source/hal/gps/GpsConfiguration.cpp deleted file mode 100644 index c67d06aab..000000000 --- a/Tactility/Source/hal/gps/GpsConfiguration.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "Tactility/hal/gps/GpsConfiguration.h" -#include "Tactility/service/gps/GpsService.h" -#include "Tactility/file/ObjectFile.h" - -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 982e7a73b..000000000 --- a/Tactility/Source/hal/gps/GpsDevice.cpp +++ /dev/null @@ -1,191 +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]; - - UartConfig uartConfig = { - .baud_rate = 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(uartDevice, &uartConfig); - if (error != ERROR_NONE) { - LOG_E(TAG, "Failed to configure UART %s: %s", uartDevice->name, error_to_string(error)); - return -1; - } - - error = uart_controller_open(uartDevice); - if (error != ERROR_NONE) { - LOG_E(TAG, "Failed to open UART %s: %s", uartDevice->name, error_to_string(error)); - return -1; - } - - if (model == GpsModel::Unknown) { - model = probe(uartDevice); - if (model == GpsModel::Unknown) { - LOG_E(TAG, "Probe failed"); - setState(State::Error); - return -1; - } - } - mutex.lock(); - mutex.unlock(); - - if (!init(uartDevice, 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(uartDevice, 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(uartDevice) != ERROR_NONE) { - LOG_W(TAG, "Failed to stop UART %s", uartDevice->name); - } - - 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/Satellites.cpp b/Tactility/Source/hal/gps/Satellites.cpp deleted file mode 100644 index a7d30ca4e..000000000 --- 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/service/gps/GpsConfiguration.cpp b/Tactility/Source/service/gps/GpsConfiguration.cpp deleted file mode 100644 index ce4719be4..000000000 --- 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 8d4f707f1..000000000 --- a/Tactility/Source/service/gps/GpsService.cpp +++ /dev/null @@ -1,268 +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) { - ::Device* uart_device; - if (device_get_by_name(configuration.uartName, &uart_device) == ERROR_NONE) { - auto gps_device = std::make_shared( - uart_device, - configuration.baudRate, - configuration.model - ); - addGpsDevice(gps_device); - } else { - LOG_E(TAG, "Failed to find device %s", configuration.uartName); - } - } - - // 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/statusbar/Statusbar.cpp b/Tactility/Source/service/statusbar/Statusbar.cpp index 32ac74cc8..3039ac3fb 100644 --- a/Tactility/Source/service/statusbar/Statusbar.cpp +++ b/Tactility/Source/service/statusbar/Statusbar.cpp @@ -10,8 +10,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -154,8 +154,8 @@ 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); + auto gps_state = gps_service_get_state(); + bool show_icon = (gps_state == GpsServiceState::GPS_SERVICE_STATE_ON_PENDING) || (gps_state == GpsServiceState::GPS_SERVICE_STATE_ON); if (gps_last_state != show_icon) { if (show_icon) { lvgl::statusbar_icon_set_image(gps_icon_id, LVGL_ICON_STATUSBAR_LOCATION_ON); diff --git a/Tactility/Source/service/webserver/WebServerService.cpp b/Tactility/Source/service/webserver/WebServerService.cpp index 12f2de647..4062da91b 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 ee2b082a3..1ce13cb15 100644 --- a/Tactility/Source/settings/DisplaySettings.cpp +++ b/Tactility/Source/settings/DisplaySettings.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include diff --git a/Tests/Tactility/CMakeLists.txt b/Tests/Tactility/CMakeLists.txt index c31fd46e5..a343f249b 100644 --- a/Tests/Tactility/CMakeLists.txt +++ b/Tests/Tactility/CMakeLists.txt @@ -14,9 +14,9 @@ target_link_libraries(TactilityTests PRIVATE Tactility TactilityKernel platform-posix - hal-device-module lvgl-module crypt-module + gps-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 015323d25..000000000 --- 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 6a4d923f6..17931e2b3 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);