From 6c678456458846f9210b3c6430ede461a44623d3 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 28 Jan 2025 17:39:58 +0100 Subject: [PATCH] Cleanup and improvements (#194) - Lots of changes for migrating C code to C++ - Improved `Lockable` in several ways like adding `withLock()` (+ tests) - Improved `Semaphore` a bit for improved readability, and also added some tests - Upgrade Linux machine in GitHub Actions so that we can compile with a newer GCC - Simplification of WiFi connection - Updated funding options - (and more) --- .github/FUNDING.yml | 2 +- .github/workflows/build-simulator.yml | 2 +- Boards/LilygoTdeck/Source/hal/TdeckPower.cpp | 20 +-- Boards/M5stackCore2/Source/hal/Core2Power.cpp | 24 ++-- .../M5stackCoreS3/Source/hal/CoreS3Power.cpp | 20 +-- .../Simulator/Source/hal/SimulatorPower.cpp | 18 +-- Boards/UnPhone/Source/hal/UnPhonePower.cpp | 14 +- Documentation/ideas.md | 1 + Tactility/Private/app/AppInstance.h | 16 +-- Tactility/Private/app/files/State.h | 12 +- Tactility/Private/app/wificonnect/State.h | 3 - Tactility/Private/app/wifimanage/State.h | 8 +- Tactility/Source/Tactility.cpp | 81 ++++++------ Tactility/Source/Tactility.h | 8 +- Tactility/Source/app/AppManifest.h | 4 +- Tactility/Source/app/boot/Boot.cpp | 8 +- .../app/crashdiagnostics/CrashDiagnostics.cpp | 2 +- Tactility/Source/app/files/View.cpp | 13 +- Tactility/Source/app/launcher/Launcher.cpp | 15 ++- Tactility/Source/app/log/Log.cpp | 53 ++++---- Tactility/Source/app/wificonnect/State.cpp | 9 -- Tactility/Source/app/wificonnect/View.cpp | 1 + Tactility/Source/app/wifimanage/State.cpp | 9 -- Tactility/Source/app/wifimanage/View.cpp | 100 +++++++------- .../Source/app/wifimanage/WifiManage.cpp | 7 +- Tactility/Source/service/loader/Loader.cpp | 43 +++--- .../Source/service/screenshot/Screenshot.cpp | 4 +- .../Source/service/screenshot/Screenshot.h | 4 +- .../service/screenshot/ScreenshotTask.cpp | 28 ++-- .../service/screenshot/ScreenshotTask.h | 8 +- .../Source/service/statusbar/Statusbar.cpp | 22 ++-- TactilityC/Source/tt_semaphore.cpp | 2 +- TactilityCore/Source/CoreExtraDefines.h | 34 ----- TactilityCore/Source/Dispatcher.h | 1 - TactilityCore/Source/Lockable.h | 31 ++++- TactilityCore/Source/Log.cpp | 26 +--- TactilityCore/Source/Log.h | 4 +- TactilityCore/Source/Mutex.h | 13 +- TactilityCore/Source/Semaphore.cpp | 12 +- TactilityCore/Source/Semaphore.h | 33 +++-- TactilityCore/Source/StringUtils.cpp | 14 +- TactilityCore/Source/StringUtils.h | 9 -- .../Source/kernel/critical/Critical.cpp | 6 +- .../Source/kernel/critical/Critical.h | 8 +- .../Source/TactilityHeadless.cpp | 4 +- TactilityHeadless/Source/hal/i2c/I2c.cpp | 7 +- .../Source/kernel/SystemEvents.cpp | 41 +++--- .../Source/service/wifi/Wifi.cpp | 25 ++-- TactilityHeadless/Source/service/wifi/Wifi.h | 1 + .../Source/service/wifi/WifiEsp.cpp | 124 ++++-------------- .../Source/service/wifi/WifiSettings.h | 2 + Tests/TactilityCore/LockableTest.cpp | 39 ++++++ Tests/TactilityCore/MutexTest.cpp | 19 +++ Tests/TactilityCore/SemaphoreTest.cpp | 35 +++++ 54 files changed, 518 insertions(+), 531 deletions(-) create mode 100644 Tests/TactilityCore/LockableTest.cpp create mode 100644 Tests/TactilityCore/SemaphoreTest.cpp diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index a240af9f..c6535e2d 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -10,6 +10,6 @@ liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry polar: # Replace with a single Polar username -buy_me_a_coffee: # Replace with a single Buy Me a Coffee username +buy_me_a_coffee: bytewelder thanks_dev: # Replace with a single thanks.dev username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/workflows/build-simulator.yml b/.github/workflows/build-simulator.yml index 1a28f6e6..8f026013 100644 --- a/.github/workflows/build-simulator.yml +++ b/.github/workflows/build-simulator.yml @@ -8,7 +8,7 @@ on: types: [opened, synchronize, reopened] jobs: Build-Simulator-Linux: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: "Build" diff --git a/Boards/LilygoTdeck/Source/hal/TdeckPower.cpp b/Boards/LilygoTdeck/Source/hal/TdeckPower.cpp index 56e97d81..2e387518 100644 --- a/Boards/LilygoTdeck/Source/hal/TdeckPower.cpp +++ b/Boards/LilygoTdeck/Source/hal/TdeckPower.cpp @@ -26,9 +26,9 @@ static adc_oneshot_chan_cfg_t adcChannelConfig = { }; static uint8_t estimateChargeLevelFromVoltage(uint32_t milliVolt) { - float volts = TT_MIN((float)milliVolt / 1000.f, BATTERY_VOLTAGE_MAX); + float volts = std::min((float)milliVolt / 1000.f, BATTERY_VOLTAGE_MAX); float voltage_percentage = (volts - BATTERY_VOLTAGE_MIN) / (BATTERY_VOLTAGE_MAX - BATTERY_VOLTAGE_MIN); - float voltage_factor = TT_MIN(1.0f, voltage_percentage); + float voltage_factor = std::min(1.0f, voltage_percentage); auto charge_level = (uint8_t) (voltage_factor * 100.f); TT_LOG_V(TAG, "mV = %lu, scaled = %.2f, factor = %.2f, result = %d", milliVolt, volts, voltage_factor, charge_level); return charge_level; @@ -56,11 +56,11 @@ TdeckPower::~TdeckPower() { bool TdeckPower::supportsMetric(MetricType type) const { switch (type) { - case MetricType::BatteryVoltage: - case MetricType::ChargeLevel: + using enum MetricType; + case BatteryVoltage: + case ChargeLevel: return true; - case MetricType::IsCharging: - case MetricType::Current: + default: return false; } @@ -69,17 +69,17 @@ bool TdeckPower::supportsMetric(MetricType type) const { bool TdeckPower::getMetric(Power::MetricType type, Power::MetricData& data) { switch (type) { - case MetricType::BatteryVoltage: + using enum MetricType; + case BatteryVoltage: return readBatteryVoltageSampled(data.valueAsUint32); - case MetricType::ChargeLevel: + case ChargeLevel: if (readBatteryVoltageSampled(data.valueAsUint32)) { data.valueAsUint32 = estimateChargeLevelFromVoltage(data.valueAsUint32); return true; } else { return false; } - case MetricType::IsCharging: - case MetricType::Current: + default: return false; } diff --git a/Boards/M5stackCore2/Source/hal/Core2Power.cpp b/Boards/M5stackCore2/Source/hal/Core2Power.cpp index 1501169f..4d4f9624 100644 --- a/Boards/M5stackCore2/Source/hal/Core2Power.cpp +++ b/Boards/M5stackCore2/Source/hal/Core2Power.cpp @@ -8,11 +8,12 @@ extern axp192_t axpDevice; bool Core2Power::supportsMetric(MetricType type) const { switch (type) { - case MetricType::BatteryVoltage: - case MetricType::ChargeLevel: - case MetricType::IsCharging: + using enum MetricType; + case BatteryVoltage: + case ChargeLevel: + case IsCharging: return true; - case MetricType::Current: + default: return false; } @@ -21,16 +22,17 @@ bool Core2Power::supportsMetric(MetricType type) const { bool Core2Power::getMetric(Power::MetricType type, Power::MetricData& data) { switch (type) { - case MetricType::BatteryVoltage: { + using enum MetricType; + case BatteryVoltage: { float voltage; if (axp192_read(&axpDevice, AXP192_BATTERY_VOLTAGE, &voltage) == ESP_OK) { - data.valueAsUint32 = (uint32_t)TT_MAX((voltage * 1000.f), 0.0f); + data.valueAsUint32 = (uint32_t)std::max((voltage * 1000.f), 0.0f); return true; } else { return false; } } - case MetricType::ChargeLevel: { + case ChargeLevel: { float vbat, charge_current; if ( axp192_read(&axpDevice, AXP192_BATTERY_VOLTAGE, &vbat) == ESP_OK && @@ -51,7 +53,7 @@ bool Core2Power::getMetric(Power::MetricType type, Power::MetricData& data) { return false; } } - case MetricType::IsCharging: { + case IsCharging: { float charge_current; if (axp192_read(&axpDevice, AXP192_CHARGE_CURRENT, &charge_current) == ESP_OK) { data.valueAsBool = charge_current > 0.001f; @@ -60,7 +62,7 @@ bool Core2Power::getMetric(Power::MetricType type, Power::MetricData& data) { return false; } } - case MetricType::Current: { + case Current: { float charge_current, discharge_current; if ( axp192_read(&axpDevice, AXP192_CHARGE_CURRENT, &charge_current) == ESP_OK && @@ -76,9 +78,9 @@ bool Core2Power::getMetric(Power::MetricType type, Power::MetricData& data) { return false; } } + default: + return false; } - - return false; // Safety guard for when new enum values are introduced } bool Core2Power::isAllowedToCharge() const { diff --git a/Boards/M5stackCoreS3/Source/hal/CoreS3Power.cpp b/Boards/M5stackCoreS3/Source/hal/CoreS3Power.cpp index 08d8fb23..31755849 100644 --- a/Boards/M5stackCoreS3/Source/hal/CoreS3Power.cpp +++ b/Boards/M5stackCoreS3/Source/hal/CoreS3Power.cpp @@ -5,11 +5,12 @@ bool CoreS3Power::supportsMetric(MetricType type) const { switch (type) { - case MetricType::BatteryVoltage: - case MetricType::IsCharging: - case MetricType::ChargeLevel: + using enum MetricType; + case BatteryVoltage: + case IsCharging: + case ChargeLevel: return true; - case MetricType::Current: + default: return false; } @@ -18,7 +19,8 @@ bool CoreS3Power::supportsMetric(MetricType type) const { bool CoreS3Power::getMetric(Power::MetricType type, Power::MetricData& data) { switch (type) { - case MetricType::BatteryVoltage: { + using enum MetricType; + case BatteryVoltage: { float milliVolt; if (axpDevice.getBatteryVoltage(milliVolt)) { data.valueAsUint32 = (uint32_t)milliVolt; @@ -27,7 +29,7 @@ bool CoreS3Power::getMetric(Power::MetricType type, Power::MetricData& data) { return false; } } - case MetricType::ChargeLevel: { + case ChargeLevel: { float vbatMillis; if (axpDevice.getBatteryVoltage(vbatMillis)) { float vbat = vbatMillis / 1000.f; @@ -44,7 +46,7 @@ bool CoreS3Power::getMetric(Power::MetricType type, Power::MetricData& data) { return false; } } - case MetricType::IsCharging: { + case IsCharging: { Axp2101::ChargeStatus status; if (axpDevice.getChargeStatus(status)) { data.valueAsBool = (status == Axp2101::CHARGE_STATUS_CHARGING); @@ -53,11 +55,9 @@ bool CoreS3Power::getMetric(Power::MetricType type, Power::MetricData& data) { return false; } } - case MetricType::Current: + default: return false; } - - return false; // Safety guard for when new enum values are introduced } bool CoreS3Power::isAllowedToCharge() const { diff --git a/Boards/Simulator/Source/hal/SimulatorPower.cpp b/Boards/Simulator/Source/hal/SimulatorPower.cpp index 1c43ae6f..642257f8 100644 --- a/Boards/Simulator/Source/hal/SimulatorPower.cpp +++ b/Boards/Simulator/Source/hal/SimulatorPower.cpp @@ -4,10 +4,11 @@ bool SimulatorPower::supportsMetric(MetricType type) const { switch (type) { - case MetricType::IsCharging: - case MetricType::Current: - case MetricType::BatteryVoltage: - case MetricType::ChargeLevel: + using enum MetricType; + case IsCharging: + case Current: + case BatteryVoltage: + case ChargeLevel: return true; } @@ -16,16 +17,17 @@ bool SimulatorPower::supportsMetric(MetricType type) const { bool SimulatorPower::getMetric(Power::MetricType type, Power::MetricData& data) { switch (type) { - case MetricType::IsCharging: + using enum MetricType; + case IsCharging: data.valueAsBool = true; return true; - case MetricType::Current: + case Current: data.valueAsInt32 = 42; return true; - case MetricType::BatteryVoltage: + case BatteryVoltage: data.valueAsUint32 = 4032; return true; - case MetricType::ChargeLevel: + case ChargeLevel: data.valueAsUint8 = 100; return true; } diff --git a/Boards/UnPhone/Source/hal/UnPhonePower.cpp b/Boards/UnPhone/Source/hal/UnPhonePower.cpp index fd104e95..5bc18ee8 100644 --- a/Boards/UnPhone/Source/hal/UnPhonePower.cpp +++ b/Boards/UnPhone/Source/hal/UnPhonePower.cpp @@ -8,9 +8,9 @@ #define BATTERY_VOLTAGE_MAX 4.2f static uint8_t estimateChargeLevelFromVoltage(uint32_t milliVolt) { - float volts = TT_MIN((float)milliVolt / 1000.f, BATTERY_VOLTAGE_MAX); + float volts = std::min((float)milliVolt / 1000.f, BATTERY_VOLTAGE_MAX); float voltage_percentage = (volts - BATTERY_VOLTAGE_MIN) / (BATTERY_VOLTAGE_MAX - BATTERY_VOLTAGE_MIN); - float voltage_factor = TT_MIN(1.0f, voltage_percentage); + float voltage_factor = std::min(1.0f, voltage_percentage); auto charge_level = (uint8_t) (voltage_factor * 100.f); TT_LOG_V(TAG, "mV = %lu, scaled = %.2f, factor = %.2f, result = %d", milliVolt, volts, voltage_factor, charge_level); return charge_level; @@ -18,8 +18,9 @@ static uint8_t estimateChargeLevelFromVoltage(uint32_t milliVolt) { bool UnPhonePower::supportsMetric(MetricType type) const { switch (type) { - case MetricType::BatteryVoltage: - case MetricType::ChargeLevel: + using enum MetricType; + case BatteryVoltage: + case ChargeLevel: return true; default: return false; @@ -28,9 +29,10 @@ bool UnPhonePower::supportsMetric(MetricType type) const { bool UnPhonePower::getMetric(Power::MetricType type, Power::MetricData& data) { switch (type) { - case MetricType::BatteryVoltage: + using enum MetricType; + case BatteryVoltage: return readBatteryVoltageSampled(data.valueAsUint32); - case MetricType::ChargeLevel: { + case ChargeLevel: { uint32_t milli_volt; if (readBatteryVoltageSampled(milli_volt)) { data.valueAsUint8 = estimateChargeLevelFromVoltage(milli_volt); diff --git a/Documentation/ideas.md b/Documentation/ideas.md index e7eade65..4a76eb31 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -1,4 +1,5 @@ # TODOs +- Use std::span or string_view in StringUtils https://youtu.be/FRkJCvHWdwQ?t=2754 - Fix bug in T-Deck/etc: esp_lvgl_port settings has a large stack size (~9kB) to fix an issue where the T-Deck would get a stackoverflow. This sometimes happens when WiFi is auto-enabled and you open the app while it is still connecting. - Clean up static_cast when casting to base class. - Mutex: Implement give/take from ISR support (works only for non-recursive ones) diff --git a/Tactility/Private/app/AppInstance.h b/Tactility/Private/app/AppInstance.h index ea1243e1..12c96aeb 100644 --- a/Tactility/Private/app/AppInstance.h +++ b/Tactility/Private/app/AppInstance.h @@ -10,13 +10,13 @@ namespace tt::app { -typedef enum { - StateInitial, // App is being activated in loader - StateStarted, // App is in memory - StateShowing, // App view is created - StateHiding, // App view is destroyed - StateStopped // App is not in memory -} State; +enum class State { + Initial, // App is being activated in loader + Started, // App is in memory + Showing, // App view is created + Hiding, // App view is destroyed + Stopped // App is not in memory +}; /** * Thread-safe app instance. @@ -27,7 +27,7 @@ private: Mutex mutex = Mutex(Mutex::Type::Normal); const std::shared_ptr manifest; - State state = StateInitial; + State state = State::Initial; Flags flags = { .showStatusbar = true }; /** @brief Optional parameters to start the app with * When these are stored in the app struct, the struct takes ownership. diff --git a/Tactility/Private/app/files/State.h b/Tactility/Private/app/files/State.h index fcd065d4..66b021b0 100644 --- a/Tactility/Private/app/files/State.h +++ b/Tactility/Private/app/files/State.h @@ -41,13 +41,11 @@ public: bool setEntriesForChildPath(const std::string& child_path); bool setEntriesForPath(const std::string& path); - const std::vector& lockEntries() const { - mutex.lock(); - return dir_entries; - } - - void unlockEntries() { - mutex.unlock(); + template &> Func> + void withEntries(Func&& onEntries) const { + mutex.withLock([&]() { + std::invoke(std::forward(onEntries), dir_entries); + }); } bool getDirent(uint32_t index, dirent& dirent); diff --git a/Tactility/Private/app/wificonnect/State.h b/Tactility/Private/app/wificonnect/State.h index 3c3d558a..5b0b4631 100644 --- a/Tactility/Private/app/wificonnect/State.h +++ b/Tactility/Private/app/wificonnect/State.h @@ -20,9 +20,6 @@ public: void setConnectionError(bool error); bool hasConnectionError() const; - const service::wifi::settings::WifiApSettings& lockApSettings(); - void unlockApSettings(); - void setApSettings(const service::wifi::settings::WifiApSettings* newSettings); void setConnecting(bool isConnecting); diff --git a/Tactility/Private/app/wifimanage/State.h b/Tactility/Private/app/wifimanage/State.h index 5e676949..cdf2e19c 100644 --- a/Tactility/Private/app/wifimanage/State.h +++ b/Tactility/Private/app/wifimanage/State.h @@ -30,8 +30,12 @@ public: void updateApRecords(); - const std::vector& lockApRecords() const; - void unlockApRecords() const; + template &> Func> + void withApRecords(Func&& onApRecords) const { + mutex.withLock([&]() { + std::invoke(std::forward(onApRecords), apRecords); + }); + } void setConnectSsid(const std::string& ssid); std::string getConnectSsid() const; diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 3315c701..df96cd22 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -62,51 +62,46 @@ namespace app { #ifndef ESP_PLATFORM #endif -static const std::vector system_apps = { - &app::alertdialog::manifest, - &app::applist::manifest, - &app::boot::manifest, - &app::display::manifest, - &app::files::manifest, - &app::gpio::manifest, - &app::i2cscanner::manifest, - &app::i2csettings::manifest, - &app::imageviewer::manifest, - &app::inputdialog::manifest, - &app::launcher::manifest, - &app::log::manifest, - &app::settings::manifest, - &app::selectiondialog::manifest, - &app::systeminfo::manifest, - &app::textviewer::manifest, - &app::timedatesettings::manifest, - &app::timezone::manifest, - &app::usbsettings::manifest, - &app::wifiapsettings::manifest, - &app::wificonnect::manifest, - &app::wifimanage::manifest, -#if TT_FEATURE_SCREENSHOT_ENABLED - &app::screenshot::manifest, -#endif -#ifdef ESP_PLATFORM - &app::crashdiagnostics::manifest -#endif -}; - // endregion -static void register_system_apps() { - TT_LOG_I(TAG, "Registering default apps"); - for (const auto* app_manifest: system_apps) { - addApp(*app_manifest); - } +static void registerSystemApps() { + addApp(app::alertdialog::manifest); + addApp(app::applist::manifest); + addApp(app::boot::manifest); + addApp(app::display::manifest); + addApp(app::files::manifest); + addApp(app::gpio::manifest); + addApp(app::i2cscanner::manifest); + addApp(app::i2csettings::manifest); + addApp(app::imageviewer::manifest); + addApp(app::inputdialog::manifest); + addApp(app::launcher::manifest); + addApp(app::log::manifest); + addApp(app::settings::manifest); + addApp(app::selectiondialog::manifest); + addApp(app::systeminfo::manifest); + addApp(app::textviewer::manifest); + addApp(app::timedatesettings::manifest); + addApp(app::timezone::manifest); + addApp(app::usbsettings::manifest); + addApp(app::wifiapsettings::manifest); + addApp(app::wificonnect::manifest); + addApp(app::wifimanage::manifest); + +#if TT_FEATURE_SCREENSHOT_ENABLED + addApp(app::screenshot::manifest); +#endif + +#ifdef ESP_PLATFORM + addApp(app::crashdiagnostics::manifest); +#endif if (getConfiguration()->hardware->power != nullptr) { addApp(app::power::manifest); } } -static void register_user_apps(const std::vector& apps) { +static void registerUserApps(const std::vector& apps) { TT_LOG_I(TAG, "Registering user apps"); for (auto* manifest : apps) { assert(manifest != nullptr); @@ -114,7 +109,7 @@ static void register_user_apps(const std::vector& apps) } } -static void register_and_start_system_services() { +static void registerAndStartSystemServices() { TT_LOG_I(TAG, "Registering and starting system services"); addService(service::loader::manifest); addService(service::gui::manifest); @@ -124,7 +119,7 @@ static void register_and_start_system_services() { #endif } -static void register_and_start_user_services(const std::vector& manifests) { +static void registerAndStartUserServices(const std::vector& manifests) { TT_LOG_I(TAG, "Registering and starting user services"); for (auto* manifest : manifests) { assert(manifest != nullptr); @@ -147,14 +142,14 @@ void run(const Configuration& config) { // Note: the order of starting apps and services is critical! // System services are registered first so the apps below can find them if needed - register_and_start_system_services(); + registerAndStartSystemServices(); // Then we register system apps. They are not used/started yet. - register_system_apps(); + registerSystemApps(); // Then we register and start user services. They are started after system app // registration just in case they want to figure out which system apps are installed. - register_and_start_user_services(config.services); + registerAndStartUserServices(config.services); // Now we register the user apps, as they might rely on the user services. - register_user_apps(config.apps); + registerUserApps(config.apps); TT_LOG_I(TAG, "init starting desktop app"); service::loader::startApp(app::boot::manifest.id); diff --git a/Tactility/Source/Tactility.h b/Tactility/Source/Tactility.h index d910baeb..8d5f9b81 100644 --- a/Tactility/Source/Tactility.h +++ b/Tactility/Source/Tactility.h @@ -7,18 +7,22 @@ namespace tt { +namespace app::launcher { extern const app::AppManifest manifest; } + /** @brief The configuration for the operating system * It contains the hardware configuration, apps and services */ struct Configuration { /** HAL configuration (drivers) */ - const hal::Configuration* hardware; + const hal::Configuration* hardware = nullptr; /** List of user applications */ const std::vector apps = {}; /** List of user services */ const std::vector services = {}; /** Optional app to start automatically after the splash screen. */ - const char* _Nullable autoStartAppId = nullptr; + const std::string launcherAppId = app::launcher::manifest.id; + /** Optional app to start automatically after the splash screen. */ + const std::string autoStartAppId = {}; }; /** diff --git a/Tactility/Source/app/AppManifest.h b/Tactility/Source/app/AppManifest.h index 2de245f1..6403603f 100644 --- a/Tactility/Source/app/AppManifest.h +++ b/Tactility/Source/app/AppManifest.h @@ -61,10 +61,10 @@ typedef std::shared_ptr(*CreateApp)(); struct AppManifest { /** The identifier by which the app is launched by the system and other apps. */ - std::string id; + std::string id = {}; /** The user-readable name of the app. Used in UI. */ - std::string name; + std::string name = {}; /** Optional icon. */ std::string icon = {}; diff --git a/Tactility/Source/app/boot/Boot.cpp b/Tactility/Source/app/boot/Boot.cpp index f489c011..cb4fa062 100644 --- a/Tactility/Source/app/boot/Boot.cpp +++ b/Tactility/Source/app/boot/Boot.cpp @@ -73,12 +73,8 @@ private: #endif auto* config = tt::getConfiguration(); - if (config->autoStartAppId) { - TT_LOG_I(TAG, "init auto-starting %s", config->autoStartAppId); - tt::service::loader::startApp(config->autoStartAppId); - } else { - app::launcher::start(); - } + assert(!config->launcherAppId.empty()); + tt::service::loader::startApp(config->launcherAppId); } public: diff --git a/Tactility/Source/app/crashdiagnostics/CrashDiagnostics.cpp b/Tactility/Source/app/crashdiagnostics/CrashDiagnostics.cpp index 699daebc..46f421e0 100644 --- a/Tactility/Source/app/crashdiagnostics/CrashDiagnostics.cpp +++ b/Tactility/Source/app/crashdiagnostics/CrashDiagnostics.cpp @@ -69,7 +69,7 @@ public: TT_LOG_I(TAG, "Create canvas"); int32_t available_height = parent_height - top_label_height - bottom_label_height; int32_t available_width = lv_display_get_horizontal_resolution(display); - int32_t smallest_size = TT_MIN(available_height, available_width); + int32_t smallest_size = std::min(available_height, available_width); int32_t pixel_size; if (qrcode.size * 2 <= smallest_size) { pixel_size = 2; diff --git a/Tactility/Source/app/files/View.cpp b/Tactility/Source/app/files/View.cpp index e1370dd6..f06755f8 100644 --- a/Tactility/Source/app/files/View.cpp +++ b/Tactility/Source/app/files/View.cpp @@ -229,12 +229,13 @@ void View::update() { auto scoped_lockable = lvgl::getLvglSyncLockable()->scoped(); if (scoped_lockable->lock(100 / portTICK_PERIOD_MS)) { lv_obj_clean(dir_entry_list); - auto entries = state->lockEntries(); - for (auto entry : entries) { - TT_LOG_D(TAG, "Entry: %s %d", entry.d_name, entry.d_type); - createDirEntryWidget(dir_entry_list, entry); - } - state->unlockEntries(); + + state->withEntries([this](const std::vector& entries) { + for (auto entry : entries) { + TT_LOG_D(TAG, "Entry: %s %d", entry.d_name, entry.d_type); + createDirEntryWidget(dir_entry_list, entry); + } + }); if (state->getCurrentPath() == "/") { lv_obj_add_flag(navigate_up_button, LV_OBJ_FLAG_HIDDEN); diff --git a/Tactility/Source/app/launcher/Launcher.cpp b/Tactility/Source/app/launcher/Launcher.cpp index 7a79808f..9ba563a8 100644 --- a/Tactility/Source/app/launcher/Launcher.cpp +++ b/Tactility/Source/app/launcher/Launcher.cpp @@ -1,9 +1,12 @@ +#include "Check.h" +#include "Tactility.h" #include "app/AppContext.h" #include "app/ManifestRegistry.h" -#include "Check.h" #include "lvgl.h" #include "service/loader/Loader.h" +#define TAG "launcher" + namespace tt::app::launcher { static void onAppPressed(TT_UNUSED lv_event_t* e) { @@ -44,6 +47,14 @@ static lv_obj_t* createAppButton(lv_obj_t* parent, const char* title, const char class LauncherApp : public App { + void onStart(TT_UNUSED AppContext& app) override { + auto* config = tt::getConfiguration(); + if (!config->autoStartAppId.empty()) { + TT_LOG_I(TAG, "auto-starting %s", config->autoStartAppId.c_str()); + tt::service::loader::startApp(config->autoStartAppId); + } + } + void onShow(TT_UNUSED AppContext& app, lv_obj_t* parent) override { auto* wrapper = lv_obj_create(parent); @@ -64,7 +75,7 @@ class LauncherApp : public App { } int32_t available_width = lv_display_get_horizontal_resolution(display) - (3 * 80); - int32_t padding = is_landscape_display ? TT_MIN(available_width / 4, 64) : 0; + int32_t padding = is_landscape_display ? std::min(available_width / 4, (int32_t)64) : 0; auto paths = app.getPaths(); auto apps_icon_path = paths->getSystemPathLvgl("icon_apps.png"); diff --git a/Tactility/Source/app/log/Log.cpp b/Tactility/Source/app/log/Log.cpp index e3217b8e..b6f49e10 100644 --- a/Tactility/Source/app/log/Log.cpp +++ b/Tactility/Source/app/log/Log.cpp @@ -1,5 +1,3 @@ -#include -#include #include "lvgl.h" #include "lvgl/Style.h" #include "lvgl/Toolbar.h" @@ -7,6 +5,10 @@ #include "service/loader/Loader.h" #include "lvgl/LvglSync.h" +#include +#include +#include + #define TAG "text_viewer" namespace tt::app::log { @@ -18,39 +20,36 @@ private: LogLevel filterLevel = LogLevel::Info; lv_obj_t* labelWidget = nullptr; - static bool shouldShowLog(LogLevel filterLevel, LogLevel logLevel) { - if (filterLevel == LogLevel::None || logLevel == LogLevel::None) { - return false; - } else { - return filterLevel >= logLevel; - } + static inline bool shouldShowLog(LogLevel filterLevel, LogLevel logLevel) { + return (filterLevel != LogLevel::None) && + (logLevel != LogLevel::None) && + filterLevel >= logLevel; } void updateLogEntries() { - unsigned int index; - auto* entries = copyLogEntries(index); + std::size_t next_log_index; + auto entries = copyLogEntries(next_log_index); std::stringstream buffer; - if (entries != nullptr) { - for (unsigned int i = index; i < TT_LOG_ENTRY_COUNT; ++i) { - if (shouldShowLog(filterLevel, entries[i].level)) { - buffer << entries[i].message; + + if (next_log_index != 0) { + long to_drop = TT_LOG_ENTRY_COUNT - next_log_index; + for (auto entry : std::views::drop(*entries, (long)next_log_index)) { + if (shouldShowLog(filterLevel, entry.level) && entry.message[0] != 0x00) { + buffer << entry.message; } } - if (index != 0) { - for (unsigned int i = 0; i < index; ++i) { - if (shouldShowLog(filterLevel, entries[i].level)) { - buffer << entries[i].message; - } - } - } - delete entries; - if (!buffer.str().empty()) { - lv_label_set_text(labelWidget, buffer.str().c_str()); - } else { - lv_label_set_text(labelWidget, "No logs for the selected log level"); + } + + for (auto entry : std::views::take(*entries, (long)next_log_index)) { + if (shouldShowLog(filterLevel, entry.level) && entry.message[0] != 0x00) { + buffer << entry.message; } + } + + if (!buffer.str().empty()) { + lv_label_set_text(labelWidget, buffer.str().c_str()); } else { - lv_label_set_text(labelWidget, "Failed to load log"); + lv_label_set_text(labelWidget, "No logs for the selected log level"); } } diff --git a/Tactility/Source/app/wificonnect/State.cpp b/Tactility/Source/app/wificonnect/State.cpp index f179eaf2..1a8dad0b 100644 --- a/Tactility/Source/app/wificonnect/State.cpp +++ b/Tactility/Source/app/wificonnect/State.cpp @@ -22,15 +22,6 @@ void State::setApSettings(const service::wifi::settings::WifiApSettings* newSett lock.unlock(); } -const service::wifi::settings::WifiApSettings& State::lockApSettings() { - lock.lock(); - return apSettings; -} - -void State::unlockApSettings() { - lock.unlock(); -} - void State::setConnecting(bool isConnecting) { lock.lock(); connecting = isConnecting; diff --git a/Tactility/Source/app/wificonnect/View.cpp b/Tactility/Source/app/wificonnect/View.cpp index 28a6b771..68745336 100644 --- a/Tactility/Source/app/wificonnect/View.cpp +++ b/Tactility/Source/app/wificonnect/View.cpp @@ -55,6 +55,7 @@ static void onConnect(TT_UNUSED lv_event_t* event) { service::wifi::settings::WifiApSettings settings; strcpy((char*)settings.password, password); strcpy((char*)settings.ssid, ssid); + settings.channel = 0; settings.auto_connect = TT_WIFI_AUTO_CONNECT; // No UI yet, so use global setting:w auto* bindings = &wifi->getBindings(); diff --git a/Tactility/Source/app/wifimanage/State.cpp b/Tactility/Source/app/wifimanage/State.cpp index b5b3d11c..c67bd67b 100644 --- a/Tactility/Source/app/wifimanage/State.cpp +++ b/Tactility/Source/app/wifimanage/State.cpp @@ -32,15 +32,6 @@ bool State::isScanning() const { return result; } -const std::vector& State::lockApRecords() const { - mutex.lock(); - return apRecords; -} - -void State::unlockApRecords() const { - mutex.unlock(); -} - void State::updateApRecords() { mutex.lock(); apRecords = service::wifi::getScanResults(); diff --git a/Tactility/Source/app/wifimanage/View.cpp b/Tactility/Source/app/wifimanage/View.cpp index 8534bd6a..36e5a3fc 100644 --- a/Tactility/Source/app/wifimanage/View.cpp +++ b/Tactility/Source/app/wifimanage/View.cpp @@ -130,16 +130,17 @@ void View::createSsidListItem(const service::wifi::ApRecord& record, bool isConn } void View::updateConnectToHidden() { + using enum service::wifi::RadioState; switch (state->getRadioState()) { - case service::wifi::RadioState::On: - case service::wifi::RadioState::ConnectionPending: - case service::wifi::RadioState::ConnectionActive: + case On: + case ConnectionPending: + case ConnectionActive: lv_obj_remove_flag(connect_to_hidden, LV_OBJ_FLAG_HIDDEN); break; - case service::wifi::RadioState::OnPending: - case service::wifi::RadioState::OffPending: - case service::wifi::RadioState::Off: + case OnPending: + case OffPending: + case Off: lv_obj_add_flag(connect_to_hidden, LV_OBJ_FLAG_HIDDEN); break; } @@ -149,20 +150,20 @@ void View::updateNetworkList() { lv_obj_clean(networks_list); switch (state->getRadioState()) { - case service::wifi::RadioState::OnPending: - case service::wifi::RadioState::On: - case service::wifi::RadioState::ConnectionPending: - case service::wifi::RadioState::ConnectionActive: { + using enum service::wifi::RadioState; + case OnPending: + case On: + case ConnectionPending: + case ConnectionActive: { std::string connection_target = service::wifi::getConnectionTarget(); - auto& ap_records = state->lockApRecords(); - bool is_connected = !connection_target.empty() && - state->getRadioState() == service::wifi::RadioState::ConnectionActive; - bool added_connected = false; - if (is_connected) { - if (!ap_records.empty()) { - for (auto &record : ap_records) { + state->withApRecords([this, &connection_target](const std::vector& apRecords){ + bool is_connected = !connection_target.empty() && + state->getRadioState() == service::wifi::RadioState::ConnectionActive; + bool added_connected = false; + if (is_connected && !apRecords.empty()) { + for (auto &record : apRecords) { if (record.ssid == connection_target) { lv_list_add_text(networks_list, "Connected"); createSsidListItem(record, false); @@ -171,38 +172,38 @@ void View::updateNetworkList() { } } } - } - lv_list_add_text(networks_list, "Other networks"); - std::set used_ssids; - if (!ap_records.empty()) { - for (auto& record : ap_records) { - if (used_ssids.find(record.ssid) == used_ssids.end()) { - bool connection_target_match = (record.ssid == connection_target); - bool is_connecting = connection_target_match - && state->getRadioState() == service::wifi::RadioState::ConnectionPending && - !connection_target.empty(); - bool skip = connection_target_match && added_connected; - if (!skip) { - createSsidListItem(record, is_connecting); + lv_list_add_text(networks_list, "Other networks"); + std::set used_ssids; + if (!apRecords.empty()) { + for (auto& record : apRecords) { + if (used_ssids.find(record.ssid) == used_ssids.end()) { + bool connection_target_match = (record.ssid == connection_target); + bool is_connecting = connection_target_match + && state->getRadioState() == service::wifi::RadioState::ConnectionPending && + !connection_target.empty(); + bool skip = connection_target_match && added_connected; + if (!skip) { + createSsidListItem(record, is_connecting); + } + used_ssids.insert(record.ssid); } - used_ssids.insert(record.ssid); } + lv_obj_clear_flag(networks_list, LV_OBJ_FLAG_HIDDEN); + } else if (!state->hasScannedAfterRadioOn() || state->isScanning()) { + // hasScannedAfterRadioOn() prevents briefly showing "No networks found" when turning radio on. + lv_obj_add_flag(networks_list, LV_OBJ_FLAG_HIDDEN); + } else { + lv_obj_clear_flag(networks_list, LV_OBJ_FLAG_HIDDEN); + lv_obj_t* label = lv_label_create(networks_list); + lv_label_set_text(label, "No networks found."); } - lv_obj_clear_flag(networks_list, LV_OBJ_FLAG_HIDDEN); - } else if (!state->hasScannedAfterRadioOn() || state->isScanning()) { - // hasScannedAfterRadioOn() prevents briefly showing "No networks found" when turning radio on. - lv_obj_add_flag(networks_list, LV_OBJ_FLAG_HIDDEN); - } else { - lv_obj_clear_flag(networks_list, LV_OBJ_FLAG_HIDDEN); - lv_obj_t* label = lv_label_create(networks_list); - lv_label_set_text(label, "No networks found."); - } - state->unlockApRecords(); + }); + break; } - case service::wifi::RadioState::OffPending: - case service::wifi::RadioState::Off: { + case OffPending: + case Off: { lv_obj_add_flag(networks_list, LV_OBJ_FLAG_HIDDEN); break; } @@ -220,18 +221,19 @@ void View::updateScanning() { void View::updateWifiToggle() { lv_obj_clear_state(enable_switch, LV_STATE_ANY); switch (state->getRadioState()) { - case service::wifi::RadioState::On: - case service::wifi::RadioState::ConnectionPending: - case service::wifi::RadioState::ConnectionActive: + using enum service::wifi::RadioState; + case On: + case ConnectionPending: + case ConnectionActive: lv_obj_add_state(enable_switch, LV_STATE_CHECKED); break; - case service::wifi::RadioState::OnPending: + case OnPending: lv_obj_add_state(enable_switch, LV_STATE_CHECKED | LV_STATE_DISABLED); break; - case service::wifi::RadioState::Off: + case Off: lv_obj_remove_state(enable_switch, LV_STATE_CHECKED | LV_STATE_DISABLED); break; - case service::wifi::RadioState::OffPending: + case OffPending: lv_obj_remove_state(enable_switch, LV_STATE_CHECKED); lv_obj_add_state(enable_switch, LV_STATE_DISABLED); break; diff --git a/Tactility/Source/app/wifimanage/WifiManage.cpp b/Tactility/Source/app/wifimanage/WifiManage.cpp index 50066d17..15f2c2c7 100644 --- a/Tactility/Source/app/wifimanage/WifiManage.cpp +++ b/Tactility/Source/app/wifimanage/WifiManage.cpp @@ -91,14 +91,15 @@ static void wifiManageEventCallback(const void* message, void* context) { TT_LOG_I(TAG, "Update with state %s", service::wifi::radioStateToString(radio_state)); wifi->getState().setRadioState(radio_state); switch (event->type) { - case tt::service::wifi::EventType::ScanStarted: + using enum tt::service::wifi::EventType; + case ScanStarted: wifi->getState().setScanning(true); break; - case tt::service::wifi::EventType::ScanFinished: + case ScanFinished: wifi->getState().setScanning(false); wifi->getState().updateApRecords(); break; - case tt::service::wifi::EventType::RadioStateOn: + case RadioStateOn: if (!service::wifi::isScanning()) { service::wifi::scan(); } diff --git a/Tactility/Source/service/loader/Loader.cpp b/Tactility/Source/service/loader/Loader.cpp index e6d3be08..9ef87162 100644 --- a/Tactility/Source/service/loader/Loader.cpp +++ b/Tactility/Source/service/loader/Loader.cpp @@ -85,15 +85,16 @@ std::shared_ptr getPubsub() { static const char* appStateToString(app::State state) { switch (state) { - case app::StateInitial: + using enum app::State; + case Initial: return "initial"; - case app::StateStarted: + case Started: return "started"; - case app::StateShowing: + case Showing: return "showing"; - case app::StateHiding: + case Hiding: return "hiding"; - case app::StateStopped: + case Stopped: return "stopped"; default: return "?"; @@ -113,31 +114,29 @@ static void transitionAppToState(std::shared_ptr app, app::Sta ); switch (state) { - case app::StateInitial: - app->setState(app::StateInitial); + using enum app::State; + case Initial: break; - case app::StateStarted: + case Started: app->getApp()->onStart(*app); - app->setState(app::StateStarted); break; - case app::StateShowing: { + case Showing: { LoaderEvent event_showing = { .type = LoaderEventTypeApplicationShowing }; loader_singleton->pubsubExternal->publish(&event_showing); - app->setState(app::StateShowing); break; } - case app::StateHiding: { + case Hiding: { LoaderEvent event_hiding = { .type = LoaderEventTypeApplicationHiding }; loader_singleton->pubsubExternal->publish(&event_hiding); - app->setState(app::StateHiding); break; } - case app::StateStopped: + case Stopped: // TODO: Verify manifest app->getApp()->onStop(*app); - app->setState(app::StateStopped); break; } + + app->setState(state); } static LoaderStatus startAppWithManifestInternal( @@ -160,15 +159,15 @@ static LoaderStatus startAppWithManifestInternal( new_app->mutableFlags().showStatusbar = (manifest->type != app::Type::Boot); loader_singleton->appStack.push(new_app); - transitionAppToState(new_app, app::StateInitial); - transitionAppToState(new_app, app::StateStarted); + transitionAppToState(new_app, app::State::Initial); + transitionAppToState(new_app, app::State::Started); // We might have to hide the previous app first if (previous_app != nullptr) { - transitionAppToState(previous_app, app::StateHiding); + transitionAppToState(previous_app, app::State::Hiding); } - transitionAppToState(new_app, app::StateShowing); + transitionAppToState(new_app, app::State::Showing); LoaderEvent event_external = { .type = LoaderEventTypeApplicationStarted }; loader_singleton->pubsubExternal->publish(&event_external); @@ -230,8 +229,8 @@ static void stopAppInternal() { result_set = true; } - transitionAppToState(app_to_stop, app::StateHiding); - transitionAppToState(app_to_stop, app::StateStopped); + transitionAppToState(app_to_stop, app::State::Hiding); + transitionAppToState(app_to_stop, app::State::Stopped); loader_singleton->appStack.pop(); @@ -254,7 +253,7 @@ static void stopAppInternal() { if (!loader_singleton->appStack.empty()) { instance_to_resume = loader_singleton->appStack.top(); assert(instance_to_resume); - transitionAppToState(instance_to_resume, app::StateShowing); + transitionAppToState(instance_to_resume, app::State::Showing); } // Unlock so that we can send results to app and they can also start/stop new apps while processing these results diff --git a/Tactility/Source/service/screenshot/Screenshot.cpp b/Tactility/Source/service/screenshot/Screenshot.cpp index 05ffc459..e25e0aae 100644 --- a/Tactility/Source/service/screenshot/Screenshot.cpp +++ b/Tactility/Source/service/screenshot/Screenshot.cpp @@ -18,7 +18,7 @@ std::shared_ptr _Nullable optScreenshotService() { return service::findServiceById(manifest.id); } -void ScreenshotService::startApps(const char* path) { +void ScreenshotService::startApps(const std::string& path) { auto scoped_lockable = mutex.scoped(); if (!scoped_lockable->lock(50 / portTICK_PERIOD_MS)) { TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); @@ -34,7 +34,7 @@ void ScreenshotService::startApps(const char* path) { } } -void ScreenshotService::startTimed(const char* path, uint8_t delayInSeconds, uint8_t amount) { +void ScreenshotService::startTimed(const std::string& path, uint8_t delayInSeconds, uint8_t amount) { auto scoped_lockable = mutex.scoped(); if (!scoped_lockable->lock(50 / portTICK_PERIOD_MS)) { TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); diff --git a/Tactility/Source/service/screenshot/Screenshot.h b/Tactility/Source/service/screenshot/Screenshot.h index 53384fad..08866c19 100644 --- a/Tactility/Source/service/screenshot/Screenshot.h +++ b/Tactility/Source/service/screenshot/Screenshot.h @@ -35,14 +35,14 @@ public: /** @brief Start taking screenshot whenever an app is started * @param[in] path the path to store the screenshots at */ - void startApps(const char* path); + void startApps(const std::string& path); /** @brief Start taking screenshots after a certain delay * @param[in] path the path to store the screenshots at * @param[in] delayInSeconds the delay before starting (and between successive screenshots) * @param[in] amount 0 = indefinite, >0 for a specific */ - void startTimed(const char* path, uint8_t delayInSeconds, uint8_t amount); + void startTimed(const std::string& path, uint8_t delayInSeconds, uint8_t amount); /** @brief Stop taking screenshots */ void stop(); diff --git a/Tactility/Source/service/screenshot/ScreenshotTask.cpp b/Tactility/Source/service/screenshot/ScreenshotTask.cpp index 92665075..412a5fd9 100644 --- a/Tactility/Source/service/screenshot/ScreenshotTask.cpp +++ b/Tactility/Source/service/screenshot/ScreenshotTask.cpp @@ -2,11 +2,10 @@ #if TT_FEATURE_SCREENSHOT_ENABLED -#include #include "ScreenshotTask.h" #include "lv_screenshot.h" +#include -#include "app/AppContext.h" #include "TactilityCore.h" #include "service/loader/Loader.h" #include "lvgl/LvglSync.h" @@ -45,12 +44,12 @@ void ScreenshotTask::setFinished() { finished = true; } -static void makeScreenshot(const char* filename) { +static void makeScreenshot(const std::string& filename) { if (lvgl::lock(50 / portTICK_PERIOD_MS)) { - if (lv_screenshot_create(lv_scr_act(), LV_100ASK_SCREENSHOT_SV_PNG, filename)) { - TT_LOG_I(TAG, "Screenshot saved to %s", filename); + if (lv_screenshot_create(lv_scr_act(), LV_100ASK_SCREENSHOT_SV_PNG, filename.c_str())) { + TT_LOG_I(TAG, "Screenshot saved to %s", filename.c_str()); } else { - TT_LOG_E(TAG, "Screenshot not saved to %s", filename); + TT_LOG_E(TAG, "Screenshot not saved to %s", filename.c_str()); } lvgl::unlock(); } else { @@ -78,8 +77,7 @@ void ScreenshotTask::taskMain() { if (!isInterrupted()) { screenshots_taken++; - char filename[SCREENSHOT_PATH_LIMIT + 32]; - sprintf(filename, "%s/screenshot-%d.png", work.path, screenshots_taken); + std::string filename = std::format("{}/screenshot-{}.png", work.path, screenshots_taken); makeScreenshot(filename); if (work.amount > 0 && screenshots_taken >= work.amount) { @@ -93,8 +91,7 @@ void ScreenshotTask::taskMain() { if (manifest.id != last_app_id) { kernel::delayMillis(100); last_app_id = manifest.id; - char filename[SCREENSHOT_PATH_LIMIT + 32]; - sprintf(filename, "%s/screenshot-%s.png", work.path, manifest.id.c_str()); + auto filename = std::format("{}/screenshot-{}.png", work.path, manifest.id); makeScreenshot(filename); } } @@ -123,9 +120,7 @@ void ScreenshotTask::taskStart() { thread->start(); } -void ScreenshotTask::startApps(const char* path) { - tt_check(strlen(path) < (SCREENSHOT_PATH_LIMIT - 1)); - +void ScreenshotTask::startApps(const std::string& path) { auto scoped_lockable = mutex.scoped(); if (!scoped_lockable->lock(50 / portTICK_PERIOD_MS)) { TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); @@ -135,15 +130,14 @@ void ScreenshotTask::startApps(const char* path) { if (thread == nullptr) { interrupted = false; work.type = TASK_WORK_TYPE_APPS; - strcpy(work.path, path); + work.path = path; taskStart(); } else { TT_LOG_E(TAG, "Task was already running"); } } -void ScreenshotTask::startTimed(const char* path, uint8_t delay_in_seconds, uint8_t amount) { - tt_check(strlen(path) < (SCREENSHOT_PATH_LIMIT - 1)); +void ScreenshotTask::startTimed(const std::string& path, uint8_t delay_in_seconds, uint8_t amount) { auto scoped_lockable = mutex.scoped(); if (!scoped_lockable->lock(50 / portTICK_PERIOD_MS)) { TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); @@ -155,7 +149,7 @@ void ScreenshotTask::startTimed(const char* path, uint8_t delay_in_seconds, uint work.type = TASK_WORK_TYPE_DELAY; work.delay_in_seconds = delay_in_seconds; work.amount = amount; - strcpy(work.path, path); + work.path = path; taskStart(); } else { TT_LOG_E(TAG, "Task was already running"); diff --git a/Tactility/Source/service/screenshot/ScreenshotTask.h b/Tactility/Source/service/screenshot/ScreenshotTask.h index 3cb2291e..5500cb45 100644 --- a/Tactility/Source/service/screenshot/ScreenshotTask.h +++ b/Tactility/Source/service/screenshot/ScreenshotTask.h @@ -13,15 +13,13 @@ namespace tt::service::screenshot { #define TASK_WORK_TYPE_DELAY 1 #define TASK_WORK_TYPE_APPS 2 -#define SCREENSHOT_PATH_LIMIT 128 - class ScreenshotTask { struct ScreenshotTaskWork { int type = TASK_WORK_TYPE_DELAY ; uint8_t delay_in_seconds = 0; uint8_t amount = 0; - char path[SCREENSHOT_PATH_LIMIT] = { 0 }; + std::string path; }; Thread* thread = nullptr; @@ -39,12 +37,12 @@ public: * @param[in] delayInSeconds the delay before starting (and between successive screenshots) * @param[in] amount 0 = indefinite, >0 for a specific */ - void startTimed(const char* path, uint8_t delayInSeconds, uint8_t amount); + void startTimed(const std::string& path, uint8_t delayInSeconds, uint8_t amount); /** @brief Start taking screenshot whenever an app is started * @param[in] path the path to store the screenshots at */ - void startApps(const char* path); + void startApps(const std::string& path); /** @brief Stop taking screenshots */ void stop(); diff --git a/Tactility/Source/service/statusbar/Statusbar.cpp b/Tactility/Source/service/statusbar/Statusbar.cpp index adb21f68..0f6fc709 100644 --- a/Tactility/Source/service/statusbar/Statusbar.cpp +++ b/Tactility/Source/service/statusbar/Statusbar.cpp @@ -54,14 +54,15 @@ const char* getWifiStatusIconForRssi(int rssi) { static const char* getWifiStatusIcon(wifi::RadioState state, bool secure) { int rssi; switch (state) { - case wifi::RadioState::On: - case wifi::RadioState::OnPending: - case wifi::RadioState::ConnectionPending: + using enum wifi::RadioState; + case On: + case OnPending: + case ConnectionPending: return STATUSBAR_ICON_WIFI_SCAN_WHITE; - case wifi::RadioState::OffPending: - case wifi::RadioState::Off: + case OffPending: + case Off: return STATUSBAR_ICON_WIFI_OFF_WHITE; - case wifi::RadioState::ConnectionActive: + case ConnectionActive: rssi = wifi::getRssi(); return getWifiStatusIconForRssi(rssi); default: @@ -71,11 +72,12 @@ static const char* getWifiStatusIcon(wifi::RadioState state, bool secure) { static const char* getSdCardStatusIcon(hal::SdCard::State state) { switch (state) { - case hal::SdCard::State::Mounted: + using enum hal::SdCard::State; + case Mounted: return STATUSBAR_ICON_SDCARD; - case hal::SdCard::State::Error: - case hal::SdCard::State::Unmounted: - case hal::SdCard::State::Unknown: + case Error: + case Unmounted: + case Unknown: return STATUSBAR_ICON_SDCARD_ALERT; default: tt_crash("Unhandled SdCard state"); diff --git a/TactilityC/Source/tt_semaphore.cpp b/TactilityC/Source/tt_semaphore.cpp index 405bf712..6fb87d5b 100644 --- a/TactilityC/Source/tt_semaphore.cpp +++ b/TactilityC/Source/tt_semaphore.cpp @@ -22,7 +22,7 @@ bool tt_semaphore_release(SemaphoreHandle handle) { } uint32_t tt_semaphore_get_count(SemaphoreHandle handle) { - return HANDLE_AS_SEMAPHORE(handle)->getCount(); + return HANDLE_AS_SEMAPHORE(handle)->getAvailable(); } } diff --git a/TactilityCore/Source/CoreExtraDefines.h b/TactilityCore/Source/CoreExtraDefines.h index 75167c24..fa260b09 100644 --- a/TactilityCore/Source/CoreExtraDefines.h +++ b/TactilityCore/Source/CoreExtraDefines.h @@ -1,37 +1,3 @@ #pragma once -/** Find the largest value - * @param[in] a first value to compare - * @param[in] b second value to compare - * @return the largest value of a and b - */ -#define TT_MAX(a, b) \ - ({ \ - __typeof__(a) _a = (a); \ - __typeof__(b) _b = (b); \ - _a > _b ? _a : _b; \ - }) - -/** Find the smallest value - * @param[in] a first value to compare - * @param[in] b second value to compare - * @return the smallest value of a and b - */ -#define TT_MIN(a, b) \ - ({ \ - __typeof__(a) _a = (a); \ - __typeof__(b) _b = (b); \ - _a < _b ? _a : _b; \ - }) - -/** @return the absolute value of the input */ -#define TT_ABS(a) ({ (a) < 0 ? -(a) : (a); }) - -/** Clamp a value between a min and a max. - * @param[in] x value to clamp - * @param[in] upper upper bounds for x - * @param[in] lower lower bounds for x - */ -#define TT_CLAMP(x, upper, lower) (TT_MIN(upper, TT_MAX(x, lower))) - #define TT_STRINGIFY(x) #x diff --git a/TactilityCore/Source/Dispatcher.h b/TactilityCore/Source/Dispatcher.h index 2645e9ea..8bb70542 100644 --- a/TactilityCore/Source/Dispatcher.h +++ b/TactilityCore/Source/Dispatcher.h @@ -13,7 +13,6 @@ namespace tt { - /** * A thread-safe way to defer code execution. * Generally, one task would dispatch the execution, diff --git a/TactilityCore/Source/Lockable.h b/TactilityCore/Source/Lockable.h index d43a2325..1ccf03bc 100644 --- a/TactilityCore/Source/Lockable.h +++ b/TactilityCore/Source/Lockable.h @@ -12,18 +12,41 @@ class ScopedLockableUsage; /** Represents a lock/mutex */ class Lockable { + public: + virtual ~Lockable() = default; - virtual bool lock(TickType_t timeoutTicks) const = 0; + virtual bool lock(TickType_t timeout) const = 0; - virtual bool lock() const { return lock(portMAX_DELAY); } + bool lock() const { return lock(portMAX_DELAY); } virtual bool unlock() const = 0; + void withLock(TickType_t timeout, const std::function& onLockAcquired) const { + if (lock(timeout)) { + onLockAcquired(); + unlock(); + } + } + + void withLock(TickType_t timeout, const std::function& onLockAcquired, const std::function& onLockFailure) const { + if (lock(timeout)) { + onLockAcquired(); + unlock(); + } else { + onLockFailure(); + } + } + + void withLock(const std::function& onLockAcquired) const { withLock(portMAX_DELAY, onLockAcquired); } + + void withLock(const std::function& onLockAcquired, const std::function& onLockFailed) const { withLock(portMAX_DELAY, onLockAcquired, onLockFailed); } + std::unique_ptr scoped() const; }; + /** * Represents a lockable instance that is scoped to a specific lifecycle. * Once the ScopedLockableUsage is destroyed, unlock() is called automatically. @@ -37,6 +60,8 @@ class ScopedLockableUsage final : public Lockable { public: + using Lockable::lock; + explicit ScopedLockableUsage(const Lockable& lockable) : lockable(lockable) {} ~ScopedLockableUsage() final { @@ -47,8 +72,6 @@ public: return lockable.lock(timeout); } - bool lock() const override { return lock(portMAX_DELAY); } - bool unlock() const override { return lockable.unlock(); } diff --git a/TactilityCore/Source/Log.cpp b/TactilityCore/Source/Log.cpp index ed27ab6d..3e8ce8e0 100644 --- a/TactilityCore/Source/Log.cpp +++ b/TactilityCore/Source/Log.cpp @@ -4,8 +4,8 @@ namespace tt { -static LogEntry* logEntries = nullptr; -static unsigned int nextLogEntryIndex; +static std::array logEntries; +static size_t nextLogEntryIndex; /** * This used to be a simple static value, but that crashes on device boot where early logging happens. @@ -20,18 +20,8 @@ Mutex& getLogMutex() { return *logMutex; } -static void ensureLogEntriesExist() { - if (logEntries == nullptr) { - logEntries = new LogEntry[TT_LOG_ENTRY_COUNT]; - assert(logEntries != nullptr); - nextLogEntryIndex = 0; - } -} - static void storeLog(LogLevel level, const char* format, va_list args) { if (getLogMutex().lock(5 / portTICK_PERIOD_MS)) { - ensureLogEntriesExist(); - logEntries[nextLogEntryIndex].level = level; vsnprintf(logEntries[nextLogEntryIndex].message, TT_LOG_MESSAGE_SIZE, format, args); @@ -44,16 +34,12 @@ static void storeLog(LogLevel level, const char* format, va_list args) { } } -LogEntry* copyLogEntries(unsigned int& outIndex) { +std::unique_ptr> copyLogEntries(std::size_t& outIndex) { if (getLogMutex().lock(5 / portTICK_PERIOD_MS)) { - auto* newEntries = new LogEntry[TT_LOG_ENTRY_COUNT]; - assert(newEntries != nullptr); - for (int i = 0; i < TT_LOG_ENTRY_COUNT; ++i) { - memcpy(&newEntries[i], &logEntries[i], sizeof(LogEntry)); - } - outIndex = nextLogEntryIndex; + auto copy = std::make_unique>(logEntries); getLogMutex().unlock(); - return newEntries; + outIndex = nextLogEntryIndex; + return copy; } else { return nullptr; } diff --git a/TactilityCore/Source/Log.h b/TactilityCore/Source/Log.h index fa3470a2..3eeef134 100644 --- a/TactilityCore/Source/Log.h +++ b/TactilityCore/Source/Log.h @@ -1,6 +1,8 @@ #pragma once #include "LogMessages.h" +#include +#include #ifdef ESP_PLATFORM #include @@ -38,7 +40,7 @@ struct LogEntry { * The array size is TT_LOG_ENTRY_COUNT * @param[out] outIndex the write index for the next log entry. */ -LogEntry* copyLogEntries(unsigned int& outIndex); +std::unique_ptr> copyLogEntries(std::size_t& outIndex); } // namespace tt diff --git a/TactilityCore/Source/Mutex.h b/TactilityCore/Source/Mutex.h index c3306b80..71cc41f3 100644 --- a/TactilityCore/Source/Mutex.h +++ b/TactilityCore/Source/Mutex.h @@ -39,24 +39,21 @@ private: public: + using Lockable::lock; + explicit Mutex(Type type = Type::Normal); - ~Mutex() override = default; + ~Mutex() final = default; /** Attempt to lock the mutex. Blocks until timeout passes or lock is acquired. * @param[in] timeout * @return success result */ - bool lock(TickType_t timeout) const override; - - /** Attempt to lock the mutex. Blocks until lock is acquired, without timeout. - * @return success result - */ - bool lock() const override { return lock(portMAX_DELAY); } + bool lock(TickType_t timeout) const final; /** Attempt to unlock the mutex. * @return success result */ - bool unlock() const override; + bool unlock() const final; /** @return the owner of the thread */ ThreadId getOwner() const; diff --git a/TactilityCore/Source/Semaphore.cpp b/TactilityCore/Source/Semaphore.cpp index 4b0d2b23..fc6d010c 100644 --- a/TactilityCore/Source/Semaphore.cpp +++ b/TactilityCore/Source/Semaphore.cpp @@ -4,7 +4,7 @@ namespace tt { -static inline struct QueueDefinition* createHandle(uint32_t maxCount, uint32_t initialCount) { +static inline QueueHandle_t createHandle(uint32_t maxCount, uint32_t initialCount) { assert((maxCount > 0U) && (initialCount <= maxCount)); if (maxCount == 1U) { @@ -21,7 +21,7 @@ static inline struct QueueDefinition* createHandle(uint32_t maxCount, uint32_t i } } -Semaphore::Semaphore(uint32_t maxCount, uint32_t initialCount) : handle(createHandle(maxCount, initialCount)){ +Semaphore::Semaphore(uint32_t maxAvailable, uint32_t initialAvailable) : handle(createHandle(maxAvailable, initialAvailable)) { assert(!TT_IS_IRQ_MODE()); tt_check(handle != nullptr); } @@ -30,7 +30,7 @@ Semaphore::~Semaphore() { assert(!TT_IS_IRQ_MODE()); } -bool Semaphore::acquire(uint32_t timeout) const { +bool Semaphore::acquire(TickType_t timeout) const { if (TT_IS_IRQ_MODE()) { if (timeout != 0U) { return false; @@ -45,7 +45,7 @@ bool Semaphore::acquire(uint32_t timeout) const { } } } else { - return xSemaphoreTake(handle.get(), (TickType_t)timeout) == pdPASS; + return xSemaphoreTake(handle.get(), timeout) == pdPASS; } } @@ -63,13 +63,13 @@ bool Semaphore::release() const { } } -uint32_t Semaphore::getCount() const { +uint32_t Semaphore::getAvailable() const { if (TT_IS_IRQ_MODE()) { // TODO: uxSemaphoreGetCountFromISR is not supported on esp-idf 5.1.2 - perhaps later on? #ifdef uxSemaphoreGetCountFromISR return uxSemaphoreGetCountFromISR(handle.get()); #else - return uxQueueMessagesWaitingFromISR((QueueHandle_t)hSemaphore); + return uxQueueMessagesWaitingFromISR(handle.get()); #endif } else { return uxSemaphoreGetCount(handle.get()); diff --git a/TactilityCore/Source/Semaphore.h b/TactilityCore/Source/Semaphore.h index ee249492..fe05c1a0 100644 --- a/TactilityCore/Source/Semaphore.h +++ b/TactilityCore/Source/Semaphore.h @@ -1,6 +1,6 @@ #pragma once -#include "Thread.h" +#include "Lockable.h" #include #include @@ -18,7 +18,7 @@ namespace tt { * Wrapper for xSemaphoreCreateBinary (max count == 1) and xSemaphoreCreateCounting (max count > 1) * Can be used from IRQ/ISR mode, but cannot be created/destroyed from such a context. */ -class Semaphore { +class Semaphore final : public Lockable { private: @@ -32,24 +32,39 @@ private: std::unique_ptr, SemaphoreHandleDeleter> handle; public: + + using Lockable::lock; + /** * Cannot be called from IRQ/ISR mode. - * @param[in] maxCount The maximum count - * @param[in] initialCount The initial count + * @param[in] maxAvailable The maximum count + * @param[in] initialAvailable The initial count */ - Semaphore(uint32_t maxCount, uint32_t initialCount); + Semaphore(uint32_t maxAvailable, uint32_t initialAvailable); + + /** + * Cannot be called from IRQ/ISR mode. + * @param[in] maxAvailable The maximum count + */ + explicit Semaphore(uint32_t maxAvailable) : Semaphore(maxAvailable, maxAvailable) {}; /** Cannot be called from IRQ/ISR mode. */ - ~Semaphore(); + ~Semaphore() override; + + Semaphore(Semaphore& other) : handle(std::move(other.handle)) {} /** Acquire semaphore */ - bool acquire(uint32_t timeout) const; + bool acquire(TickType_t timeout) const; /** Release semaphore */ bool release() const; - /** @return semaphore count */ - uint32_t getCount() const; + bool lock(TickType_t timeout) const override { return acquire(timeout); } + + bool unlock() const override { return release(); } + + /** @return return the amount of times this semaphore can be acquired/locked */ + uint32_t getAvailable() const; }; } // namespace diff --git a/TactilityCore/Source/StringUtils.cpp b/TactilityCore/Source/StringUtils.cpp index e5db7d0f..095c3413 100644 --- a/TactilityCore/Source/StringUtils.cpp +++ b/TactilityCore/Source/StringUtils.cpp @@ -1,21 +1,13 @@ #include "StringUtils.h" #include #include +#include namespace tt::string { -int findLastIndex(const char* text, size_t from_index, char find) { - for (int i = (int)from_index; i >= 0; i--) { - if (text[i] == find) { - return (int)i; - } - } - return -1; -} - bool getPathParent(const std::string& path, std::string& output) { - int index = findLastIndex(path.c_str(), path.length() - 1, '/'); - if (index == -1) { + auto index = path.find_last_of('/'); + if (index == std::string::npos) { return false; } else if (index == 0) { output = "/"; diff --git a/TactilityCore/Source/StringUtils.h b/TactilityCore/Source/StringUtils.h index 7c229698..c8eb84d1 100644 --- a/TactilityCore/Source/StringUtils.h +++ b/TactilityCore/Source/StringUtils.h @@ -7,15 +7,6 @@ namespace tt::string { -/** - * Find the last occurrence of a character. - * @param[in] text the text to search in - * @param[in] from_index the index to search from (searching from right to left) - * @param[in] find the character to search for - * @return the index of the found character, or -1 if none found - */ -int findLastIndex(const char* text, size_t from_index, char find); - /** * Given a filesystem path as input, try and get the parent path. * @param[in] path input path diff --git a/TactilityCore/Source/kernel/critical/Critical.cpp b/TactilityCore/Source/kernel/critical/Critical.cpp index 7c5cd2f7..169ce846 100644 --- a/TactilityCore/Source/kernel/critical/Critical.cpp +++ b/TactilityCore/Source/kernel/critical/Critical.cpp @@ -11,8 +11,8 @@ static portMUX_TYPE critical_mutex; namespace tt::kernel::critical { -TtCriticalInfo enter() { - TtCriticalInfo info = { +CriticalInfo enter() { + CriticalInfo info = { .isrm = 0, .fromIsr = TT_IS_ISR(), .kernelRunning = (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) @@ -29,7 +29,7 @@ TtCriticalInfo enter() { return info; } -void exit(TtCriticalInfo info) { +void exit(CriticalInfo info) { if (info.fromIsr) { taskEXIT_CRITICAL_FROM_ISR(info.isrm); } else if (info.kernelRunning) { diff --git a/TactilityCore/Source/kernel/critical/Critical.h b/TactilityCore/Source/kernel/critical/Critical.h index 890be92c..465f8e13 100644 --- a/TactilityCore/Source/kernel/critical/Critical.h +++ b/TactilityCore/Source/kernel/critical/Critical.h @@ -4,21 +4,21 @@ namespace tt::kernel::critical { -typedef struct { +struct CriticalInfo { uint32_t isrm; bool fromIsr; bool kernelRunning; -} TtCriticalInfo; +}; /** Enter a critical section * @return info on the status */ -TtCriticalInfo enter(); +CriticalInfo enter(); /** * Exit a critical section * @param[in] info the info from when the critical section was started */ -void exit(TtCriticalInfo info); +void exit(CriticalInfo info); } // namespace diff --git a/TactilityHeadless/Source/TactilityHeadless.cpp b/TactilityHeadless/Source/TactilityHeadless.cpp index f59ed52a..f4076cf6 100644 --- a/TactilityHeadless/Source/TactilityHeadless.cpp +++ b/TactilityHeadless/Source/TactilityHeadless.cpp @@ -23,7 +23,7 @@ static Dispatcher mainDispatcher; static const hal::Configuration* hardwareConfig = nullptr; -static void register_and_start_system_services() { +static void registerAndStartSystemServices() { TT_LOG_I(TAG, "Registering and starting system services"); addService(service::sdcard::manifest); addService(service::wifi::manifest); @@ -38,7 +38,7 @@ void initHeadless(const hal::Configuration& config) { time::init(); hal::init(config); network::ntp::init(); - register_and_start_system_services(); + registerAndStartSystemServices(); } diff --git a/TactilityHeadless/Source/hal/i2c/I2c.cpp b/TactilityHeadless/Source/hal/i2c/I2c.cpp index 3490c1eb..7aa2193e 100644 --- a/TactilityHeadless/Source/hal/i2c/I2c.cpp +++ b/TactilityHeadless/Source/hal/i2c/I2c.cpp @@ -23,11 +23,12 @@ static Data dataArray[I2C_NUM_MAX]; const char* initModeToString(InitMode mode) { switch (mode) { - case InitMode::ByTactility: + using enum InitMode; + case ByTactility: return TT_STRINGIFY(InitMode::ByTactility); - case InitMode::ByExternal: + case ByExternal: return TT_STRINGIFY(InitMode::ByExternal); - case InitMode::Disabled: + case Disabled: return TT_STRINGIFY(InitMode::Disabled); } tt_crash("not implemented"); diff --git a/TactilityHeadless/Source/kernel/SystemEvents.cpp b/TactilityHeadless/Source/kernel/SystemEvents.cpp index bb9b053f..8a6a185b 100644 --- a/TactilityHeadless/Source/kernel/SystemEvents.cpp +++ b/TactilityHeadless/Source/kernel/SystemEvents.cpp @@ -19,26 +19,27 @@ static std::list subscriptions; static const char* getEventName(SystemEvent event) { switch (event) { - case SystemEvent::BootInitHalBegin: - return TT_STRINGIFY(SystemEvent::BootInitHalBegin); - case SystemEvent::BootInitHalEnd: - return TT_STRINGIFY(SystemEvent::BootInitHalEnd); - case SystemEvent::BootInitI2cBegin: - return TT_STRINGIFY(SystemEvent::BootInitI2cBegin); - case SystemEvent::BootInitI2cEnd: - return TT_STRINGIFY(SystemEvent::BootInitI2cEnd); - case SystemEvent::BootInitLvglBegin: - return TT_STRINGIFY(SystemEvent::BootInitLvglBegin); - case SystemEvent::BootInitLvglEnd: - return TT_STRINGIFY(SystemEvent::BootInitLvglEnd); - case SystemEvent::BootSplash: - return TT_STRINGIFY(SystemEvent::BootSplash); - case SystemEvent::NetworkConnected: - return TT_STRINGIFY(SystemEvent::NetworkConnected); - case SystemEvent::NetworkDisconnected: - return TT_STRINGIFY(SystemEvent::NetworkDisconnected); - case SystemEvent::Time: - return TT_STRINGIFY(SystemEvent::Time); + using enum SystemEvent; + case BootInitHalBegin: + return TT_STRINGIFY(BootInitHalBegin); + case BootInitHalEnd: + return TT_STRINGIFY(BootInitHalEnd); + case BootInitI2cBegin: + return TT_STRINGIFY(BootInitI2cBegin); + case BootInitI2cEnd: + return TT_STRINGIFY(BootInitI2cEnd); + case BootInitLvglBegin: + return TT_STRINGIFY(BootInitLvglBegin); + case BootInitLvglEnd: + return TT_STRINGIFY(BootInitLvglEnd); + case BootSplash: + return TT_STRINGIFY(BootSplash); + case NetworkConnected: + return TT_STRINGIFY(NetworkConnected); + case NetworkDisconnected: + return TT_STRINGIFY(NetworkDisconnected); + case Time: + return TT_STRINGIFY(Time); } tt_crash(); // Missing case above diff --git a/TactilityHeadless/Source/service/wifi/Wifi.cpp b/TactilityHeadless/Source/service/wifi/Wifi.cpp index 20715312..74989bfc 100644 --- a/TactilityHeadless/Source/service/wifi/Wifi.cpp +++ b/TactilityHeadless/Source/service/wifi/Wifi.cpp @@ -4,18 +4,19 @@ namespace tt::service::wifi { const char* radioStateToString(RadioState state) { switch (state) { - case RadioState::OnPending: - return TT_STRINGIFY(RadioState::OnPending); - case RadioState::On: - return TT_STRINGIFY(RadioState::On); - case RadioState::ConnectionPending: - return TT_STRINGIFY(RadioState::ConnectionPending); - case RadioState::ConnectionActive: - return TT_STRINGIFY(RadioState::ConnectionActive); - case RadioState::OffPending: - return TT_STRINGIFY(RadioState::OnPending); - case RadioState::Off: - return TT_STRINGIFY(RadioState::Off); + using enum RadioState; + case OnPending: + return TT_STRINGIFY(OnPending); + case On: + return TT_STRINGIFY(On); + case ConnectionPending: + return TT_STRINGIFY(ConnectionPending); + case ConnectionActive: + return TT_STRINGIFY(ConnectionActive); + case OffPending: + return TT_STRINGIFY(OnPending); + case Off: + return TT_STRINGIFY(Off); } tt_crash("not implemented"); } diff --git a/TactilityHeadless/Source/service/wifi/Wifi.h b/TactilityHeadless/Source/service/wifi/Wifi.h index 8b4831af..6c32b4ab 100644 --- a/TactilityHeadless/Source/service/wifi/Wifi.h +++ b/TactilityHeadless/Source/service/wifi/Wifi.h @@ -69,6 +69,7 @@ struct Event { struct ApRecord { std::string ssid; int8_t rssi; + int32_t channel; wifi_auth_mode_t auth_mode; }; diff --git a/TactilityHeadless/Source/service/wifi/WifiEsp.cpp b/TactilityHeadless/Source/service/wifi/WifiEsp.cpp index c0294b03..ff209156 100644 --- a/TactilityHeadless/Source/service/wifi/WifiEsp.cpp +++ b/TactilityHeadless/Source/service/wifi/WifiEsp.cpp @@ -268,6 +268,7 @@ std::vector getScanResults() { records.push_back((ApRecord) { .ssid = (const char*)wifi->scan_list[i].ssid, .rssi = wifi->scan_list[i].rssi, + .channel = wifi->scan_list[i].primary, .auth_mode = wifi->scan_list[i].authmode }); } @@ -389,7 +390,7 @@ static bool copy_scan_list(std::shared_ptr wifi) { uint16_t record_count = wifi->scan_list_limit; esp_err_t scan_result = esp_wifi_scan_get_ap_records(&record_count, wifi->scan_list); if (scan_result == ESP_OK) { - uint16_t safe_record_count = TT_MIN(wifi->scan_list_limit, record_count); + uint16_t safe_record_count = std::min(wifi->scan_list_limit, record_count); wifi->scan_list_count = safe_record_count; TT_LOG_I(TAG, "Scanned %u APs. Showing %u:", record_count, safe_record_count); for (uint16_t i = 0; i < safe_record_count; i++) { @@ -720,65 +721,24 @@ static void dispatchConnect(std::shared_ptr context) { publish_event_simple(wifi, EventType::ConnectionPending); - wifi_config_t wifi_config = { - .sta = { - /* Authmode threshold resets to WPA2 as default if password matches WPA2 standards (pasword len => 8). - * If you want to connect the device to deprecated WEP/WPA networks, Please set the threshold value - * to WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK and set the password with length and format matching to - * WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK standards. - */ - .ssid = {0}, - .password = {0}, - .scan_method = WIFI_ALL_CHANNEL_SCAN, - .bssid_set = false, - .bssid = { 0 }, - .channel = 0, - .listen_interval = 0, - .sort_method = WIFI_CONNECT_AP_BY_SIGNAL, - .threshold = { - .rssi = 0, - .authmode = WIFI_AUTH_OPEN, - .rssi_5g_adjustment = 0 - }, - .pmf_cfg = { - .capable = false, - .required = false - }, - .rm_enabled = 0, - .btm_enabled = 0, - .mbo_enabled = 0, - .ft_enabled = 0, - .owe_enabled = 0, - .transition_disable = 0, - .reserved = 0, - .sae_pwe_h2e = WPA3_SAE_PWE_BOTH, - .sae_pk_mode = WPA3_SAE_PK_MODE_AUTOMATIC, - .failure_retry_cnt = 1, - .he_dcm_set = 0, - .he_dcm_max_constellation_tx = 0, - .he_dcm_max_constellation_rx = 0, - .he_mcs9_enabled = 0, - .he_su_beamformee_disabled = 0, - .he_trig_su_bmforming_feedback_disabled = 0, - .he_trig_mu_bmforming_partial_feedback_disabled = 0, - .he_trig_cqi_feedback_disabled = 0, - .he_reserved = 0, - .sae_h2e_identifier = {0}, - } - }; + wifi_config_t config; + memset(&config, 0, sizeof(wifi_config_t)); + config.sta.channel = wifi_singleton->connection_target.channel; + config.sta.scan_method = WIFI_FAST_SCAN; + config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL; + config.sta.threshold.rssi = -127; + config.sta.pmf_cfg.capable = true; - static_assert(sizeof(wifi_config.sta.ssid) == (sizeof(wifi_singleton->connection_target.ssid)-1), "SSID size mismatch"); - memcpy(wifi_config.sta.ssid, wifi_singleton->connection_target.ssid, sizeof(wifi_config.sta.ssid)); - memcpy(wifi_config.sta.password, wifi_singleton->connection_target.password, sizeof(wifi_config.sta.password)); + static_assert(sizeof(config.sta.ssid) == (sizeof(wifi_singleton->connection_target.ssid)-1), "SSID size mismatch"); + memcpy(config.sta.ssid, wifi_singleton->connection_target.ssid, sizeof(config.sta.ssid)); - if (wifi_singleton->connection_target.password[0] != 0x00U) { - wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_WPA3_PSK; - } else { - wifi_config.sta.threshold.authmode = WIFI_AUTH_OPEN; + if (wifi_singleton->connection_target.password[0] != 0x00) { + memcpy(config.sta.password, wifi_singleton->connection_target.password, sizeof(config.sta.password)); + config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK; } TT_LOG_I(TAG, "esp_wifi_set_config()"); - esp_err_t set_config_result = esp_wifi_set_config(WIFI_IF_STA, &wifi_config); + esp_err_t set_config_result = esp_wifi_set_config(WIFI_IF_STA, &config); if (set_config_result != ESP_OK) { wifi->setRadioState(RadioState::On); TT_LOG_E(TAG, "Failed to set wifi config (%s)", esp_err_to_name(set_config_result)); @@ -802,7 +762,7 @@ static void dispatchConnect(std::shared_ptr context) { TT_LOG_I(TAG, "Waiting for EventFlag by event_handler()"); if (bits & WIFI_CONNECTED_BIT) { - wifi->setSecureConnection(wifi_config.sta.password[0] != 0x00U); + wifi->setSecureConnection(config.sta.password[0] != 0x00U); wifi->setRadioState(RadioState::ConnectionActive); publish_event_simple(wifi, EventType::ConnectionSuccess); TT_LOG_I(TAG, "Connected to %s", wifi->connection_target.ssid); @@ -842,49 +802,15 @@ static void dispatchDisconnectButKeepActive(std::shared_ptr context) { return; } - wifi_config_t wifi_config = { - .sta = { - .ssid = {0}, - .password = {0}, - .scan_method = WIFI_ALL_CHANNEL_SCAN, - .bssid_set = false, - .bssid = { 0 }, - .channel = 0, - .listen_interval = 0, - .sort_method = WIFI_CONNECT_AP_BY_SIGNAL, - .threshold = { - .rssi = 0, - .authmode = WIFI_AUTH_OPEN, - .rssi_5g_adjustment = 0 - }, - .pmf_cfg = { - .capable = false, - .required = false, - }, - .rm_enabled = false, - .btm_enabled = false, - .mbo_enabled = false, - .ft_enabled = false, - .owe_enabled = false, - .transition_disable = false, - .reserved = 0, - .sae_pwe_h2e = WPA3_SAE_PWE_UNSPECIFIED, - .sae_pk_mode = WPA3_SAE_PK_MODE_AUTOMATIC, - .failure_retry_cnt = 0, - .he_dcm_set = false, - .he_dcm_max_constellation_tx = false, - .he_dcm_max_constellation_rx = false, - .he_mcs9_enabled = false, - .he_su_beamformee_disabled = false, - .he_trig_su_bmforming_feedback_disabled = false, - .he_trig_mu_bmforming_partial_feedback_disabled = false, - .he_trig_cqi_feedback_disabled = false, - .he_reserved = 0, - .sae_h2e_identifier = {0}, - }, - }; + wifi_config_t config; + memset(&config, 0, sizeof(wifi_config_t)); + config.sta.channel = wifi_singleton->connection_target.channel; + config.sta.scan_method = WIFI_ALL_CHANNEL_SCAN; + config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL; + config.sta.threshold.rssi = -127; + config.sta.pmf_cfg.capable = true; - esp_err_t set_config_result = esp_wifi_set_config(WIFI_IF_STA, &wifi_config); + esp_err_t set_config_result = esp_wifi_set_config(WIFI_IF_STA, &config); if (set_config_result != ESP_OK) { // TODO: disable radio, because radio state is in limbo between off and on wifi->setRadioState(RadioState::Off); @@ -948,7 +874,7 @@ public: wifi_singleton->autoConnectTimer = std::make_unique(Timer::Type::Periodic, onAutoConnectTimer, wifi_singleton); // We want to try and scan more often in case of startup or scan lock failure - wifi_singleton->autoConnectTimer->start(TT_MIN(2000, AUTO_SCAN_INTERVAL)); + wifi_singleton->autoConnectTimer->start(std::min(2000, AUTO_SCAN_INTERVAL)); if (settings::shouldEnableOnBoot()) { TT_LOG_I(TAG, "Auto-enabling due to setting"); diff --git a/TactilityHeadless/Source/service/wifi/WifiSettings.h b/TactilityHeadless/Source/service/wifi/WifiSettings.h index 468370fa..553eb47b 100644 --- a/TactilityHeadless/Source/service/wifi/WifiSettings.h +++ b/TactilityHeadless/Source/service/wifi/WifiSettings.h @@ -1,6 +1,7 @@ #pragma once #include "WifiGlobals.h" +#include namespace tt::service::wifi::settings { @@ -13,6 +14,7 @@ namespace tt::service::wifi::settings { struct WifiApSettings { char ssid[TT_WIFI_SSID_LIMIT + 1] = { 0 }; char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT + 1] = { 0 }; + int32_t channel = 0; bool auto_connect = true; }; diff --git a/Tests/TactilityCore/LockableTest.cpp b/Tests/TactilityCore/LockableTest.cpp new file mode 100644 index 00000000..dd39f57e --- /dev/null +++ b/Tests/TactilityCore/LockableTest.cpp @@ -0,0 +1,39 @@ +#include "Lockable.h" +#include "Semaphore.h" +#include "doctest.h" +#include + +using namespace tt; + +TEST_CASE("withLock() locks correctly on Semaphore") { + auto semaphore = std::make_shared(2U); + semaphore->withLock([semaphore](){ + CHECK_EQ(semaphore->getAvailable(), 1); + }); +} + +TEST_CASE("withLock() unlocks correctly on Semaphore") { + auto semaphore = std::make_shared(2U); + semaphore->withLock([=](){ + // NO-OP + }); + + CHECK_EQ(semaphore->getAvailable(), 2); +} + +TEST_CASE("withLock() locks correctly on Mutex") { + auto mutex = std::make_shared(); + mutex->withLock([mutex](){ + CHECK_EQ(mutex->lock(1), false); + }); +} + +TEST_CASE("withLock() unlocks correctly on Mutex") { + auto mutex = std::make_shared(); + mutex->withLock([=](){ + // NO-OP + }); + + CHECK_EQ(mutex->lock(1), true); + CHECK_EQ(mutex->unlock(), true); +} diff --git a/Tests/TactilityCore/MutexTest.cpp b/Tests/TactilityCore/MutexTest.cpp index 908a5adf..2c01eefb 100644 --- a/Tests/TactilityCore/MutexTest.cpp +++ b/Tests/TactilityCore/MutexTest.cpp @@ -32,3 +32,22 @@ TEST_CASE("a mutex can block a thread") { thread.join(); } + +TEST_CASE("a Mutex can be locked exactly once") { + auto mutex = Mutex(Mutex::Type::Normal); + CHECK_EQ(mutex.lock(0), true); + CHECK_EQ(mutex.lock(0), false); + CHECK_EQ(mutex.unlock(), true); +} + +TEST_CASE("unlocking a Mutex without locking returns false") { + auto mutex = Mutex(Mutex::Type::Normal); + CHECK_EQ(mutex.unlock(), false); +} + +TEST_CASE("unlocking a Mutex twice returns false on the second attempt") { + auto mutex = Mutex(Mutex::Type::Normal); + CHECK_EQ(mutex.lock(0), true); + CHECK_EQ(mutex.unlock(), true); + CHECK_EQ(mutex.unlock(), false); +} diff --git a/Tests/TactilityCore/SemaphoreTest.cpp b/Tests/TactilityCore/SemaphoreTest.cpp new file mode 100644 index 00000000..f6e42bac --- /dev/null +++ b/Tests/TactilityCore/SemaphoreTest.cpp @@ -0,0 +1,35 @@ +#include "doctest.h" +#include "Semaphore.h" + +using namespace tt; + +// We want a distinct test for 1 item, because it creates the Semaphore differently +TEST_CASE("a Semaphore with max count of 1 can be acquired exactly once") { + auto semaphore = Semaphore(1); + CHECK_EQ(semaphore.acquire(0), true); + CHECK_EQ(semaphore.getAvailable(), 0); + CHECK_EQ(semaphore.acquire(0), false); + CHECK_EQ(semaphore.release(), true); + CHECK_EQ(semaphore.getAvailable(), 1); +} + +TEST_CASE("a Semaphore with max count of 2 can be acquired exactly twice") { + auto semaphore = Semaphore(2); + CHECK_EQ(semaphore.acquire(0), true); + CHECK_EQ(semaphore.getAvailable(), 1); + CHECK_EQ(semaphore.acquire(0), true); + CHECK_EQ(semaphore.getAvailable(), 0); + CHECK_EQ(semaphore.acquire(0), false); + CHECK_EQ(semaphore.release(), true); + CHECK_EQ(semaphore.getAvailable(), 1); + CHECK_EQ(semaphore.release(), true); + CHECK_EQ(semaphore.getAvailable(), 2); +} + +TEST_CASE("the semaphore count should be correct initially") { + auto semaphore_a = Semaphore(2); + CHECK_EQ(semaphore_a.getAvailable(), 2); + + auto semaphore_b = Semaphore(2, 0); + CHECK_EQ(semaphore_b.getAvailable(), 0); +}