Skip to content

Commit d59857a

Browse files
author
Krim
committed
Upgrade docs, reduce RAM usage, and fix minor bugs (plugin power_source mapping, serial diagnostic label)
1 parent 5c180b5 commit d59857a

52 files changed

Lines changed: 606 additions & 543 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,16 @@ Modular firmware for the CDC Badge v1.0/v1.1 hardware security key featuring TRO
2727
| **TOTP Authenticator** | Working | Time-based OTP (100 accounts, Google Authenticator compatible) |
2828
| **Password Vault** | Working | Secure password storage (369 entries) |
2929
| **GPG/CCID** | Working (UI WIP) | OpenPGP smartcard via USB CCID, sign / encrypt / decrypt / SSH end-to-end with GnuPG |
30-
| **BLE vCard** | WIP | Badge-to-badge contact exchange via BLE |
31-
| **BLE HID** | WIP | Bluetooth keyboard for auto-type |
30+
| **BLE vCard** | ⚠️ **WIP, untested on hardware.** | Badge-to-badge contact exchange via BLE |
31+
| **BLE HID** | ⚠️ **WIP, untested on hardware.** | Bluetooth keyboard for auto-type |
3232
| **WiFi + NTP** | Working | Time synchronization over WiFi, serial control (scan, connect, status, ..) |
33-
| **BLE Serial** | WIP | Bluetooth serial console (Nordic UART Service) |
33+
| **BLE Serial** | ⚠️ **WIP, untested on hardware.** | Bluetooth serial console (Nordic UART Service) |
3434
| **SAO Detection** | Working | Shitty Add-On port detection and info |
3535
| **E-Paper Display** | Working | 2.9" low-power display with backlight |
3636
| **12-Button Keypad** | Working | Phone-style T9 input |
3737
| **Multi-Language** | Working | English and German UI |
3838
| **Secure Serial** | Working | PIN authentication for serial commands |
39-
| **WASM Plugin Runtime** | WIP | Sandboxed third-party plugins via WebAssembly (WAMR Fast Interpreter). Host API exposes 80+ symbols under module `"cdc"`, including a Canvas view for plugin-drawn UIs. |
39+
| **WASM Plugin Runtime** | Working | Sandboxed third-party plugins via WebAssembly (WAMR). The host API under module `"cdc"` covers NVS, vFAT, i18n, HTTP/WiFi, GPIO/ADC/I2C, Pixel-Strip, BLE, SecureElement, Crypto, and UI views including a Canvas for plugin-drawn UIs. |
4040
| **vFAT File Browser** | Working | On-device file explorer (Tools  Expert) and a `VFAT` serial shell for the plugins FAT partition. |
4141

4242
### Planned
@@ -56,9 +56,9 @@ partition and can be installed / updated without re-flashing.
5656
| Web installer | [krim404.github.io/cdc-badge-plugins](https://krim404.github.io/cdc-badge-plugins/) |
5757
| SDK + examples + source | [github.com/krim404/cdc-badge-plugins](https://github.com/krim404/cdc-badge-plugins) |
5858

59-
The plugin host API surface (80+ functions: NVS, sandboxed vFAT file access,
60-
i18n, HTTP, WiFi, GPIO, ADC, I2C, Pixel-Strip, BLE, SecureElement, Crypto, UI
61-
views, Canvas, RGB Color Picker, …) is defined canonically in
59+
The plugin host API surface (NVS, sandboxed vFAT file access, i18n, HTTP, WiFi,
60+
GPIO, ADC, I2C, Pixel-Strip, BLE, SecureElement, Crypto, UI views, Canvas, RGB
61+
Color Picker, …) is defined canonically in
6262
[`components/plugin_manager/include/plugin_manager/host_api.h`](components/plugin_manager/include/plugin_manager/host_api.h)
6363
and mirrored byte-identical into the SDK repo.
6464

@@ -129,7 +129,8 @@ reset.
129129
- Tamper-resistant key storage
130130
- Keys cannot be extracted or cloned
131131

132-
See [Module Development Guide](docs/MODULE_DEVELOPMENT.md) for the storage map.
132+
See [Module Development Guide](docs/MODULE_DEVELOPMENT.md) for the storage map
133+
(canonical source: `main/tropic_slot_map.h`).
133134

134135
## Hardware
135136

@@ -163,7 +164,7 @@ pip install -r tools/requirements.txt
163164
python tools/flash_firmware.py --release latest
164165

165166
# Flash a specific version
166-
python tools/flash_firmware.py --release v0.4.1
167+
python tools/flash_firmware.py --release latest
167168

168169
# Flash from a local directory
169170
python tools/flash_firmware.py --dir ./artifacts/

assets/i18n/lang_de.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
"core.hw_entries": "Einträge",
114114
"core.hw_battery": "Batterie",
115115
"core.hw_temp": "Temperatur",
116+
"core.hw_cpu_load": "CPU-Last",
116117
"core.hw_charging_suffix": " (laden)",
117118
"core.hw_not_available": "n/v",
118119
"core.actions": "Aktionen",

components/cdc_core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ idf_component_register(
66
"src/AttestationKeyService.cpp"
77
"src/ServiceRegistry.cpp"
88
"src/Cp437.cpp"
9+
"src/CpuStats.cpp"
910
"src/EventBus.cpp"
1011
"src/PinManager.cpp"
1112
"src/ModuleRegistry.cpp"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
5+
namespace cdc::core {
6+
7+
/**
8+
* \brief On-demand aggregate CPU-load read-out from FreeRTOS run-time stats.
9+
*
10+
* Requires CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS. Load is derived from the
11+
* idle-task run-time counters versus wall-clock time across all cores. Nothing
12+
* is sampled in the background; a caller measures only when it asks.
13+
*/
14+
class CpuStats {
15+
public:
16+
/**
17+
* \brief Snapshot cumulative idle CPU time and the wall-clock reference.
18+
* \param idleUs Out: summed run-time of every core's idle task, in run-time
19+
* counter units (microseconds with the esp_timer source).
20+
* \param wallUs Out: current wall clock from esp_timer, in microseconds.
21+
* \return true on success, false if run-time stats are unavailable.
22+
*/
23+
static bool sample(uint64_t& idleUs, uint64_t& wallUs);
24+
25+
/**
26+
* \brief Measure aggregate CPU load over a blocking window.
27+
* \param windowMs Measurement window in milliseconds.
28+
* \return CPU load across all cores as 0..100 percent.
29+
*/
30+
static uint8_t loadOverWindow(uint32_t windowMs = 250);
31+
};
32+
33+
} // namespace cdc::core
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#include "cdc_core/CpuStats.h"
2+
#include "cdc_core/Raii.h"
3+
4+
#include "freertos/FreeRTOS.h"
5+
#include "freertos/task.h"
6+
#include "esp_timer.h"
7+
8+
#include <cstring>
9+
10+
namespace cdc::core {
11+
12+
bool CpuStats::sample(uint64_t& idleUs, uint64_t& wallUs)
13+
{
14+
#if (configGENERATE_RUN_TIME_STATS == 1) && (configUSE_TRACE_FACILITY == 1)
15+
UBaseType_t count = uxTaskGetNumberOfTasks();
16+
if (count == 0) return false;
17+
18+
auto buf = psramAlloc<TaskStatus_t>(count);
19+
if (!buf) return false;
20+
21+
UBaseType_t got = uxTaskGetSystemState(buf.get(), count, nullptr);
22+
uint64_t idle = 0;
23+
for (UBaseType_t i = 0; i < got; ++i) {
24+
// ESP-IDF SMP names the per-core idle tasks "IDLE0"/"IDLE1"; sum them.
25+
if (buf[i].pcTaskName && std::strncmp(buf[i].pcTaskName, "IDLE", 4) == 0) {
26+
idle += buf[i].ulRunTimeCounter;
27+
}
28+
}
29+
30+
idleUs = idle;
31+
wallUs = static_cast<uint64_t>(esp_timer_get_time());
32+
return true;
33+
#else
34+
(void)idleUs;
35+
(void)wallUs;
36+
return false;
37+
#endif
38+
}
39+
40+
uint8_t CpuStats::loadOverWindow(uint32_t windowMs)
41+
{
42+
uint64_t idle0, wall0, idle1, wall1;
43+
if (!sample(idle0, wall0)) return 0;
44+
vTaskDelay(pdMS_TO_TICKS(windowMs));
45+
if (!sample(idle1, wall1)) return 0;
46+
47+
uint64_t wallDelta = (wall1 - wall0) * static_cast<uint64_t>(configNUMBER_OF_CORES);
48+
if (wallDelta == 0) return 0;
49+
uint64_t idleDelta = idle1 - idle0;
50+
if (idleDelta > wallDelta) idleDelta = wallDelta; // clamp counter-wrap glitch
51+
return static_cast<uint8_t>(100 - (idleDelta * 100) / wallDelta);
52+
}
53+
54+
} // namespace cdc::core

components/cdc_core/src/ModuleRegistry.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ namespace cdc::core {
2020
* \return Registry singleton reference.
2121
*/
2222
ModuleRegistry& ModuleRegistry::instance() {
23-
static ModuleRegistry instance;
24-
return instance;
23+
static ModuleRegistry* instance = new ModuleRegistry();
24+
return *instance;
2525
}
2626

2727
/**

components/cdc_hal/src/BluetoothController.cpp

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "cdc_hal/hw_config.h"
1010
#include "cdc_log.h"
1111
#include "sdkconfig.h"
12+
#include "esp_attr.h"
1213

1314
static const char* TAG = "BT-Ctrl";
1415

@@ -96,7 +97,7 @@ struct InternalService {
9697
uint8_t numChars = 0;
9798
};
9899

99-
static InternalService s_services[MAX_REGISTERED_SERVICES];
100+
EXT_RAM_BSS_ATTR static InternalService s_services[MAX_REGISTERED_SERVICES];
100101

101102
/**
102103
* \brief Converts a generic BLE UUID into NimBLE's `ble_uuid_any_t` format.
@@ -152,7 +153,7 @@ static ble_gatt_chr_flags mapProperties(uint8_t props, uint8_t perms) {
152153
* cannot race with itself. Hoisting it out of `gattServiceAccessCb` reclaims
153154
* roughly 1 KB of host-task stack per call.
154155
*/
155-
static uint8_t s_gattAccessBuf[512];
156+
EXT_RAM_BSS_ATTR static uint8_t s_gattAccessBuf[512];
156157

157158
static int gattServiceAccessCb(uint16_t connHandle, uint16_t attrHandle,
158159
struct ble_gatt_access_ctxt* ctxt, void* arg) {
@@ -2126,17 +2127,13 @@ void BluetoothController::setWriteCompleteCallback(WriteCompleteCallback cb) {
21262127
if (cb) addListener(writeCompleteCallbacks_, cb);
21272128
}
21282129

2129-
/**
2130-
* \brief Singleton Bluetooth controller instance.
2131-
*/
2132-
static BluetoothController g_bluetoothController;
2133-
21342130
/**
21352131
* \brief Returns the singleton Bluetooth controller instance.
21362132
* \return Pointer to the global `IBluetoothController` implementation.
21372133
*/
21382134
IBluetoothController* getBluetoothControllerInstance() {
2139-
return &g_bluetoothController;
2135+
static BluetoothController* g_bluetoothController = new BluetoothController();
2136+
return g_bluetoothController;
21402137
}
21412138

21422139
} // namespace cdc::hal

components/cdc_hal/src/TCA9535Keypad.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
#include "cdc_core/SystemLock.h"
1010
#include "cdc_log.h"
1111
#include "esp_attr.h"
12+
#include "esp_heap_caps.h"
1213
#include "driver/gpio.h"
1314
#include "freertos/FreeRTOS.h"
15+
#include "freertos/idf_additions.h"
1416
#include "freertos/task.h"
1517
#include "freertos/semphr.h"
1618

@@ -241,8 +243,8 @@ bool TCA9535Keypad::init() {
241243
}
242244

243245
// Create task
244-
BaseType_t ret = xTaskCreate(taskFunc, "keypad", TASK_STACK_SIZE,
245-
this, TASK_PRIORITY, &taskHandle_);
246+
BaseType_t ret = xTaskCreateWithCaps(taskFunc, "keypad", TASK_STACK_SIZE,
247+
this, TASK_PRIORITY, &taskHandle_, MALLOC_CAP_SPIRAM);
246248
if (ret != pdPASS) {
247249
LOG_E(TAG, "Failed to create task");
248250
vSemaphoreDelete(semaphore_);

components/cdc_os_ui/src/HardwareInfo.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "cdc_views/InfoView.h"
44
#include "cdc_ui/I18n.h"
5+
#include "cdc_core/CpuStats.h"
56
#include "cdc_hal/II2cBus.h"
67
#include "cdc_hal/IPowerManager.h"
78
#include "cdc_hal/IKeypad.h"
@@ -174,6 +175,8 @@ static void buildHardwareInfoText(char* buf, size_t bufSize) {
174175

175176
uint64_t uptimeS = esp_timer_get_time() / 1000000ULL;
176177
append("%s: %llu s\n", ui::tr("core.hw_uptime"), (unsigned long long)uptimeS);
178+
179+
append("%s: %u%%\n", ui::tr("core.hw_cpu_load"), cdc::core::CpuStats::loadOverWindow());
177180
}
178181

179182
/**

components/cdc_os_ui/src/SleepManager.cpp

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
#include "cdc_core/EventBus.h"
88
#include "cdc_log.h"
99
#include "esp_timer.h"
10-
#include "freertos/FreeRTOS.h"
11-
#include "freertos/task.h"
1210
#include <ctime>
1311
#include <cstdio>
1412
#include <cstring>
@@ -116,11 +114,10 @@ void SleepManager::enterLockScreenSleep() {
116114
lockScreen_->addStatusIcon(StatusIcon::LIGHT_SLEEP);
117115
inLightSleep_ = true;
118116

119-
// Render the icon before sleeping
120-
ViewStack::instance().render();
121-
122-
// Wait for E-Paper partial refresh to complete
123-
vTaskDelay(pdMS_TO_TICKS(350));
117+
// Render the icon synchronously so the full SPI command/data stream reaches
118+
// the panel before light sleep halts the chip; otherwise an in-flight
119+
// refresh is frozen mid-transfer and the panel is left half-rendered.
120+
ViewStack::instance().render(true);
124121

125122
// Enter light sleep (blocking call, returns after wakeup)
126123
sleep_->enterLightSleep();
@@ -172,9 +169,9 @@ void SleepManager::handleWakeup() {
172169
// Update power icons
173170
updatePowerStatusIcons();
174171

175-
// Render clock update
176-
ViewStack::instance().render();
177-
vTaskDelay(pdMS_TO_TICKS(350));
172+
// Render clock update synchronously so the panel update completes
173+
// before we re-enter light sleep below.
174+
ViewStack::instance().render(true);
178175

179176
// Check if USB was connected during sleep
180177
if (power_ && power_->isUsbConnected()) {

0 commit comments

Comments
 (0)