Compare commits

..

No commits in common. "57164ad6c809d85378e745fb005b03254628d8b0" and "75be23eca28a27f0283c0dd7f9d1ddd746fb0d92" have entirely different histories.

56 changed files with 424 additions and 523 deletions

View File

@ -1,2 +0,0 @@
launcherAppId=Launcher
#autoStartAppId=

2
Data/data/test.txt Normal file
View File

@ -0,0 +1,2 @@
This file exists to test the partition.
It can be deleted when the partition contains other files.

View File

@ -1,49 +1,40 @@
# TODOs
## Higher Priority
- Store encrypted WiFi credentials in `/data/app/wifi/x.ap.properties` (fixes problem with long SSIDs)
- Move WiFi settings from flash to `/data/apps/wifi/wifi.properties` (just the "start on boot")
- Move Development settings from flash to `/data/apps/development/development.properties` (just the "start on boot")
- Move Display settings from flash to `/data/apps/display/display.properties`
- App data directory should be automatically created (and then we can remove the custom code from Notes.cpp)
- When an external app fails to load (e.g. due to mapping error) then show an error dialog.
- Expose app::Paths to TactilityC
- Call tt::lvgl::isSyncSet after HAL init and show an error (and crash?) when it is not set.
- External app loading: Check the version of Tactility and check ESP target hardware to check for compatibility.
- Localization of texts (load in boot app from sd?)
- App packaging
- Create more unit tests for `tactility-core`
- Make a URL handler. Use it for handling local files. Match file types with apps.
## Lower Priority
- Support hot-plugging SD card
- Create more unit tests for `tactility`
- Explore LVGL9's FreeRTOS functionality
- CrashHandler: use "corrupted" flag
- CrashHandler: process other types of crashes (WDT?)
- Add a Keyboard setting in `keyboard.properties` to override the behaviour of soft keyboard hiding (e.g. keyboard hardware is present, but the user wants to use a soft keyboard)
- Use GPS time to set/update the current time
- All drivers (e.g. display, touch, etc.) should call stop() in their destructor, or at least assert that they should not be running.
- 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.
- Revisit TinyUSB mouse idea: the bugs related to cleanup seem to be fixed in the library.
- Bug: When a Wi-Fi SSID is too long, then it fails to save the credentials
- Add a Keyboard setting app to override the behaviour of soft keyboard hiding (e.g. keyboard hardware is present, but the user wants to use a soft keyboard)
- HAL for display touch calibration
- Start using non_null (either via MS GSL, or custom)
- `hal/Configuration.h` defines C function types: Use C++ std::function instead
- Fix system time to not be 1980 (use build year as a minimum). Consider keeping track of the last known time.
- 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.
- Mutex: Implement give/take from ISR support (works only for non-recursive ones)
- Extend unPhone power driver: add charging status, usb connection status, etc.
- Expose app::Paths to TactilityC
- CrashHandler: use "corrupted" flag
- CrashHandler: process other types of crashes (WDT?)
- Call tt::lvgl::isSyncSet after HAL init and show an error (and crash?) when it is not set.
- Create different partition files for different ESP flash size targets (N4, N8, N16, N32)
- T-Deck: Clear screen before turning on blacklight
- T-Deck: Use knob for UI selection?
- Crash monitoring: Keep track of which system phase the app crashed in (e.g. which app in which state)
- App::onResult should pass the app id (or launch request id!) that was started, so we can differentiate between multiple types of apps being launched
- Create more unit tests for `tactility-core` and `tactility` (PC-only for now)
- Show a warning screen if firmware encryption or secure boot are off when saving WiFi credentials.
- Show a warning screen when a user plugs in the SD card on a device that only supports mounting at boot.
- Localisation of texts (load in boot app from sd?)
- Explore LVGL9's FreeRTOS functionality
- External app loading: Check version of Tactility and check ESP target hardware to check for compatibility.
- Scanning SD card for external apps and auto-register them (in a temporary register?)
- Support hot-plugging SD card
- All drivers (e.g. display, touch, etc.) should call stop() in their destructor, or at least assert that they should not be running.
- Use GPS time to set/update the current time
- Remove flex_flow from app_container in Gui.cpp
# Nice-to-haves
- Considering the lack of callstack debugging for external apps: allow for some debugging to be exposed during a device crash. Apps could report their state (e.g. an integer value) which can be stored during app operation and retrieve after crash. The same can be done for various OS apps and states. We can keep an array of these numbers to keep track of the last X states, to get an idea of what's going on.
- Give external app a different icon. Allow an external app to update their id, icon, type and name once they are running (and persist that info?). Loader will need to be able to find app by (external) location.
- Audio player app
- Audio recording app
@ -60,7 +51,6 @@
# App Ideas
- Revisit TinyUSB mouse idea: the bugs related to cleanup seem to be fixed in the library.
- Map widget:
https://github.com/portapack-mayhem/mayhem-firmware/blob/b66d8b1aa178d8a9cd06436fea788d5d58cb4c8d/firmware/application/ui/ui_geomap.cpp
https://github.com/portapack-mayhem/mayhem-firmware/blob/b66d8b1aa178d8a9cd06436fea788d5d58cb4c8d/firmware/tools/generate_world_map.bin.py
@ -77,4 +67,3 @@
- Compile unix tools to ELF apps?
- Todo list
- Calendar
- Display touch calibration

View File

@ -1,18 +0,0 @@
#pragma once
#include <string>
namespace tt {
struct BootProperties {
/** App to start automatically after the splash screen. */
std::string launcherAppId;
/** App to start automatically from the launcher screen. */
std::string autoStartAppId;
};
bool loadBootProperties(BootProperties& properties);
bool saveBootProperties(const BootProperties& properties);
}

View File

@ -19,6 +19,10 @@ struct Configuration {
const std::vector<const app::AppManifest*> apps = {};
/** List of user services */
const std::vector<const service::ServiceManifest*> services = {};
/** Optional app to start automatically after the splash screen. */
const std::string launcherAppId = app::launcher::manifest.id;
/** Optional app to start automatically after the splash screen. */
const std::string autoStartAppId = {};
};
/**

View File

@ -1,9 +1,15 @@
#pragma once
#include "Tactility/app/AppRegistration.h"
#include "Tactility/app/ManifestRegistry.h"
#include <Tactility/CoreDefines.h>
#include <Tactility/Bundle.h>
#include <string>
// Forward declarations
typedef struct _lv_obj_t lv_obj_t;
namespace tt::app {
class App;
@ -34,6 +40,8 @@ enum class Result {
class Location {
private:
std::string path;
Location() = default;
explicit Location(const std::string& path) : path(path) {}
@ -46,13 +54,7 @@ public:
return Location(path);
}
/** Internal apps are all apps that are part of the firmware release. */
bool isInternal() const { return path.empty(); }
/**
* External apps are all apps that are not part of the firmware release.
* e.g. an application on the sd card or one that is installed in /data
*/
bool isExternal() const { return !path.empty(); }
const std::string& getPath() const { return path; }
};

View File

@ -1,7 +0,0 @@
#pragma once
namespace tt::app::notes {
void start(const std::string& filePath);
}

View File

@ -0,0 +1,7 @@
#pragma once
namespace tt::app::textviewer {
void start(const std::string& file);
}

View File

@ -1,14 +1,17 @@
#pragma once
#include "./WifiGlobals.h"
#include "./WifiSettings.h"
#include <Tactility/PubSub.h>
#include <cstdio>
#include <string>
#include <vector>
#include "WifiApSettings.h"
#ifdef ESP_PLATFORM
#include "esp_wifi.h"
#include "WifiSettings.h"
#else
#include <cstdint>
// From esp_wifi_types.h in ESP-IDF 5.2
@ -121,7 +124,7 @@ std::string getIp();
* @param[in] ap
* @param[in] remember whether to save the ap data to the settings upon successful connection
*/
void connect(const settings::WifiApSettings& ap, bool remember);
void connect(const settings::WifiApSettings* ap, bool remember);
/** @brief Disconnect from the access point. Doesn't have any effect when not connected. */
void disconnect();

View File

@ -1,37 +0,0 @@
#pragma once
#include <string>
namespace tt::service::wifi::settings {
/**
* This struct is stored as-is into NVS flash.
*
* The SSID and secret are increased by 1 byte to facilitate string null termination.
* This makes it easier to use the char array as a string in various places.
*/
struct WifiApSettings {
std::string ssid;
std::string password;
bool autoConnect;
int32_t channel;
WifiApSettings(
std::string ssid,
std::string password,
bool autoConnect = true,
int32_t channel = 0
) : ssid(ssid), password(password), autoConnect(autoConnect), channel(channel) {}
WifiApSettings() : ssid(""), password(""), autoConnect(true), channel(0) {}
};
bool contains(const std::string& ssid);
bool load(const std::string& ssid, WifiApSettings& settings);
bool save(const WifiApSettings& settings);
bool remove(const std::string& ssid);
} // namespace

View File

@ -1,7 +1,31 @@
#pragma once
#include "WifiGlobals.h"
#include <cstdint>
namespace tt::service::wifi::settings {
/**
* This struct is stored as-is into NVS flash.
*
* The SSID and secret are increased by 1 byte to facilitate string null termination.
* This makes it easier to use the char array as a string in various places.
*/
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;
};
bool contains(const char* ssid);
bool load(const char* ssid, WifiApSettings* settings);
bool save(const WifiApSettings* settings);
bool remove(const char* ssid);
void setEnableOnBoot(bool enable);
bool shouldEnableOnBoot();

View File

@ -1,10 +1,10 @@
#pragma once
#include <Tactility/service/wifi/WifiApSettings.h>
#include "Tactility/service/wifi/WifiSettings.h"
namespace tt::app::wificonnect {
typedef void (*OnConnectSsid)(const service::wifi::settings::WifiApSettings& settings, bool store, void* context);
typedef void (*OnConnectSsid)(const service::wifi::settings::WifiApSettings* settings, bool store, void* context);
typedef struct {
OnConnectSsid onConnectSsid;

View File

@ -1,13 +1,18 @@
#pragma once
#include <Tactility/Mutex.h>
#include <Tactility/service/wifi/WifiApSettings.h>
#include <Tactility/service/wifi/Wifi.h>
#include <Tactility/service/wifi/WifiSettings.h>
namespace tt::app::wificonnect {
class State {
Mutex lock;
service::wifi::settings::WifiApSettings apSettings;
service::wifi::settings::WifiApSettings apSettings = {
.ssid = { 0 },
.password = { 0 },
.auto_connect = false
};
bool connectionError = false;
bool connecting = false;
public:
@ -15,7 +20,7 @@ public:
void setConnectionError(bool error);
bool hasConnectionError() const;
void setApSettings(const service::wifi::settings::WifiApSettings& newSettings);
void setApSettings(const service::wifi::settings::WifiApSettings* newSettings);
void setConnecting(bool isConnecting);
bool isConnecting() const;

View File

@ -13,6 +13,8 @@ class WifiConnect;
class View {
private:
Bindings* bindings;
State* state;

View File

@ -12,6 +12,8 @@ namespace tt::app::wificonnect {
class WifiConnect : public App {
private:
Mutex mutex;
State state;
Bindings bindings = {

View File

@ -1,36 +0,0 @@
#include "Tactility/BootProperties.h"
#include <Tactility/LogEsp.h>
#include <Tactility/file/PropertiesFile.h>
namespace tt {
constexpr auto* TAG = "BootProperties";
constexpr auto* PROPERTIES_FILE = "/data/boot.properties";
constexpr auto* PROPERTIES_KEY_LAUNCHER_APP_ID = "launcherAppId";
constexpr auto* PROPERTIES_KEY_AUTO_START_APP_ID = "autoStartAppId";
bool loadBootProperties(BootProperties& properties) {
if (!file::loadPropertiesFile(PROPERTIES_FILE, [&properties](auto& key, auto& value) {
if (key == PROPERTIES_KEY_AUTO_START_APP_ID) {
properties.autoStartAppId = value;
} else if (key == PROPERTIES_KEY_LAUNCHER_APP_ID) {
properties.launcherAppId = value;
}
})) {
TT_LOG_E(TAG, "Failed to load %s", PROPERTIES_FILE);
return false;
};
return !properties.launcherAppId.empty();
}
bool saveBootProperties(const BootProperties& bootProperties) {
assert(!bootProperties.launcherAppId.empty());
std::map<std::string, std::string> properties;
properties[PROPERTIES_KEY_AUTO_START_APP_ID] = bootProperties.autoStartAppId;
properties[PROPERTIES_KEY_LAUNCHER_APP_ID] = bootProperties.launcherAppId;
return file::savePropertiesFile(PROPERTIES_FILE, properties);
}
}

View File

@ -1,11 +1,11 @@
#include "Tactility/Tactility.h"
#include "Tactility/app/AppRegistration.h"
#include "Tactility/app/ManifestRegistry.h"
#include "Tactility/lvgl/LvglPrivate.h"
#include "Tactility/service/ServiceManifest.h"
#include <Tactility/TactilityHeadless.h>
#include <Tactility/service/ServiceRegistration.h>
#include <Tactility/service/ServiceRegistry.h>
#include <Tactility/service/loader/Loader.h>
namespace tt {
@ -54,6 +54,7 @@ namespace app {
namespace serialconsole { extern const AppManifest manifest; }
namespace settings { extern const AppManifest manifest; }
namespace systeminfo { extern const AppManifest manifest; }
namespace textviewer { extern const AppManifest manifest; }
namespace timedatesettings { extern const AppManifest manifest; }
namespace timezone { extern const AppManifest manifest; }
namespace usbsettings { extern const AppManifest manifest; }
@ -95,6 +96,7 @@ static void registerSystemApps() {
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);

View File

@ -3,7 +3,7 @@
#include "Tactility/hal/Hal_i.h"
#include "Tactility/network/NtpPrivate.h"
#include "Tactility/service/ServiceManifest.h"
#include "Tactility/service/ServiceRegistration.h"
#include "Tactility/service/ServiceRegistry.h"
#include <Tactility/Dispatcher.h>
#include <Tactility/time/TimePrivate.h>

View File

@ -1,11 +1,9 @@
#include "Tactility/app/AppRegistration.h"
#include "Tactility/app/ManifestRegistry.h"
#include "Tactility/app/AppManifest.h"
#include <Tactility/Mutex.h>
#include <unordered_map>
#include <Tactility/file/File.h>
#include <sys/stat.h>
#define TAG "app"
@ -16,20 +14,12 @@ typedef std::unordered_map<std::string, std::shared_ptr<AppManifest>> AppManifes
static AppManifestMap app_manifest_map;
static Mutex hash_mutex(Mutex::Type::Normal);
void ensureAppPathsExist(const AppManifest& manifest) {
std::string path = "/data/app/" + manifest.id;
if (!file::findOrCreateDirectory(path, 0777)) {
TT_LOG_E(TAG, "Failed to create app directory: %s", path.c_str());
}
}
void addApp(const AppManifest& manifest) {
TT_LOG_I(TAG, "Registering manifest %s", manifest.id.c_str());
hash_mutex.lock();
if (!app_manifest_map.contains(manifest.id)) {
ensureAppPathsExist(manifest);
app_manifest_map[manifest.id] = std::make_shared<AppManifest>(manifest);
} else {
TT_LOG_E(TAG, "App id in use: %s", manifest.id.c_str());

View File

@ -1,4 +1,4 @@
#include "Tactility/app/AppRegistration.h"
#include "Tactility/app/ManifestRegistry.h"
#include "Tactility/service/loader/Loader.h"
#include "Tactility/lvgl/Toolbar.h"

View File

@ -11,7 +11,6 @@
#include <Tactility/kernel/SystemEvents.h>
#include <lvgl.h>
#include <Tactility/BootProperties.h>
#include <Tactility/CpuAffinity.h>
#ifdef ESP_PLATFORM
@ -106,14 +105,9 @@ class BootApp : public App {
}
#endif
BootProperties boot_properties;
if (!loadBootProperties(boot_properties) || boot_properties.launcherAppId.empty()) {
TT_LOG_E(TAG, "Launcher not configured");
stop();
return;
}
service::loader::startApp(boot_properties.launcherAppId);
const auto* config = getConfiguration();
assert(!config->launcherAppId.empty());
service::loader::startApp(config->launcherAppId);
}
public:

View File

@ -4,7 +4,7 @@
#include "Tactility/app/alertdialog/AlertDialog.h"
#include "Tactility/app/imageviewer/ImageViewer.h"
#include "Tactility/app/inputdialog/InputDialog.h"
#include "Tactility/app/notes/Notes.h"
#include "Tactility/app/textviewer/TextViewer.h"
#include "Tactility/app/ElfApp.h"
#include "Tactility/lvgl/Toolbar.h"
#include "Tactility/lvgl/LvglSync.h"
@ -95,10 +95,10 @@ void View::viewFile(const std::string& path, const std::string& filename) {
imageviewer::start(processed_filepath);
} else if (isSupportedTextFile(filename)) {
if (kernel::getPlatform() == kernel::PlatformEsp) {
notes::start(processed_filepath);
textviewer::start(processed_filepath);
} else {
// Remove forward slash, because we need a relative path
notes::start(processed_filepath.substr(1));
textviewer::start(processed_filepath.substr(1));
}
} else {
TT_LOG_W(TAG, "opening files of this type is not supported");

View File

@ -1,11 +1,11 @@
#include "Tactility/app/AppContext.h"
#include "Tactility/app/AppRegistration.h"
#include "Tactility/app/ManifestRegistry.h"
#include "Tactility/service/loader/Loader.h"
#include <Tactility/Check.h>
#include <Tactility/Tactility.h>
#include <lvgl.h>
#include <Tactility/BootProperties.h>
#define TAG "launcher"
@ -54,10 +54,10 @@ static lv_obj_t* createAppButton(lv_obj_t* parent, const char* title, const char
class LauncherApp : public App {
void onCreate(TT_UNUSED AppContext& app) override {
BootProperties boot_properties;
if (loadBootProperties(boot_properties) && !boot_properties.autoStartAppId.empty()) {
TT_LOG_I(TAG, "Starting %s", boot_properties.autoStartAppId.c_str());
service::loader::startApp(boot_properties.autoStartAppId);
auto* config = getConfiguration();
if (!config->autoStartAppId.empty()) {
TT_LOG_I(TAG, "auto-starting %s", config->autoStartAppId.c_str());
service::loader::startApp(config->autoStartAppId);
}
}

View File

@ -1,5 +1,6 @@
#include <Tactility/app/AppManifest.h>
#include <Tactility/file/File.h>
#include <Tactility/lvgl/Keyboard.h>
#include <Tactility/lvgl/Toolbar.h>
#include <Tactility/Assets.h>
#include <lvgl.h>
@ -7,12 +8,10 @@
#include <Tactility/app/fileselection/FileSelection.h>
#include <Tactility/hal/sdcard/SdCardDevice.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/service/loader/Loader.h>
namespace tt::app::notes {
constexpr auto* TAG = "Notes";
constexpr auto* NOTES_FILE_ARGUMENT = "file";
constexpr const char* TAG = "Notes";
class NotesApp : public App {
@ -110,20 +109,11 @@ class NotesApp : public App {
#pragma endregion Open_Events_Functions
void onCreate(AppContext& appContext) override {
auto parameters = appContext.getParameters();
std::string file_path;
if (parameters != nullptr && parameters->optString(NOTES_FILE_ARGUMENT, file_path)) {
if (!file_path.empty()) {
filePath = file_path;
}
}
}
void onShow(AppContext& context, lv_obj_t* parent) override {
lv_obj_remove_flag(parent, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lv_obj_t* toolbar = lvgl::toolbar_create(parent, context);
lv_obj_t* toolbar = tt::lvgl::toolbar_create(parent, context);
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
uiDropDownMenu = lv_dropdown_create(toolbar);
@ -178,8 +168,9 @@ class NotesApp : public App {
lv_label_set_text(uiCurrentFileName, "Untitled");
lv_obj_align(uiCurrentFileName, LV_ALIGN_CENTER, 0, 0);
if (!filePath.empty()) {
openFile(filePath);
//TODO: Move this to SD Card?
if (!file::findOrCreateDirectory(context.getPaths()->getDataDirectory(), 0777)) {
TT_LOG_E(TAG, "Failed to find or create path %s", context.getPaths()->getDataDirectory().c_str());
}
}
@ -211,9 +202,4 @@ extern const AppManifest manifest = {
.createApp = create<NotesApp>
};
void start(const std::string& filePath) {
auto parameters = std::make_shared<Bundle>();
parameters->putString(NOTES_FILE_ARGUMENT, filePath);
service::loader::startApp(manifest.id, parameters);
}
} // namespace tt::app::notes

View File

@ -1,4 +1,4 @@
#include "Tactility/app/AppRegistration.h"
#include "Tactility/app/ManifestRegistry.h"
#include "Tactility/lvgl/Toolbar.h"
#include "Tactility/service/loader/Loader.h"

View File

@ -0,0 +1,61 @@
#include "Tactility/lvgl/LabelUtils.h"
#include "Tactility/lvgl/Style.h"
#include "Tactility/lvgl/Toolbar.h"
#include "Tactility/service/loader/Loader.h"
#include <Tactility/TactilityCore.h>
#include <lvgl.h>
#define TAG "text_viewer"
#define TEXT_VIEWER_FILE_ARGUMENT "file"
namespace tt::app::textviewer {
class TextViewerApp : public App {
void onShow(AppContext& app, lv_obj_t* parent) override {
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lvgl::toolbar_create(parent, app);
auto* wrapper = lv_obj_create(parent);
lv_obj_set_width(wrapper, LV_PCT(100));
lv_obj_set_flex_grow(wrapper, 1);
lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_all(wrapper, 0, 0);
lvgl::obj_set_style_bg_invisible(wrapper);
auto* label = lv_label_create(wrapper);
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
auto parameters = app.getParameters();
tt_check(parameters != nullptr, "Parameters missing");
bool success = false;
std::string file_argument;
if (parameters->optString(TEXT_VIEWER_FILE_ARGUMENT, file_argument)) {
TT_LOG_I(TAG, "Opening %s", file_argument.c_str());
if (lvgl::label_set_text_file(label, file_argument.c_str())) {
success = true;
}
}
if (!success) {
lv_label_set_text_fmt(label, "Failed to load %s", file_argument.c_str());
}
}
};
extern const AppManifest manifest = {
.id = "TextViewer",
.name = "Text Viewer",
.type = Type::Hidden,
.createApp = create<TextViewerApp>
};
void start(const std::string& file) {
auto parameters = std::make_shared<Bundle>();
parameters->putString(TEXT_VIEWER_FILE_ARGUMENT, file);
service::loader::startApp(manifest.id, parameters);
}
} // namespace

View File

@ -44,10 +44,10 @@ static void onToggleAutoConnect(lv_event_t* event) {
bool is_on = lv_obj_has_state(enable_switch, LV_STATE_CHECKED);
std::string ssid = parameters->getString("ssid");
service::wifi::settings::WifiApSettings settings;
if (service::wifi::settings::load(ssid.c_str(), settings)) {
settings.autoConnect = is_on;
if (!service::wifi::settings::save(settings)) {
service::wifi::settings::WifiApSettings settings {};
if (service::wifi::settings::load(ssid.c_str(), &settings)) {
settings.auto_connect = is_on;
if (!service::wifi::settings::save(&settings)) {
TT_LOG_E(TAG, "Failed to save settings");
}
} else {
@ -98,9 +98,9 @@ class WifiApSettings : public App {
lv_obj_align(forget_button_label, LV_ALIGN_CENTER, 0, 0);
lv_label_set_text(forget_button_label, "Forget");
service::wifi::settings::WifiApSettings settings;
if (service::wifi::settings::load(ssid.c_str(), settings)) {
if (settings.autoConnect) {
service::wifi::settings::WifiApSettings settings {};
if (service::wifi::settings::load(ssid.c_str(), &settings)) {
if (settings.auto_connect) {
lv_obj_add_state(auto_connect_switch, LV_STATE_CHECKED);
} else {
lv_obj_remove_state(auto_connect_switch, LV_STATE_CHECKED);

View File

@ -17,9 +17,9 @@ bool State::hasConnectionError() const {
return result;
}
void State::setApSettings(const service::wifi::settings::WifiApSettings& newSettings) {
void State::setApSettings(const service::wifi::settings::WifiApSettings* newSettings) {
lock.lock();
this->apSettings = newSettings;
memcpy(&this->apSettings, newSettings, sizeof(service::wifi::settings::WifiApSettings));
lock.unlock();
}

View File

@ -5,11 +5,10 @@
#include "Tactility/lvgl/Spinner.h"
#include <Tactility/TactilityCore.h>
#include <Tactility/service/wifi/WifiSettings.h>
#include <lvgl.h>
#include <cstring>
#include <Tactility/service/wifi/WifiApSettings.h>
#include <Tactility/service/wifi/WifiGlobals.h>
namespace tt::app::wificonnect {
@ -51,14 +50,14 @@ static void onConnect(TT_UNUSED lv_event_t* event) {
view.setLoading(true);
service::wifi::settings::WifiApSettings settings;
settings.password = password;
settings.ssid = ssid;
strcpy((char*)settings.password, password);
strcpy((char*)settings.ssid, ssid);
settings.channel = 0;
settings.autoConnect = TT_WIFI_AUTO_CONNECT; // No UI yet, so use global setting:w
settings.auto_connect = TT_WIFI_AUTO_CONNECT; // No UI yet, so use global setting:w
auto* bindings = &wifi->getBindings();
bindings->onConnectSsid(
settings,
&settings,
store,
bindings->onConnectSsidContext
);

View File

@ -6,6 +6,7 @@
#include "Tactility/lvgl/LvglSync.h"
namespace tt::app::wificonnect {
#define TAG "wifi_connect"
#define WIFI_CONNECT_PARAM_SSID "ssid" // String
#define WIFI_CONNECT_PARAM_PASSWORD "password" // String
@ -36,7 +37,7 @@ static void eventCallback(const void* message, void* context) {
wifi->requestViewUpdate();
}
static void onConnect(const service::wifi::settings::WifiApSettings& ap_settings, bool remember, TT_UNUSED void* parameter) {
static void onConnect(const service::wifi::settings::WifiApSettings* ap_settings, bool remember, TT_UNUSED void* parameter) {
auto* wifi = static_cast<WifiConnect*>(parameter);
wifi->getState().setApSettings(ap_settings);
wifi->getState().setConnecting(true);

View File

@ -11,7 +11,6 @@
#include <format>
#include <string>
#include <set>
#include <Tactility/service/wifi/WifiSettings.h>
namespace tt::app::wifimanage {

View File

@ -16,9 +16,9 @@ extern const AppManifest manifest;
static void onConnect(const char* ssid) {
service::wifi::settings::WifiApSettings settings;
if (service::wifi::settings::load(ssid, settings)) {
if (service::wifi::settings::load(ssid, &settings)) {
TT_LOG_I(TAG, "Connecting with known credentials");
service::wifi::connect(settings, false);
service::wifi::connect(&settings, false);
} else {
TT_LOG_I(TAG, "Starting connection dialog");
wificonnect::start(ssid);

View File

@ -15,7 +15,7 @@
#include <lvgl.h>
#include <Tactility/TactilityHeadless.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/service/ServiceRegistration.h>
#include <Tactility/service/ServiceRegistry.h>
namespace tt::lvgl {

View File

@ -1,4 +1,4 @@
#include "Tactility/service/ServiceRegistration.h"
#include "Tactility/service/ServiceRegistry.h"
#include "Tactility/service/ServiceInstance.h"
#include "Tactility/service/ServiceManifest.h"
@ -8,7 +8,6 @@
#include <string>
#include <unordered_map>
#include <Tactility/app/AppInstance.h>
#include <Tactility/file/File.h>
namespace tt::service {
@ -23,22 +22,14 @@ static ServiceInstanceMap service_instance_map;
static Mutex manifest_mutex(Mutex::Type::Normal);
static Mutex instance_mutex(Mutex::Type::Normal);
void ensureServerPathsExist(const ServiceManifest& manifest) {
std::string path = "/data/service/" + manifest.id;
if (!file::findOrCreateDirectory(path, 0777)) {
TT_LOG_E(TAG, "Failed to create service directory: %s", path.c_str());
}
}
void addService(std::shared_ptr<const ServiceManifest> manifest, bool autoStart) {
assert(manifest != nullptr);
// We'll move the manifest pointer, but we'll need to id later
const auto& id = manifest->id;
std::string id = manifest->id;
TT_LOG_I(TAG, "Adding %s", id.c_str());
manifest_mutex.lock();
if (service_manifest_map[id] == nullptr) {
ensureServerPathsExist(*manifest);
service_manifest_map[id] = std::move(manifest);
} else {
TT_LOG_E(TAG, "Service id in use: %s", id.c_str());

View File

@ -6,7 +6,7 @@
#include "Tactility/network/Url.h"
#include "Tactility/TactilityHeadless.h"
#include "Tactility/service/ServiceManifest.h"
#include "Tactility/service/ServiceRegistration.h"
#include "Tactility/service/ServiceRegistry.h"
#include "Tactility/service/wifi/Wifi.h"
#include <cstring>
@ -17,7 +17,7 @@
#include <Tactility/StringUtils.h>
#include <Tactility/app/App.h>
#include <Tactility/app/ElfApp.h>
#include <Tactility/app/AppRegistration.h>
#include <Tactility/app/ManifestRegistry.h>
namespace tt::service::development {

View File

@ -3,7 +3,7 @@
#include "Tactility/service/espnow/EspNowService.h"
#include "Tactility/TactilityHeadless.h"
#include "Tactility/service/ServiceManifest.h"
#include "Tactility/service/ServiceRegistration.h"
#include "Tactility/service/ServiceRegistry.h"
#include "Tactility/service/espnow/EspNowWifi.h"
#include <cstring>
#include <esp_now.h>

View File

@ -1,6 +1,6 @@
#include "Tactility/service/gps/GpsService.h"
#include "Tactility/service/ServiceManifest.h"
#include "Tactility/service/ServiceRegistration.h"
#include "Tactility/service/ServiceRegistry.h"
#include <Tactility/Log.h>
#include <Tactility/file/File.h>

View File

@ -6,7 +6,7 @@
#include <Tactility/Tactility.h>
#include <Tactility/app/AppInstance.h>
#include <Tactility/service/ServiceRegistration.h>
#include <Tactility/service/ServiceRegistry.h>
namespace tt::service::gui {

View File

@ -1,12 +1,12 @@
#include "Tactility/service/loader/Loader.h"
#include "Tactility/app/AppInstance.h"
#include "Tactility/app/AppManifest.h"
#include "Tactility/app/AppRegistration.h"
#include "Tactility/app/ManifestRegistry.h"
#include <Tactility/RtosCompat.h>
#include <Tactility/DispatcherThread.h>
#include <Tactility/service/ServiceManifest.h>
#include <Tactility/service/ServiceRegistration.h>
#include <Tactility/service/ServiceRegistry.h>
#include <stack>

View File

@ -5,7 +5,7 @@
#include "Tactility/service/screenshot/Screenshot.h"
#include <Tactility/service/ServiceContext.h>
#include <Tactility/service/ServiceRegistration.h>
#include <Tactility/service/ServiceRegistry.h>
#include <memory>

View File

@ -1,6 +1,6 @@
#include "Tactility/service/ServiceContext.h"
#include "Tactility/TactilityHeadless.h"
#include "Tactility/service/ServiceRegistration.h"
#include "Tactility/service/ServiceRegistry.h"
#include <Tactility/Mutex.h>
#include <Tactility/Timer.h>

View File

@ -9,7 +9,7 @@
#include <Tactility/TactilityHeadless.h>
#include <Tactility/Timer.h>
#include <Tactility/service/ServiceContext.h>
#include <Tactility/service/ServiceRegistration.h>
#include <Tactility/service/ServiceRegistry.h>
#include <Tactility/service/wifi/Wifi.h>
namespace tt::service::statusbar {

View File

@ -1,186 +0,0 @@
#include "Tactility/service/wifi/WifiApSettings.h"
#include <cstring>
#include <iomanip>
#include <ranges>
#include <Tactility/crypt/Crypt.h>
#include <Tactility/file/File.h>
#include <Tactility/file/PropertiesFile.h>
namespace tt::service::wifi::settings {
constexpr auto* TAG = "WifiApSettings";
constexpr auto* AP_SETTINGS_FORMAT = "/data/service/Wifi/{}.ap.properties";
constexpr auto* AP_PROPERTIES_KEY_SSID = "ssid";
constexpr auto* AP_PROPERTIES_KEY_PASSWORD = "password";
constexpr auto* AP_PROPERTIES_KEY_AUTO_CONNECT = "autoConnect";
constexpr auto* AP_PROPERTIES_KEY_CHANNEL = "channel";
std::string toHexString(const uint8_t *data, int length) {
std::stringstream stream;
stream << std::hex;
for( int i(0) ; i < length; ++i )
stream << std::setw(2) << std::setfill('0') << static_cast<int>(data[i]);
return stream.str();
}
bool readHex(const std::string& input, uint8_t* buffer, int length) {
if (input.size() / 2 != length) {
TT_LOG_E(TAG, "readHex() length mismatch");
return false;
}
char hex[3] = { 0 };
for (int i = 0; i < length; i++) {
hex[0] = input[i * 2];
hex[1] = input[i * 2 + 1];
char* endptr;
buffer[i] = static_cast<uint8_t>(strtoul(hex, &endptr, 16));
}
return true;
}
static std::string getApPropertiesFilePath(const std::string& ssid) {
return std::format(AP_SETTINGS_FORMAT, ssid);
}
static bool encrypt(const std::string& ssidInput, std::string& ssidOutput) {
uint8_t iv[16];
const auto length = ssidInput.size();
constexpr size_t chunk_size = 16;
const auto encrypted_length = ((length / chunk_size) + (length % chunk_size ? 1 : 0)) * chunk_size;
auto* buffer = static_cast<uint8_t*>(malloc(encrypted_length));
crypt::getIv(ssidInput.c_str(), ssidInput.size(), iv);
if (crypt::encrypt(iv, reinterpret_cast<const uint8_t*>(ssidInput.c_str()), buffer, encrypted_length) != 0) {
TT_LOG_E(TAG, "Failed to encrypt");
free(buffer);
return false;
}
ssidOutput = toHexString(buffer, encrypted_length);
free(buffer);
return true;
}
static bool decrypt(const std::string& ssidInput, std::string& ssidOutput) {
assert(!ssidInput.empty());
assert(ssidInput.size() % 2 == 0);
auto* data = static_cast<uint8_t*>(malloc(ssidInput.size() / 2));
if (!readHex(ssidInput, data, ssidInput.size() / 2)) {
TT_LOG_E(TAG, "Failed to read hex");
return false;
}
uint8_t iv[16];
crypt::getIv(ssidInput.c_str(), ssidInput.size(), iv);
auto result_length = ssidInput.size() / 2;
// Allocate correct length plus space for string null terminator
auto* result = static_cast<uint8_t*>(malloc(result_length + 1));
result[result_length] = 0;
int decrypt_result = crypt::decrypt(
iv,
data,
result,
ssidInput.size() / 2
);
free(data);
if (decrypt_result != 0) {
TT_LOG_E(TAG, "Failed to decrypt credentials for \"%s\": %d", ssidInput.c_str(), decrypt_result);
free(result);
return false;
}
ssidOutput = reinterpret_cast<char*>(result);
free(result);
return true;
}
bool contains(const std::string& ssid) {
const auto file_path = getApPropertiesFilePath(ssid);
return file::exists(file_path);
}
bool load(const std::string& ssid, WifiApSettings& apSettings) {
const auto file_path = getApPropertiesFilePath(ssid);
std::map<std::string, std::string> map;
if (!file::loadPropertiesFile(file_path, map)) {
return false;
}
// SSID is required
if (!map.contains(AP_PROPERTIES_KEY_SSID)) {
return false;
}
apSettings.ssid = map[AP_PROPERTIES_KEY_SSID];
assert(ssid == apSettings.ssid);
if (map.contains(AP_PROPERTIES_KEY_PASSWORD)) {
std::string password_decrypted;
if (decrypt(map[AP_PROPERTIES_KEY_PASSWORD], password_decrypted)) {
apSettings.password = password_decrypted;
} else {
return false;
}
} else {
apSettings.password = "";
}
if (map.contains(AP_PROPERTIES_KEY_AUTO_CONNECT)) {
apSettings.autoConnect = (map[AP_PROPERTIES_KEY_AUTO_CONNECT] == "true");
} else {
apSettings.autoConnect = true;
}
if (map.contains(AP_PROPERTIES_KEY_CHANNEL)) {
apSettings.channel = std::stoi(map[AP_PROPERTIES_KEY_CHANNEL].c_str());
} else {
apSettings.channel = 0;
}
return true;
}
bool save(const WifiApSettings& apSettings) {
if (apSettings.ssid.empty()) {
return false;
}
const auto file_path = getApPropertiesFilePath(apSettings.ssid);
std::map<std::string, std::string> map;
std::string password_encrypted;
if (!encrypt(apSettings.password, password_encrypted)) {
return false;
}
map[AP_PROPERTIES_KEY_PASSWORD] = password_encrypted;
map[AP_PROPERTIES_KEY_SSID] = apSettings.ssid;
map[AP_PROPERTIES_KEY_AUTO_CONNECT] = apSettings.autoConnect ? "true" : "false";
map[AP_PROPERTIES_KEY_CHANNEL] = std::to_string(apSettings.channel);
return file::savePropertiesFile(file_path, map);
}
bool remove(const std::string& ssid) {
const auto path = getApPropertiesFilePath(ssid);
if (!file::exists(path)) {
return false;
}
return ::remove(path.c_str()) == 0;
}
}

View File

@ -1,19 +1,19 @@
#ifdef ESP_PLATFORM
#include <lwip/esp_netif_net_stack.h>
#include "Tactility/service/wifi/Wifi.h"
#include "Tactility/TactilityHeadless.h"
#include "Tactility/service/ServiceContext.h"
#include "Tactility/service/wifi/WifiGlobals.h"
#include "Tactility/service/wifi/WifiSettings.h"
#include <Tactility/kernel/SystemEvents.h>
#include <Tactility/Timer.h>
#include <lwip/esp_netif_net_stack.h>
#include <freertos/FreeRTOS.h>
#include <atomic>
#include <cstring>
#include <Tactility/kernel/SystemEvents.h>
#include <sys/cdefs.h>
namespace tt::service::wifi {
@ -36,6 +36,8 @@ static void dispatchDisconnectButKeepActive(std::shared_ptr<Wifi> wifi);
class Wifi {
private:
std::atomic<RadioState> radio_state = RadioState::Off;
bool scan_active = false;
bool secure_connection = false;
@ -64,7 +66,11 @@ public:
esp_event_handler_instance_t event_handler_any_id = nullptr;
esp_event_handler_instance_t event_handler_got_ip = nullptr;
EventFlag connection_wait_flags;
settings::WifiApSettings connection_target;
settings::WifiApSettings connection_target = {
.ssid = { 0 },
.password = { 0 },
.auto_connect = false
};
bool pause_auto_connect = false; // Pause when manually disconnecting until manually connecting again
bool connection_target_remember = false; // Whether to store the connection_target on successful connection or not
esp_netif_ip_info_t ip_info;
@ -181,8 +187,8 @@ bool isScanning() {
}
}
void connect(const settings::WifiApSettings& ap, bool remember) {
TT_LOG_I(TAG, "connect(%s, %d)", ap.ssid.c_str(), remember);
void connect(const settings::WifiApSettings* ap, bool remember) {
TT_LOG_I(TAG, "connect(%s, %d)", ap->ssid, remember);
auto wifi = wifi_singleton;
if (wifi == nullptr) {
return;
@ -195,14 +201,14 @@ void connect(const settings::WifiApSettings& ap, bool remember) {
// Manual connect (e.g. via app) should stop auto-connecting until the connection is established
wifi->pause_auto_connect = true;
wifi->connection_target = ap;
memcpy(&wifi->connection_target, ap, sizeof(settings::WifiApSettings));
wifi->connection_target_remember = remember;
if (wifi->getRadioState() == RadioState::Off) {
getMainDispatcher().dispatch([wifi] { dispatchEnable(wifi); });
getMainDispatcher().dispatch([wifi]() { dispatchEnable(wifi); });
}
getMainDispatcher().dispatch([wifi] { dispatchConnect(wifi); });
getMainDispatcher().dispatch([wifi]() { dispatchConnect(wifi); });
}
void disconnect() {
@ -217,7 +223,11 @@ void disconnect() {
return;
}
wifi->connection_target = settings::WifiApSettings("", "");
wifi->connection_target = (settings::WifiApSettings) {
.ssid = { 0 },
.password = { 0 },
.auto_connect = false
};
// Manual disconnect (e.g. via app) should stop auto-connecting until a new connection is established
wifi->pause_auto_connect = true;
getMainDispatcher().dispatch([wifi]() { dispatchDisconnectButKeepActive(wifi); });
@ -297,9 +307,9 @@ void setEnabled(bool enabled) {
}
if (enabled) {
getMainDispatcher().dispatch([wifi] { dispatchEnable(wifi); });
getMainDispatcher().dispatch([wifi]() { dispatchEnable(wifi); });
} else {
getMainDispatcher().dispatch([wifi] { dispatchDisable(wifi); });
getMainDispatcher().dispatch([wifi]() { dispatchDisable(wifi); });
}
wifi->pause_auto_connect = false;
@ -421,8 +431,8 @@ static bool find_auto_connect_ap(std::shared_ptr<Wifi> wifi, settings::WifiApSet
auto ssid = reinterpret_cast<const char*>(wifi->scan_list[i].ssid);
if (settings::contains(ssid)) {
static_assert(sizeof(wifi->scan_list[i].ssid) == (TT_WIFI_SSID_LIMIT + 1), "SSID size mismatch");
if (settings::load(ssid, settings)) {
if (settings.autoConnect) {
if (settings::load(ssid, &settings)) {
if (settings.auto_connect) {
return true;
}
} else {
@ -441,8 +451,8 @@ static void dispatchAutoConnect(std::shared_ptr<Wifi> wifi) {
settings::WifiApSettings settings;
if (find_auto_connect_ap(wifi, settings)) {
TT_LOG_I(TAG, "Auto-connecting to %s", settings.ssid.c_str());
connect(settings, false);
TT_LOG_I(TAG, "Auto-connecting to %s", settings.ssid);
connect(&settings, false);
// TODO: We currently have to manually reset it because connect() sets it.
// connect() assumes it's only being called by the user and not internally, so it disables auto-connect
wifi->pause_auto_connect = false;
@ -716,7 +726,7 @@ static void dispatchConnect(std::shared_ptr<Wifi> wifi) {
return;
}
TT_LOG_I(TAG, "Connecting to %s", wifi->connection_target.ssid.c_str());
TT_LOG_I(TAG, "Connecting to %s", wifi->connection_target.ssid);
// Stop radio first, if needed
RadioState radio_state = wifi->getRadioState();
@ -748,10 +758,11 @@ static void dispatchConnect(std::shared_ptr<Wifi> wifi) {
config.sta.threshold.rssi = -127;
config.sta.pmf_cfg.capable = true;
memcpy(config.sta.ssid, wifi_singleton->connection_target.ssid.c_str(), wifi_singleton->connection_target.ssid.size());
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] != 0x00) {
memcpy(config.sta.password, wifi_singleton->connection_target.password.c_str(), wifi_singleton->connection_target.password.size());
memcpy(config.sta.password, wifi_singleton->connection_target.password, sizeof(config.sta.password));
config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
}
@ -783,9 +794,9 @@ static void dispatchConnect(std::shared_ptr<Wifi> wifi) {
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.c_str());
TT_LOG_I(TAG, "Connected to %s", wifi->connection_target.ssid);
if (wifi->connection_target_remember) {
if (!settings::save(wifi->connection_target)) {
if (!settings::save(&wifi->connection_target)) {
TT_LOG_E(TAG, "Failed to store credentials");
} else {
TT_LOG_I(TAG, "Stored credentials");
@ -794,7 +805,7 @@ static void dispatchConnect(std::shared_ptr<Wifi> wifi) {
} else if (bits & WIFI_FAIL_BIT) {
wifi->setRadioState(RadioState::On);
publish_event_simple(wifi, EventType::ConnectionFailed);
TT_LOG_I(TAG, "Failed to connect to %s", wifi->connection_target.ssid.c_str());
TT_LOG_I(TAG, "Failed to connect to %s", wifi->connection_target.ssid);
} else {
wifi->setRadioState(RadioState::On);
publish_event_simple(wifi, EventType::ConnectionFailed);

View File

@ -1,54 +1,18 @@
#include "Tactility/service/wifi/WifiSettings.h"
#include "Tactility/Preferences.h"
#include <Tactility/LogEsp.h>
#include <Tactility/file/File.h>
#include <Tactility/file/PropertiesFile.h>
#define WIFI_PREFERENCES_NAMESPACE "wifi"
#define WIFI_PREFERENCES_KEY_ENABLE_ON_BOOT "enable_on_boot"
namespace tt::service::wifi::settings {
constexpr auto* TAG = "WifiSettings";
constexpr auto* SETTINGS_FILE = "/data/service/Wifi/wifi.properties";
constexpr auto* SETTINGS_KEY_ENABLE_ON_BOOT = "enableOnBoot";
struct WifiProperties {
bool enableOnBoot;
};
static bool load(WifiProperties& properties) {
std::map<std::string, std::string> map;
if (!file::loadPropertiesFile(SETTINGS_FILE, map)) {
return false;
}
if (!map.contains(SETTINGS_KEY_ENABLE_ON_BOOT)) {
return false;
}
auto enable_on_boot_string = map[SETTINGS_KEY_ENABLE_ON_BOOT];
properties.enableOnBoot = (enable_on_boot_string == "true");
return true;
}
static bool save(const WifiProperties& properties) {
std::map<std::string, std::string> map;
map[SETTINGS_KEY_ENABLE_ON_BOOT] = properties.enableOnBoot ? "true" : "false";
return file::savePropertiesFile(SETTINGS_FILE, map);
}
void setEnableOnBoot(bool enable) {
WifiProperties properties { .enableOnBoot = enable };
if (!save(properties)) {
TT_LOG_E(TAG, "Failed to save %s", SETTINGS_FILE);
}
Preferences(WIFI_PREFERENCES_NAMESPACE).putBool(WIFI_PREFERENCES_KEY_ENABLE_ON_BOOT, enable);
}
bool shouldEnableOnBoot() {
WifiProperties properties;
if (!load(properties)) {
return false;
}
return properties.enableOnBoot;
bool enable = false;
Preferences(WIFI_PREFERENCES_NAMESPACE).optBool(WIFI_PREFERENCES_KEY_ENABLE_ON_BOOT, enable);
return enable;
}
} // namespace

View File

@ -0,0 +1,148 @@
#ifdef ESP_PLATFORM
#include "Tactility/service/wifi/WifiGlobals.h"
#include "Tactility/service/wifi/WifiSettings.h"
#include <Tactility/Log.h>
#include <Tactility/crypt/Hash.h>
#include <Tactility/crypt/Crypt.h>
#include <nvs_flash.h>
#include <cstring>
#define TAG "wifi_settings"
#define TT_NVS_NAMESPACE "wifi_settings" // limited by NVS_KEY_NAME_MAX_SIZE
// region Wi-Fi Credentials - static
static esp_err_t credentials_nvs_open(nvs_handle_t* handle, nvs_open_mode_t mode) {
return nvs_open(TT_NVS_NAMESPACE, NVS_READWRITE, handle);
}
static void credentials_nvs_close(nvs_handle_t handle) {
nvs_close(handle);
}
// endregion Wi-Fi Credentials - static
// region Wi-Fi Credentials - public
namespace tt::service::wifi::settings {
bool contains(const char* ssid) {
nvs_handle_t handle;
esp_err_t result = credentials_nvs_open(&handle, NVS_READONLY);
if (result != ESP_OK) {
TT_LOG_E(TAG, "Failed to open NVS handle: %s", esp_err_to_name(result));
return false;
}
bool key_exists = nvs_find_key(handle, ssid, nullptr) == ESP_OK;
credentials_nvs_close(handle);
return key_exists;
}
bool load(const char* ssid, WifiApSettings* settings) {
nvs_handle_t handle;
esp_err_t result = credentials_nvs_open(&handle, NVS_READONLY);
if (result != ESP_OK) {
TT_LOG_E(TAG, "Failed to open NVS handle: %s", esp_err_to_name(result));
return false;
}
WifiApSettings encrypted_settings;
size_t length = sizeof(WifiApSettings);
result = nvs_get_blob(handle, ssid, &encrypted_settings, &length);
uint8_t iv[16];
crypt::getIv(ssid, strlen(ssid), iv);
int decrypt_result = crypt::decrypt(
iv,
(uint8_t*)encrypted_settings.password,
(uint8_t*)settings->password,
TT_WIFI_CREDENTIALS_PASSWORD_LIMIT
);
// Manually ensure null termination, because encryption must be a multiple of 16 bytes
encrypted_settings.password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT] = 0;
if (decrypt_result != 0) {
result = ESP_FAIL;
TT_LOG_E(TAG, "Failed to decrypt credentials for \"%s\": %d", ssid, decrypt_result);
}
if (result != ESP_OK && result != ESP_ERR_NVS_NOT_FOUND) {
TT_LOG_E(TAG, "Failed to get credentials for \"%s\": %s", ssid, esp_err_to_name(result));
}
credentials_nvs_close(handle);
settings->auto_connect = encrypted_settings.auto_connect;
strcpy((char*)settings->ssid, encrypted_settings.ssid);
return result == ESP_OK;
}
bool save(const WifiApSettings* settings) {
nvs_handle_t handle;
esp_err_t result = credentials_nvs_open(&handle, NVS_READWRITE);
if (result != ESP_OK) {
TT_LOG_E(TAG, "Failed to open NVS handle: %s", esp_err_to_name(result));
return false;
}
WifiApSettings encrypted_settings = {
.ssid = { 0 },
.password = { 0 },
.auto_connect = settings->auto_connect,
};
strcpy((char*)encrypted_settings.ssid, settings->ssid);
// We only decrypt multiples of 16, so we have to ensure the last byte is set to 0
encrypted_settings.password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT] = 0;
uint8_t iv[16];
crypt::getIv(settings->ssid, strlen(settings->ssid), iv);
int encrypt_result = crypt::encrypt(
iv,
(uint8_t*)settings->password,
(uint8_t*)encrypted_settings.password,
TT_WIFI_CREDENTIALS_PASSWORD_LIMIT
);
if (encrypt_result != 0) {
result = ESP_FAIL;
TT_LOG_E(TAG, "Failed to encrypt credentials \"%s\": %d", settings->ssid, encrypt_result);
}
if (result == ESP_OK) {
result = nvs_set_blob(handle, settings->ssid, &encrypted_settings, sizeof(WifiApSettings));
if (result != ESP_OK) {
TT_LOG_E(TAG, "Failed to get credentials for \"%s\": %s", settings->ssid, esp_err_to_name(result));
}
}
credentials_nvs_close(handle);
return result == ESP_OK;
}
bool remove(const char* ssid) {
nvs_handle_t handle;
esp_err_t result = credentials_nvs_open(&handle, NVS_READWRITE);
if (result != ESP_OK) {
TT_LOG_E(TAG, "Failed to open NVS handle to store \"%s\": %s", ssid, esp_err_to_name(result));
return false;
}
result = nvs_erase_key(handle, ssid);
if (result != ESP_OK) {
TT_LOG_E(TAG, "Failed to erase credentials for \"%s\": %s", ssid, esp_err_to_name(result));
}
credentials_nvs_close(handle);
return result == ESP_OK;
}
// end region Wi-Fi Credentials - public
} // nemespace
#endif // ESP_PLATFORM

View File

@ -0,0 +1,27 @@
#ifndef ESP_PLATFORM
#include "Tactility/service/wifi/WifiSettings.h"
namespace tt::service::wifi::settings {
#define TAG "wifi_settings_mock"
bool contains(const char* ssid) {
return false;
}
bool load(const char* ssid, WifiApSettings* settings) {
return false;
}
bool save(const WifiApSettings* settings) {
return true;
}
bool remove(const char* ssid) {
return true;
}
} // namespace
#endif // ESP_PLATFORM

View File

@ -34,11 +34,10 @@ void tt_wifi_set_enabled(bool enabled) {
void tt_wifi_connect(const char* ssid, const char* password, int32_t channel, bool autoConnect, bool remember) {
wifi::settings::WifiApSettings settings;
settings.ssid = ssid;
settings.password = password;
strcpy(settings.ssid, ssid);
strcpy(settings.password, password);
settings.channel = channel;
settings.autoConnect = autoConnect;
wifi::connect(settings, remember);
settings.auto_connect = autoConnect;
}
void tt_wifi_disconnect() {

View File

@ -44,7 +44,7 @@ void getIv(const void* data, size_t dataLength, uint8_t iv[16]);
* @param[in] dataLength data length, a multiple of 16 (for both inData and outData)
* @return the result of esp_aes_crypt_cbc() (MBEDTLS_ERR_*)
*/
int encrypt(const uint8_t iv[16], const uint8_t* inData, uint8_t* outData, size_t dataLength);
int encrypt(const uint8_t iv[16], uint8_t* inData, uint8_t* outData, size_t dataLength);
/**
* @brief Decrypt data.
@ -58,7 +58,6 @@ int encrypt(const uint8_t iv[16], const uint8_t* inData, uint8_t* outData, size_
* @param[in] dataLength data length, a multiple of 16 (for both inData and outData)
* @return the result of esp_aes_crypt_cbc() (MBEDTLS_ERR_*)
*/
int decrypt(const uint8_t iv[16], const uint8_t* inData, uint8_t* outData, size_t dataLength);
int decrypt(const uint8_t iv[16], uint8_t* inData, uint8_t* outData, size_t dataLength);
} // namespace

View File

@ -92,8 +92,6 @@ bool direntSortAlphaAndType(const dirent& left, const dirent& right);
/** A filter for filtering out "." and ".." */
int direntFilterDotEntries(const dirent* entry);
bool exists(const std::string& path);
/**
* A scandir()-like implementation that works on ESP32.
* It does not return "." and ".." items but otherwise functions the same.

View File

@ -10,6 +10,4 @@ bool loadPropertiesFile(const std::string& filePath, std::map<std::string, std::
bool loadPropertiesFile(const std::string& filePath, std::function<void(const std::string& key, const std::string& value)> callback);
bool savePropertiesFile(const std::string& filePath, const std::map<std::string, std::string>& properties);
}

View File

@ -127,7 +127,7 @@ static void getKey(uint8_t key[32]) {
}
void getIv(const void* data, size_t dataLength, uint8_t iv[16]) {
memset(iv, 0, 16);
memset((void*)iv, 0, 16);
auto* data_bytes = (uint8_t*)data;
for (int i = 0; i < dataLength; ++i) {
size_t safe_index = i % 16;
@ -161,7 +161,7 @@ static int aes256CryptCbc(
return result;
}
int encrypt(const uint8_t iv[16], const uint8_t* inData, uint8_t* outData, size_t dataLength) {
int encrypt(const uint8_t iv[16], uint8_t* inData, uint8_t* outData, size_t dataLength) {
tt_check(dataLength % 16 == 0, "Length is not a multiple of 16 bytes (for AES 256");
uint8_t key[32];
getKey(key);
@ -173,7 +173,7 @@ int encrypt(const uint8_t iv[16], const uint8_t* inData, uint8_t* outData, size_
return aes256CryptCbc(key, MBEDTLS_AES_ENCRYPT, dataLength, iv_copy, inData, outData);
}
int decrypt(const uint8_t iv[16], const uint8_t* inData, uint8_t* outData, size_t dataLength) {
int decrypt(const uint8_t iv[16], uint8_t* inData, uint8_t* outData, size_t dataLength) {
tt_check(dataLength % 16 == 0, "Length is not a multiple of 16 bytes (for AES 256");
uint8_t key[32];
getKey(key);

View File

@ -208,8 +208,4 @@ bool findOrCreateDirectory(std::string path, mode_t mode) {
return true;
}
bool exists(const std::string& path) {
return access(path.c_str(), F_OK) == 0;
}
}

View File

@ -53,20 +53,4 @@ bool loadPropertiesFile(const std::string& filePath, std::map<std::string, std::
});
}
bool savePropertiesFile(const std::string& filePath, const std::map<std::string, std::string>& properties) {
FILE* file = fopen(filePath.c_str(), "w");
if (file == nullptr) {
TT_LOG_E(TAG, "Failed to open %s", filePath.c_str());
return false;
}
for (const auto& [key, value] : properties) {
fprintf(file, "%s=%s\n", key.c_str(), value.c_str());
}
fclose(file);
return true;
}
}