Merge develop into main (#327)
## New features - Implemented support for app packaging in firmware and `tactility.py`: load `.app` files instead of `.elf` files. Install apps remotely or via `FileBrowser`. - Ensure headless mode works: all services that require LVGL can deal with the absence of a display - Service `onStart()` is now allowed to fail (return `bool` result) - Added and improved various file-related helper functions ## Improvements - Completely revamped the SystemInfo app UI - Improved Calculator UI of internal and external variant - Fix Chat UI and removed the emoji buttons for now - Fix for toolbar bottom padding issue in all apps ## Fixes - Fix for allowing recursive locking for certain SPI SD cards & more
This commit is contained in:
committed by
GitHub
parent
068600f98c
commit
84049658db
@@ -77,8 +77,15 @@ bool startService(const std::string& id) {
|
||||
instance_mutex.unlock();
|
||||
|
||||
service_instance->setState(State::Starting);
|
||||
service_instance->getService()->onStart(*service_instance);
|
||||
service_instance->setState(State::Started);
|
||||
if (service_instance->getService()->onStart(*service_instance)) {
|
||||
service_instance->setState(State::Started);
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Starting %s failed", id.c_str());
|
||||
service_instance->setState(State::Stopped);
|
||||
instance_mutex.lock();
|
||||
service_instance_map.erase(manifest->id);
|
||||
instance_mutex.unlock();
|
||||
}
|
||||
|
||||
TT_LOG_I(TAG, "Started %s", id.c_str());
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#include <esp_wifi.h>
|
||||
#include <ranges>
|
||||
#include <sstream>
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <Tactility/file/FileLock.h>
|
||||
|
||||
namespace tt::service::development {
|
||||
|
||||
@@ -25,7 +27,7 @@ extern const ServiceManifest manifest;
|
||||
|
||||
constexpr const char* TAG = "DevService";
|
||||
|
||||
void DevelopmentService::onStart(ServiceContext& service) {
|
||||
bool DevelopmentService::onStart(ServiceContext& service) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
|
||||
@@ -39,6 +41,8 @@ void DevelopmentService::onStart(ServiceContext& service) {
|
||||
);
|
||||
|
||||
setEnabled(shouldEnableOnBoot());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DevelopmentService::onStop(ServiceContext& service) {
|
||||
@@ -97,6 +101,7 @@ void DevelopmentService::startServer() {
|
||||
deviceResponse = stream.str();
|
||||
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
config.stack_size = 5120;
|
||||
|
||||
config.server_port = 6666;
|
||||
config.uri_match_fn = httpd_uri_match_wildcard;
|
||||
@@ -154,6 +159,8 @@ void DevelopmentService::onNetworkDisconnected() {
|
||||
// region endpoints
|
||||
|
||||
esp_err_t DevelopmentService::handleGetInfo(httpd_req_t* request) {
|
||||
TT_LOG_I(TAG, "GET /device");
|
||||
|
||||
if (httpd_resp_set_type(request, "application/json") != ESP_OK) {
|
||||
TT_LOG_W(TAG, "Failed to send header");
|
||||
return ESP_FAIL;
|
||||
@@ -171,6 +178,8 @@ esp_err_t DevelopmentService::handleGetInfo(httpd_req_t* request) {
|
||||
}
|
||||
|
||||
esp_err_t DevelopmentService::handleAppRun(httpd_req_t* request) {
|
||||
TT_LOG_I(TAG, "POST /app/run");
|
||||
|
||||
std::string query;
|
||||
if (!network::getQueryOrSendError(request, query)) {
|
||||
return ESP_FAIL;
|
||||
@@ -184,28 +193,15 @@ esp_err_t DevelopmentService::handleAppRun(httpd_req_t* request) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
auto app_id = id_key_pos->second;
|
||||
if (app_id.ends_with(".app.elf")) {
|
||||
if (!file::isFile(app_id)) {
|
||||
TT_LOG_W(TAG, "[400] /app/run cannot find app %s", app_id.c_str());
|
||||
httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "app not found");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
app::registerElfApp(app_id);
|
||||
app_id = app::getElfAppId(app_id);
|
||||
} else if (!app::findAppById(app_id.c_str())) {
|
||||
TT_LOG_W(TAG, "[400] /app/run app not found");
|
||||
httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "app not found");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
app::start(app_id);
|
||||
TT_LOG_I(TAG, "[200] /app/run %s", app_id.c_str());
|
||||
app::start(id_key_pos->second);
|
||||
TT_LOG_I(TAG, "[200] /app/run %s", id_key_pos->second.c_str());
|
||||
httpd_resp_send(request, nullptr, 0);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t DevelopmentService::handleAppInstall(httpd_req_t* request) {
|
||||
TT_LOG_I(TAG, "PUT /app/install");
|
||||
|
||||
std::string boundary;
|
||||
if (!network::getMultiPartBoundaryOrSendError(request, boundary)) {
|
||||
return false;
|
||||
@@ -250,8 +246,19 @@ esp_err_t DevelopmentService::handleAppInstall(httpd_req_t* request) {
|
||||
}
|
||||
content_left -= content_read;
|
||||
|
||||
const std::string tmp_path = app::getTempPath();
|
||||
auto lock = file::getLock(tmp_path)->asScopedLock();
|
||||
|
||||
lock.lock();
|
||||
if (!file::findOrCreateDirectory(tmp_path, 0777)) {
|
||||
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to save file");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
// Write file
|
||||
auto file_path = std::format("/data/{}", filename_entry->second);
|
||||
lock.lock();
|
||||
auto file_path = std::format("{}/{}", tmp_path, filename_entry->second);
|
||||
auto* file = fopen(file_path.c_str(), "wb");
|
||||
auto file_bytes_written = fwrite(buffer.get(), 1, file_size, file);
|
||||
fclose(file);
|
||||
@@ -259,6 +266,8 @@ esp_err_t DevelopmentService::handleAppInstall(httpd_req_t* request) {
|
||||
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to save file");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
|
||||
// Read and verify part
|
||||
if (!network::readAndDiscardOrSendError(request, part_after_file)) {
|
||||
@@ -270,9 +279,21 @@ esp_err_t DevelopmentService::handleAppInstall(httpd_req_t* request) {
|
||||
TT_LOG_W(TAG, "We have more bytes at the end of the request parsing?!");
|
||||
}
|
||||
|
||||
if (!app::install(file_path)) {
|
||||
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to install");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
lock.lock();
|
||||
if (remove(file_path.c_str()) != 0) {
|
||||
TT_LOG_W(TAG, "Failed to delete %s", file_path.c_str());
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
TT_LOG_I(TAG, "[200] /app/install -> %s", file_path.c_str());
|
||||
|
||||
httpd_resp_send(request, nullptr, 0);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,13 @@ static uint8_t BROADCAST_MAC[ESP_NOW_ETH_ALEN];
|
||||
|
||||
constexpr bool isBroadcastAddress(uint8_t address[ESP_NOW_ETH_ALEN]) { return memcmp(address, BROADCAST_MAC, ESP_NOW_ETH_ALEN) == 0; }
|
||||
|
||||
void EspNowService::onStart(ServiceContext& service) {
|
||||
bool EspNowService::onStart(ServiceContext& service) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
|
||||
memset(BROADCAST_MAC, 0xFF, sizeof(BROADCAST_MAC));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EspNowService::onStop(ServiceContext& service) {
|
||||
|
||||
@@ -12,8 +12,8 @@ namespace tt::service::gps {
|
||||
constexpr const char* TAG = "GpsService";
|
||||
extern const ServiceManifest manifest;
|
||||
|
||||
constexpr inline bool hasTimeElapsed(TickType_t now, TickType_t timeInThePast, TickType_t expireTimeInTicks) {
|
||||
return (TickType_t)(now - timeInThePast) >= expireTimeInTicks;
|
||||
constexpr bool hasTimeElapsed(TickType_t now, TickType_t timeInThePast, TickType_t expireTimeInTicks) {
|
||||
return (now - timeInThePast) >= expireTimeInTicks;
|
||||
}
|
||||
|
||||
GpsService::GpsDeviceRecord* _Nullable GpsService::findGpsRecord(const std::shared_ptr<GpsDevice>& device) {
|
||||
@@ -58,14 +58,14 @@ void GpsService::removeGpsDevice(const std::shared_ptr<GpsDevice>& device) {
|
||||
});
|
||||
}
|
||||
|
||||
void GpsService::onStart(tt::service::ServiceContext& serviceContext) {
|
||||
bool GpsService::onStart(ServiceContext& serviceContext) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
|
||||
paths = serviceContext.getPaths();
|
||||
return true;
|
||||
}
|
||||
|
||||
void GpsService::onStop(tt::service::ServiceContext& serviceContext) {
|
||||
void GpsService::onStop(ServiceContext& serviceContext) {
|
||||
if (getState() == State::On) {
|
||||
stopReceiving();
|
||||
}
|
||||
|
||||
@@ -112,7 +112,13 @@ void GuiService::redraw() {
|
||||
unlock();
|
||||
}
|
||||
|
||||
void GuiService::onStart(TT_UNUSED ServiceContext& service) {
|
||||
bool GuiService::onStart(TT_UNUSED ServiceContext& service) {
|
||||
auto* screen_root = lv_screen_active();
|
||||
if (screen_root == nullptr) {
|
||||
TT_LOG_E(TAG, "No display found");
|
||||
return false;
|
||||
}
|
||||
|
||||
thread = new Thread(
|
||||
"gui",
|
||||
4096, // Last known minimum was 2800 for launching desktop
|
||||
@@ -123,12 +129,9 @@ void GuiService::onStart(TT_UNUSED ServiceContext& service) {
|
||||
onLoaderEvent(event);
|
||||
});
|
||||
|
||||
lvgl::lock(portMAX_DELAY);
|
||||
|
||||
tt_check(lvgl::lock(1000 / portTICK_PERIOD_MS));
|
||||
keyboardGroup = lv_group_create();
|
||||
auto* screen_root = lv_screen_active();
|
||||
assert(screen_root != nullptr);
|
||||
|
||||
lvgl::obj_set_style_bg_blacken(screen_root);
|
||||
|
||||
lv_obj_t* vertical_container = lv_obj_create(screen_root);
|
||||
@@ -156,6 +159,8 @@ void GuiService::onStart(TT_UNUSED ServiceContext& service) {
|
||||
|
||||
thread->setPriority(THREAD_PRIORITY_SERVICE);
|
||||
thread->start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GuiService::onStop(TT_UNUSED ServiceContext& service) {
|
||||
|
||||
@@ -62,8 +62,9 @@ class LoaderService final : public Service {
|
||||
|
||||
public:
|
||||
|
||||
void onStart(TT_UNUSED ServiceContext& service) override {
|
||||
bool onStart(TT_UNUSED ServiceContext& service) override {
|
||||
dispatcherThread->start();
|
||||
return true;
|
||||
}
|
||||
|
||||
void onStop(TT_UNUSED ServiceContext& service) override {
|
||||
|
||||
@@ -2,16 +2,15 @@
|
||||
|
||||
#if TT_FEATURE_SCREENSHOT_ENABLED
|
||||
|
||||
#include "Tactility/service/screenshot/Screenshot.h"
|
||||
|
||||
#include <Tactility/service/ServiceContext.h>
|
||||
#include <Tactility/service/screenshot/Screenshot.h>
|
||||
#include <Tactility/service/ServiceRegistration.h>
|
||||
|
||||
#include <memory>
|
||||
#include <lvgl.h>
|
||||
|
||||
namespace tt::service::screenshot {
|
||||
|
||||
#define TAG "screenshot_service"
|
||||
constexpr auto* TAG = "ScreenshotService";
|
||||
|
||||
extern const ServiceManifest manifest;
|
||||
|
||||
@@ -51,6 +50,15 @@ void ScreenshotService::startTimed(const std::string& path, uint8_t delayInSecon
|
||||
}
|
||||
}
|
||||
|
||||
bool ScreenshotService::onStart(ServiceContext& serviceContext) {
|
||||
if (lv_screen_active() == nullptr) {
|
||||
TT_LOG_E(TAG, "No display found");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScreenshotService::stop() {
|
||||
auto lock = mutex.asScopedLock();
|
||||
if (!lock.lock(50 / portTICK_PERIOD_MS)) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <Tactility/service/ServiceRegistration.h>
|
||||
|
||||
#include <Tactility/Mutex.h>
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <Tactility/Timer.h>
|
||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||
|
||||
@@ -52,13 +53,21 @@ class SdCardService final : public Service {
|
||||
|
||||
public:
|
||||
|
||||
void onStart(ServiceContext& serviceContext) override {
|
||||
bool onStart(ServiceContext& serviceContext) override {
|
||||
if (hal::findFirstDevice<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard) == nullptr) {
|
||||
TT_LOG_W(TAG, "No SD card device found - not starting Service");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto service = findServiceById<SdCardService>(manifest.id);
|
||||
updateTimer = std::make_unique<Timer>(Timer::Type::Periodic, [service]() {
|
||||
service->update();
|
||||
});
|
||||
|
||||
// We want to try and scan more often in case of startup or scan lock failure
|
||||
updateTimer->start(1000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void onStop(ServiceContext& serviceContext) override {
|
||||
|
||||
@@ -249,7 +249,12 @@ public:
|
||||
lvgl::statusbar_icon_remove(gps_icon_id);
|
||||
}
|
||||
|
||||
void onStart(ServiceContext& serviceContext) override {
|
||||
bool onStart(ServiceContext& serviceContext) override {
|
||||
if (lv_screen_active() == nullptr) {
|
||||
TT_LOG_E(TAG, "No display found");
|
||||
return false;
|
||||
}
|
||||
|
||||
paths = serviceContext.getPaths();
|
||||
|
||||
// TODO: Make thread-safe for LVGL
|
||||
@@ -265,6 +270,8 @@ public:
|
||||
|
||||
// We want to try and scan more often in case of startup or scan lock failure
|
||||
updateTimer->start(1000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void onStop(ServiceContext& service) override{
|
||||
|
||||
@@ -414,10 +414,9 @@ static bool copy_scan_list(std::shared_ptr<Wifi> wifi) {
|
||||
}
|
||||
|
||||
static bool find_auto_connect_ap(std::shared_ptr<Wifi> wifi, settings::WifiApSettings& settings) {
|
||||
TT_LOG_I(TAG, "find_auto_connect_ap()");
|
||||
auto lock = wifi->dataMutex.asScopedLock();
|
||||
|
||||
if (lock.lock(10 / portTICK_PERIOD_MS)) {
|
||||
TT_LOG_I(TAG, "auto_connect()");
|
||||
for (int i = 0; i < wifi->scan_list_count; ++i) {
|
||||
auto ssid = reinterpret_cast<const char*>(wifi->scan_list[i].ssid);
|
||||
if (settings::contains(ssid)) {
|
||||
@@ -897,7 +896,7 @@ class WifiService final : public Service {
|
||||
|
||||
public:
|
||||
|
||||
void onStart(ServiceContext& service) override {
|
||||
bool onStart(ServiceContext& service) override {
|
||||
assert(wifi_singleton == nullptr);
|
||||
wifi_singleton = std::make_shared<Wifi>();
|
||||
|
||||
@@ -913,6 +912,8 @@ public:
|
||||
TT_LOG_I(TAG, "Auto-enabling due to setting");
|
||||
getMainDispatcher().dispatch([] { dispatchEnable(wifi_singleton); });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void onStop(ServiceContext& service) override {
|
||||
|
||||
@@ -144,9 +144,10 @@ class WifiService final : public Service {
|
||||
|
||||
public:
|
||||
|
||||
void onStart(TT_UNUSED ServiceContext& service) final {
|
||||
bool onStart(TT_UNUSED ServiceContext& service) final {
|
||||
tt_check(wifi == nullptr);
|
||||
wifi = new Wifi();
|
||||
return true;
|
||||
}
|
||||
|
||||
void onStop(TT_UNUSED ServiceContext& service) final {
|
||||
|
||||
Reference in New Issue
Block a user