CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
SystemSettingsBackup.cpp
Go to the documentation of this file.
1
11
16
18#include "cdc_hal/IDisplay.h"
20#include "cdc_hal/IRtc.h"
22#include "cdc_ui/I18n.h"
23#include "cdc_log.h"
24
25#include "cJSON.h"
26
27#include <cstring>
28#include <cstdio>
29
30namespace cdc::os_ui {
31
32namespace {
33
34constexpr const char* TAG = "SysBackup";
35
37constexpr int kSchemaVer = 1;
38
40void addStr(cJSON* obj, const char* key, const char* value) {
41 if (value && value[0] != '\0') {
42 cJSON_AddStringToObject(obj, key, value);
43 }
44}
45
47const char* getStr(const cJSON* obj, const char* key) {
48 const cJSON* item = cJSON_GetObjectItemCaseSensitive(obj, key);
49 if (cJSON_IsString(item) && item->valuestring && item->valuestring[0] != '\0') {
50 return item->valuestring;
51 }
52 return nullptr;
53}
54
56bool getNum(const cJSON* obj, const char* key, double* out) {
57 const cJSON* item = cJSON_GetObjectItemCaseSensitive(obj, key);
58 if (cJSON_IsNumber(item)) {
59 *out = item->valuedouble;
60 return true;
61 }
62 return false;
63}
64
66bool getBool(const cJSON* obj, const char* key, bool* out) {
67 const cJSON* item = cJSON_GetObjectItemCaseSensitive(obj, key);
68 if (cJSON_IsBool(item)) {
69 *out = cJSON_IsTrue(item);
70 return true;
71 }
72 return false;
73}
74
75} // namespace
76
78 if (!out) return false;
79
80 cJSON_AddNumberToObject(out, "schema_ver", kSchemaVer);
81
82 // Language (I18n owns NVS "i18n"/"langc").
83 addStr(out, "language", cdc::ui::I18n::instance().getLanguageCode().c_str());
84
85 // Backlight (IDisplay owns NVS "display"/"backlight").
86 if (auto* display = hal::getDisplayInstance()) {
87 cJSON_AddNumberToObject(out, "backlight", display->getBacklight());
88 }
89
90 // Light-sleep interval in seconds (ISleepController owns NVS "sleep"/"interval").
91 if (auto* sleep = hal::getSleepControllerInstance()) {
92 cJSON_AddNumberToObject(out, "sleep_interval", sleep->getLightSleepInterval());
93 }
94
95 // Timezone offset in hours (IRtc owns NVS "rtc"/"tz_offset").
96 if (auto* rtc = hal::getRtcInstance()) {
97 cJSON_AddNumberToObject(out, "tz_offset", rtc->getTimezoneOffset());
98 }
99
100 // Badge display text (SettingsHandlers owns NVS "display"/name|info|info2).
102 if (cdc::ui::settings::loadDisplayField("name", buf, sizeof(buf))) addStr(out, "badge_name", buf);
103 if (cdc::ui::settings::loadDisplayField("info", buf, sizeof(buf))) addStr(out, "badge_info", buf);
104 if (cdc::ui::settings::loadDisplayField("info2", buf, sizeof(buf))) addStr(out, "badge_info2", buf);
105
106 // WiFi configuration (WifiHandlers owns NVS "wifi"/*). The container is
107 // passphrase-encrypted, so the credentials may be included.
108 auto& wifi = cdc::ui::WifiHandlers::instance();
109 const cdc::ui::WifiConfig& cfg = wifi.config();
110 if (cfg.valid) {
111 cJSON* w = cJSON_AddObjectToObject(out, "wifi");
112 if (w) {
113 addStr(w, "ssid", cfg.ssid);
114 addStr(w, "pass", cfg.password);
115 cJSON_AddNumberToObject(w, "sec", cfg.security);
116 cJSON_AddBoolToObject(w, "dhcp", cfg.useDhcp);
117 if (!cfg.useDhcp) {
118 cJSON_AddNumberToObject(w, "ip", cfg.staticIp);
119 cJSON_AddNumberToObject(w, "gw", cfg.gateway);
120 cJSON_AddNumberToObject(w, "nm", cfg.netmask);
121 }
122 }
123 }
124 cJSON_AddNumberToObject(out, "wifi_timeout", wifi.getConnectTimeoutMs());
125 cJSON_AddBoolToObject(out, "wifi_enabled", wifi.isUserEnabled());
126
127 // Per-module enable state (ModuleRegistry owns NVS "modules"/"disabled").
128 cJSON* mods = cJSON_AddObjectToObject(out, "modules_enabled");
129 if (mods) {
131 uint8_t count = reg.getModuleCount();
132 for (uint8_t i = 0; i < count; i++) {
133 cdc::core::IModule* m = reg.getModuleAt(i);
134 if (m && m->getName()) {
135 cJSON_AddBoolToObject(mods, m->getName(), reg.isModuleEnabled(i));
136 }
137 }
138 }
139
140 return true;
141}
142
145 if (!in) return r;
146
147 double sv = 0;
148 if (getNum(in, "schema_ver", &sv) && static_cast<int>(sv) != kSchemaVer) {
149 LOG_W(TAG, "system schema_ver %d != expected %d, skipping section",
150 static_cast<int>(sv), kSchemaVer);
151 return r;
152 }
153
154 // Language (applies live; persists via I18n).
155 if (const char* lang = getStr(in, "language")) {
156 if (cdc::ui::I18n::instance().setLanguageCode(lang)) r.imported++; else r.failed++;
157 }
158
159 // Backlight (applies live; persists to NVS).
160 double num = 0;
161 if (getNum(in, "backlight", &num)) {
162 if (auto* display = hal::getDisplayInstance()) {
163 display->setBacklight(static_cast<uint16_t>(num));
164 display->saveBacklight();
165 r.imported++;
166 } else {
167 r.failed++;
168 }
169 }
170
171 // Light-sleep interval.
172 if (getNum(in, "sleep_interval", &num)) {
173 if (auto* sleep = hal::getSleepControllerInstance()) {
174 sleep->setLightSleepInterval(static_cast<uint32_t>(num));
175 r.imported++;
176 } else {
177 r.failed++;
178 }
179 }
180
181 // Timezone offset.
182 if (getNum(in, "tz_offset", &num)) {
183 if (auto* rtc = hal::getRtcInstance()) {
184 rtc->setTimezoneOffset(static_cast<int8_t>(num));
185 r.imported++;
186 } else {
187 r.failed++;
188 }
189 }
190
191 // Badge display text (persisted; reflected on the lock screen at next boot).
192 if (const char* v = getStr(in, "badge_name")) { cdc::ui::settings::saveDisplayField("name", v); r.imported++; }
193 if (const char* v = getStr(in, "badge_info")) { cdc::ui::settings::saveDisplayField("info", v); r.imported++; }
194 if (const char* v = getStr(in, "badge_info2")) { cdc::ui::settings::saveDisplayField("info2", v); r.imported++; }
195
196 // WiFi configuration. Drive the wizard + saveConfig path so SSID, password,
197 // security and static-IP fields are all persisted; the connect intent is
198 // applied at the next restoreOnBoot, never via a blocking connect here.
199 auto& wifi = cdc::ui::WifiHandlers::instance();
200 const cJSON* w = cJSON_GetObjectItemCaseSensitive(in, "wifi");
201 if (cJSON_IsObject(w)) {
202 const char* ssid = getStr(w, "ssid");
203 if (ssid) {
204 cdc::ui::WifiWizard& wz = wifi.wizard();
205 wz.reset();
206 std::strncpy(wz.ssid, ssid, sizeof(wz.ssid) - 1);
207 if (const char* pass = getStr(w, "pass")) {
208 std::strncpy(wz.password, pass, sizeof(wz.password) - 1);
209 }
210 double secNum = static_cast<uint8_t>(hal::WifiSecurity::WPA2_PSK);
211 getNum(w, "sec", &secNum);
212 wz.security = static_cast<hal::WifiSecurity>(static_cast<uint8_t>(secNum));
213
214 bool dhcp = true;
215 getBool(w, "dhcp", &dhcp);
216 wz.useDhcp = dhcp;
217 if (!dhcp) {
218 // saveConfig() re-parses dotted-quad strings; reconstruct them
219 // from the packed values produced at export.
220 auto packToStr = [](const cJSON* obj, const char* key, char* dst, size_t dstSize) {
221 double v = 0;
222 getNum(obj, key, &v);
223 uint32_t ip = static_cast<uint32_t>(v);
224 std::snprintf(dst, dstSize, "%u.%u.%u.%u",
225 static_cast<unsigned>((ip >> 24) & 0xFF),
226 static_cast<unsigned>((ip >> 16) & 0xFF),
227 static_cast<unsigned>((ip >> 8) & 0xFF),
228 static_cast<unsigned>(ip & 0xFF));
229 };
230 packToStr(w, "ip", wz.staticIp, sizeof(wz.staticIp));
231 packToStr(w, "gw", wz.gateway, sizeof(wz.gateway));
232 packToStr(w, "nm", wz.netmask, sizeof(wz.netmask));
233 }
234 wifi.saveConfig();
235 r.imported++;
236 } else {
237 r.failed++;
238 }
239 }
240
241 if (getNum(in, "wifi_timeout", &num)) {
242 if (wifi.setConnectTimeoutMs(static_cast<uint32_t>(num))) r.imported++; else r.failed++;
243 }
244
245 bool wifiEnabled = false;
246 if (getBool(in, "wifi_enabled", &wifiEnabled)) {
247 wifi.persistUserIntent(wifiEnabled);
248 r.imported++;
249 }
250
251 // Per-module enable state.
252 const cJSON* mods = cJSON_GetObjectItemCaseSensitive(in, "modules_enabled");
253 if (cJSON_IsObject(mods)) {
255 uint8_t count = reg.getModuleCount();
256 for (const cJSON* item = mods->child; item; item = item->next) {
257 if (!item->string || !cJSON_IsBool(item)) {
258 r.failed++;
259 continue;
260 }
261 bool found = false;
262 for (uint8_t i = 0; i < count; i++) {
263 cdc::core::IModule* m = reg.getModuleAt(i);
264 if (m && m->getName() && std::strcmp(m->getName(), item->string) == 0) {
265 reg.setModuleEnabled(i, cJSON_IsTrue(item));
266 r.imported++;
267 found = true;
268 break;
269 }
270 }
271 if (!found) r.failed++;
272 }
273 }
274
275 LOG_I(TAG, "System settings import: %u applied, %u skipped", r.imported, r.failed);
276 return r;
277}
278
279} // namespace cdc::os_ui
static const char * TAG
Internationalization with English fallbacks in code and overlay translations loaded at runtime from a...
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
Definition cdc_log.h:146
#define LOG_I(tag, fmt,...)
Definition cdc_log.h:147
Module interface that extends IService with module-specific features.
Definition IModule.h:55
virtual const char * getName() const =0
static ModuleRegistry & instance()
Returns the singleton module registry instance.
static bool exportSystemSettings(cJSON *out)
Writes the user-configurable NVS settings into out.
static cdc::core::IModule::BackupResult importSystemSettings(const cJSON *in)
Restores the system settings from in best-effort.
static I18n & instance()
Singleton accessor.
Definition I18n.cpp:287
static constexpr uint8_t MAX_TEXT_LEN
static WifiHandlers & instance()
Returns singleton Wi-Fi handlers instance.
IDisplay * getDisplayInstance()
Returns lazily created singleton display instance.
IRtc * getRtcInstance()
Returns the singleton RTC service instance.
Definition Rtc.cpp:304
ISleepController * getSleepControllerInstance()
Returns the singleton sleep controller service instance.
bool loadDisplayField(const char *key, char *out, size_t outSize)
Reads one display text field from NVS into the caller buffer.
void saveDisplayField(const char *key, const char *value)
Saves one display text field to NVS.
Per-module restore outcome reported by importBackup().
Definition IModule.h:85
uint16_t failed
Records skipped due to errors.
Definition IModule.h:87
uint16_t imported
Records restored successfully.
Definition IModule.h:86
hal::WifiSecurity security
void reset()
Resets Wi-Fi wizard state to defaults.