Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Documentation/ideas.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

## Higher Priority

- Make tt.py work without an app project. Ping the device for info.
- LVGL spinner not working on cl32 v04 (possibly anything monochrome)
- Find out why the first build of an app with a clean build cache folder always fails
- Improve posix support: use user home path and system /tmp folder.
Expand Down
18 changes: 18 additions & 0 deletions Drivers/gps-meshtastic-module/source/gps_meshtastic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,24 @@ static int32_t gps_thread_main(void* context) {
}
break;
}
case MINMEA_SENTENCE_GSV: {
GpsEvent event { .type = GPS_EVENT_MESSAGE_GSV, .data = {} };
if (minmea_parse_gsv(&event.data.gsv, reinterpret_cast<char*>(buffer))) {
notify_subscribers(internal, event);
Comment thread
KenVanHoeylandt marked this conversation as resolved.
Resolved
} else {
LOG_E(TAG, "GSV parse error: %s", reinterpret_cast<const char*>(buffer));
}
break;
}
case MINMEA_SENTENCE_GSA: {
GpsEvent event { .type = GPS_EVENT_MESSAGE_GSA, .data = {} };
if (minmea_parse_gsa(&event.data.gsa, reinterpret_cast<char*>(buffer))) {
notify_subscribers(internal, event);
} else {
LOG_E(TAG, "GSA parse error: %s", reinterpret_cast<const char*>(buffer));
}
break;
}
default:
break;
}
Expand Down
12 changes: 6 additions & 6 deletions Modules/app-hub-module/include/app_hub/app_hub.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ extern "C" {

/**
* App Hub service: finds, installs and removes apps from the online app catalog.
* Operations are queued and run one at a time on a background task. Their outcome is reported through events.
* Operations are queued and run one at a time, in order, on a background task. Their outcome is reported through events.
*/

#define APP_HUB_NAME_MAX_LENGTH 63
Expand Down Expand Up @@ -103,18 +103,18 @@ error_t app_hub_refresh(uint32_t* operation_id);

/**
* Queue the installation of a catalog app. An installed version is replaced.
* Only one install or remove is queued or running at a time. A refresh can be pending next to it.
* Installs and removes of different apps can be queued together. Per app, only one is queued or running at a time.
* @param[out] operation_id the id of the queued operation (optional)
* @retval ERROR_NONE the operation was queued, or the same install was already pending and its id is returned
* @retval ERROR_RESOURCE_BUSY another install or remove is pending
* @retval ERROR_RESOURCE_BUSY a remove of this app or an update of all apps is pending
*/
error_t app_hub_install(const char* id, uint32_t* operation_id);

/**
* Queue the update of every installed app that has a newer version in the catalog. The apps are installed one at a time,
* with an INSTALLING and INSTALLED event each. A failed update doesn't stop the updates of the other apps.
* While the operation is pending, the apps with an update report it as their pending operation.
* The operation takes the place of an install or remove: only one of them is queued or running at a time.
* It can't be queued next to an install or remove.
* @param[out] operation_id the id of the queued operation (optional)
* @retval ERROR_NONE the operation was queued, or it was already pending and its id is returned
* @retval ERROR_RESOURCE_BUSY an install or remove is pending
Expand All @@ -123,10 +123,10 @@ error_t app_hub_update_all(uint32_t* operation_id);

/**
* Queue the removal of an installed app.
* Only one install or remove is queued or running at a time. A refresh can be pending next to it.
* Installs and removes of different apps can be queued together. Per app, only one is queued or running at a time.
* @param[out] operation_id the id of the queued operation (optional)
* @retval ERROR_NONE the operation was queued, or the same removal was already pending and its id is returned
* @retval ERROR_RESOURCE_BUSY another install or remove is pending
* @retval ERROR_RESOURCE_BUSY an install of this app or an update of all apps is pending
*/
error_t app_hub_remove(const char* id, uint32_t* operation_id);

Expand Down
2 changes: 1 addition & 1 deletion Modules/app-hub-module/source/app_hub_command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ static void print_usage() {

static int report_error(const char* action, error_t error) {
if (error == ERROR_RESOURCE_BUSY) {
printf("apphub: %s failed: another app is being installed or removed\n", action);
printf("apphub: %s failed: a conflicting operation is pending\n", action);
} else {
printf("apphub: %s failed: %s\n", action, error_to_string(error));
}
Expand Down
17 changes: 15 additions & 2 deletions Modules/app-hub-module/source/app_hub_service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,11 @@ tt::std::vector<InstalledPackage> getInstalledPackages() {
return packages;
}

/** Requires the global lock. Only one refresh and one install or remove can be queued or running at a time. */
/**
* Requires the global lock. Only one refresh can be queued or running at a time.
* Installs and removes of different apps can be queued together, but an update of all apps excludes them.
* @return the refresh, or the first install, remove or update of all apps
*/
const Operation* findPendingOperationOfKind(bool refresh) {
if (state->operationRunning && (state->runningOperation.type == APP_HUB_OPERATION_REFRESH) == refresh) {
return &state->runningOperation;
Expand Down Expand Up @@ -539,7 +543,16 @@ error_t enqueue(AppHubOperationType type, const char* id, uint32_t* operationId)
return ERROR_INVALID_STATE;
}

const Operation* pending = findPendingOperationOfKind(type == APP_HUB_OPERATION_REFRESH);
const Operation* pending;
if (type == APP_HUB_OPERATION_REFRESH) {
pending = findPendingOperationOfKind(true);
} else {
pending = findPendingOperationOfKind(false);
// Installs and removes only conflict with an operation for the same app, or with an update of all apps
if (type != APP_HUB_OPERATION_UPDATE_ALL && (pending == nullptr || pending->type != APP_HUB_OPERATION_UPDATE_ALL)) {
pending = findPendingOperation(id);
}
}
if (pending != nullptr) {
const bool same = pending->type == type && (id == nullptr || strcmp(pending->app_id, id) == 0);
if (same && operationId != nullptr) {
Expand Down
32 changes: 32 additions & 0 deletions Modules/app-hub-module/tests/source/app_hub_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,38 @@ TEST_CASE("app_hub operations report their outcome and the worker stops when idl
task_event_group_destruct(&group);
}

TEST_CASE("app_hub queues operations of different apps and runs them in order") {
ensureFixture();
TaskEventGroup group {};
task_event_group_construct(&group);
AppHubEventSubscription sub {};
REQUIRE_EQ(app_hub_event_subscribe(&sub, &group), ERROR_NONE);

uint32_t firstId = 0;
uint32_t secondId = 0;
REQUIRE_EQ(app_hub_remove("first", &firstId), ERROR_NONE);
REQUIRE_EQ(app_hub_install("second", &secondId), ERROR_NONE);
CHECK_NE(firstId, secondId);

AppHubEvent event = waitForEvent(sub);
CHECK_EQ(event.type, APP_HUB_EVENT_REMOVING);
CHECK_EQ(event.operation_id, firstId);
event = waitForEvent(sub);
CHECK_EQ(event.type, APP_HUB_EVENT_REMOVED);
CHECK_EQ(event.operation_id, firstId);
CHECK_EQ(event.result, ERROR_NOT_FOUND);
event = waitForEvent(sub);
CHECK_EQ(event.type, APP_HUB_EVENT_INSTALLING);
CHECK_EQ(event.operation_id, secondId);
event = waitForEvent(sub);
CHECK_EQ(event.type, APP_HUB_EVENT_INSTALLED);
CHECK_EQ(event.operation_id, secondId);
CHECK_EQ(event.result, ERROR_NOT_FOUND);

CHECK_EQ(app_hub_event_unsubscribe(&sub), ERROR_NONE);
task_event_group_destruct(&group);
}

TEST_CASE("app_hub drops newer events while a subscriber's queue is full") {
ensureFixture();
TaskEventGroup group {};
Expand Down
7 changes: 7 additions & 0 deletions Modules/app-module/include/app/install.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <tactility/error.h>

#include <stddef.h>
#include <stdint.h>

struct Module;

Expand Down Expand Up @@ -43,6 +44,12 @@ error_t app_get_install_path(const char* app_id, char* path, size_t path_size);
*/
error_t app_install(const char* source_path);

/** @return true while an app_install() call is running on any task */
bool app_install_is_active(void);

/** @return the number of app_install() calls started since boot, wrapping around */
uint32_t app_install_get_started_count(void);

/**
* Uninstalls a previously app_install()-ed package: stops every one of its apps if currently
* running, deletes its install directory, and unregisters all of them (app_manager_remove()).
Expand Down
29 changes: 28 additions & 1 deletion Modules/app-module/source/install.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include <minitar.h>

#include <atomic>
#include <cerrno>
#include <cstdio>
#include <cstring>
Expand Down Expand Up @@ -129,6 +130,13 @@ bool untar_directory(const minitar_entry* entry, const std::string& destination_
}

bool untar(const std::string& tar_path, const std::string& destination_path) {
// minitar aborts on a file that isn't made of 512-byte blocks, e.g. a truncated or non-tar file
struct stat file_stat {};
if (stat(tar_path.c_str(), &file_stat) != 0 || file_stat.st_size % 512 != 0) {
LOG_E(TAG, "%s isn't a valid tar archive", tar_path.c_str());
return false;
}

minitar archive {};
if (minitar_open(tar_path.c_str(), &archive) != 0) {
LOG_E(TAG, "Failed to open %s", tar_path.c_str());
Expand Down Expand Up @@ -378,7 +386,7 @@ error_t app_get_install_path(const char* app_id, char* path, size_t path_size) {
return ERROR_NONE;
}

error_t app_install(const char* source_path) {
static error_t install_package(const char* source_path) {
LOG_I(TAG, "Installing app package from %s", source_path);

std::string app_parent_path;
Expand Down Expand Up @@ -476,6 +484,25 @@ error_t app_install(const char* source_path) {
return add_result;
}

static std::atomic<int> active_install_count = 0;
static std::atomic<uint32_t> started_install_count = 0;

error_t app_install(const char* source_path) {
started_install_count++;
active_install_count++;
const error_t result = install_package(source_path);
active_install_count--;
return result;
}

bool app_install_is_active(void) {
return active_install_count > 0;
}

uint32_t app_install_get_started_count(void) {
return started_install_count;
}

error_t app_uninstall(const char* app_id) {
LOG_I(TAG, "Uninstalling app %s", app_id);

Expand Down
12 changes: 12 additions & 0 deletions Modules/app-module/tests/source/install_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: Apache-2.0
#include "doctest.h"

#include <app/install.h>

TEST_CASE("app_install counts the started installs") {
const uint32_t before = app_install_get_started_count();
CHECK_FALSE(app_install_is_active());
CHECK_NE(app_install("/nonexistent/package.app"), ERROR_NONE);
CHECK_FALSE(app_install_is_active());
CHECK_EQ(app_install_get_started_count(), before + 1);
}
5 changes: 4 additions & 1 deletion Modules/c-symbols-module/source/module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,11 @@ static const ModuleSymbol SYMBOLS[] = {
DEFINE_MODULE_SYMBOL(strftime),
DEFINE_MODULE_SYMBOL(time),
DEFINE_MODULE_SYMBOL(difftime),
DEFINE_MODULE_SYMBOL(localtime),
DEFINE_MODULE_SYMBOL(mktime),
DEFINE_MODULE_SYMBOL(gmtime),
DEFINE_MODULE_SYMBOL(gmtime_r),
DEFINE_MODULE_SYMBOL(localtime),
DEFINE_MODULE_SYMBOL(localtime_r),
// math.h
DEFINE_MODULE_SYMBOL(acoshf),
DEFINE_MODULE_SYMBOL(acosf),
Expand Down
4 changes: 4 additions & 0 deletions Modules/gps-module/include/gps/gps.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,17 @@ enum GpsEventType {
GPS_EVENT_UNSUBSCRIBED, // Last event, device wants to destroy itself and unsubscribed the subscriber.
GPS_EVENT_MESSAGE_RMC,
GPS_EVENT_MESSAGE_GGA,
GPS_EVENT_MESSAGE_GSV,
GPS_EVENT_MESSAGE_GSA,
};

struct GpsEvent {
enum GpsEventType type;
union {
struct minmea_sentence_rmc rmc;
struct minmea_sentence_gga gga;
struct minmea_sentence_gsv gsv;
struct minmea_sentence_gsa gsa;
} data;
};

Expand Down
3 changes: 3 additions & 0 deletions Modules/location-module/source/gps_provider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ void receive(GpsProvider& provider) {
case GPS_EVENT_MESSAGE_GGA:
onSentence(provider, sub.event);
break;
case GPS_EVENT_MESSAGE_GSV:
case GPS_EVENT_MESSAGE_GSA:
break;
case GPS_EVENT_UNSUBSCRIBED:
provider.device = nullptr;
break;
Expand Down
7 changes: 7 additions & 0 deletions Modules/lvgl-module/source/widgets/spinner.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
#define LV_USE_PRIVATE_API 1 // For actual lv_obj_t declaration

#include <lvgl/theme.h>
#include <lvgl/ui_assets.h>
#include <lvgl/widgets/spinner.h>

Expand All @@ -27,6 +28,12 @@ lv_obj_t* lvgl_spinner_create(lv_obj_t* parent) {

lv_image_set_src(obj, lvgl_get_spinner_image());

// The spinner image isn't visible on every monochrome background, so it takes the color of the text next to it
if (lvgl_theme_is_mono()) {
lv_obj_set_style_image_recolor(obj, lv_obj_get_style_text_color(parent, LV_PART_MAIN), LV_STATE_DEFAULT);
lv_obj_set_style_image_recolor_opa(obj, LV_OPA_COVER, LV_STATE_DEFAULT);
}

return obj;
}

Expand Down
Loading
Loading