4#include "cdc_msg/MessageTransfer.h"
24#include "freertos/FreeRTOS.h"
25#include "freertos/semphr.h"
30static const char*
TAG =
"VCARD";
35 {
"mod_vcard.title",
"vCards"},
36 {
"mod_vcard.my_vcard",
"My vCard"},
37 {
"mod_vcard.nearby",
"Nearby"},
38 {
"mod_vcard.scan",
"Start Scan"},
39 {
"mod_vcard.stop_scan",
"Stop Scan"},
40 {
"mod_vcard.advertising",
"Start Advertising"},
41 {
"mod_vcard.stop_adv",
"Stop Advertising"},
42 {
"mod_vcard.exchange",
"Exchange"},
43 {
"mod_vcard.no_peers",
"No peers found"},
44 {
"mod_vcard.scanning",
"Scanning..."},
45 {
"mod_vcard.exchange_req",
"Exchange Request"},
46 {
"mod_vcard.accept",
"Accept"},
47 {
"mod_vcard.decline",
"Decline"},
48 {
"mod_vcard.exchange_ok",
"Exchange successful"},
49 {
"mod_vcard.exchange_fail",
"Exchange failed"},
50 {
"mod_vcard.connecting",
"Connecting..."},
51 {
"mod_vcard.edit_my_vcard",
"Edit my vCard"},
52 {
"mod_vcard.no_vcard",
"No vCard set"},
53 {
"mod_vcard.given_name",
"First name"},
54 {
"mod_vcard.family_name",
"Last name"},
55 {
"mod_vcard.formatted_name",
"Display name"},
56 {
"mod_vcard.organization",
"Organization"},
57 {
"mod_vcard.position",
"Position"},
58 {
"mod_vcard.email",
"Email"},
59 {
"mod_vcard.tel_cell",
"Phone (Mobile)"},
60 {
"mod_vcard.tel_home",
"Phone (Home)"},
61 {
"mod_vcard.tel_work",
"Phone (Work)"},
62 {
"mod_vcard.url",
"Website"},
63 {
"mod_vcard.telegram",
"Telegram"},
64 {
"mod_vcard.matrix",
"Matrix"},
65 {
"mod_vcard.threema",
"Threema"},
66 {
"mod_vcard.social_profile",
"Social Profile"},
67 {
"mod_vcard.note",
"Note"},
68 {
"mod_vcard.send",
"Send vCard"},
69 {
"mod_vcard.received",
"Contact (vCard)"},
70 {
"mod_vcard.received_title",
"Received vCards"},
71 {
"mod_vcard.no_received",
"No received vCards"},
72 {
"mod_vcard.show_qr",
"Show QR"},
73 {
"mod_vcard.forward",
"Forward"},
74 {
"mod_vcard.confirm_delete",
"Delete this contact?"},
78 "mod_vcard.given_name",
"mod_vcard.family_name",
"mod_vcard.formatted_name",
79 "mod_vcard.organization",
"mod_vcard.position",
"mod_vcard.email",
80 "mod_vcard.tel_cell",
"mod_vcard.tel_home",
"mod_vcard.tel_work",
81 "mod_vcard.url",
"mod_vcard.telegram",
"core.signal",
82 "mod_vcard.matrix",
"mod_vcard.threema",
"mod_vcard.social_profile",
122static void showVcardDetails(
const char* title,
const char* raw,
bool withActions);
123static void showVcardQr(
const char* raw,
const char* fallbackTitle);
134static bool deliverVcard(
const uint8_t* data, uint32_t len,
const char* ,
138 memcpy(buf, data, len);
142 LOG_W(
TAG,
"Failed to store received vCard: %s", err);
162 memset(&s_parsed, 0,
sizeof(s_parsed));
166 const int cap =
static_cast<int>(
sizeof(s_text));
167 auto append = [&](
const char* fmt,
const char* a,
const char* b) {
168 if (n >= cap - 1)
return;
169 int w = snprintf(s_text + n,
static_cast<size_t>(cap - n), fmt, a, b);
171 if (n > cap - 1) n = cap - 1;
174 if (s_parsed.formatted_name[0]) {
175 append(
"%s%s\n\n", s_parsed.formatted_name,
"");
176 }
else if (s_parsed.given_name[0] || s_parsed.family_name[0]) {
177 append(
"%s %s\n\n", s_parsed.given_name, s_parsed.family_name);
180 auto field = [&](
const char* labelKey,
const char* value) {
181 if (value[0]) append(
"%s: %s\n",
ui::tr(labelKey), value);
183 field(
"mod_vcard.organization", s_parsed.organization);
184 field(
"mod_vcard.position", s_parsed.title);
185 field(
"mod_vcard.email", s_parsed.email);
186 field(
"mod_vcard.tel_cell", s_parsed.tel_cell);
187 field(
"mod_vcard.tel_home", s_parsed.tel_home);
188 field(
"mod_vcard.tel_work", s_parsed.tel_work);
189 field(
"mod_vcard.url", s_parsed.url);
190 field(
"mod_vcard.telegram", s_parsed.impp_telegram);
191 field(
"core.signal", s_parsed.impp_signal);
192 field(
"mod_vcard.matrix", s_parsed.impp_matrix);
193 field(
"mod_vcard.threema", s_parsed.impp_threema);
194 field(
"mod_vcard.social_profile", s_parsed.social_profile);
195 field(
"mod_vcard.note", s_parsed.note);
197 if (n == 0) snprintf(s_text,
sizeof(s_text),
"%s", raw);
200 s_detailView.
init(title, s_text);
210static void showVcardQr(
const char* raw,
const char* fallbackTitle) {
212 static char s_qrTitle[96];
213 static char s_qrSubtitle[96];
215 memset(&s_parsed, 0,
sizeof(s_parsed));
218 if (s_parsed.formatted_name[0]) {
219 snprintf(s_qrTitle,
sizeof(s_qrTitle),
"%s", s_parsed.formatted_name);
220 }
else if (s_parsed.given_name[0] || s_parsed.family_name[0]) {
221 snprintf(s_qrTitle,
sizeof(s_qrTitle),
"%s %s",
222 s_parsed.given_name, s_parsed.family_name);
224 snprintf(s_qrTitle,
sizeof(s_qrTitle),
"%s", fallbackTitle);
227 const char* sub = s_parsed.organization[0] ? s_parsed.organization
228 : s_parsed.title[0] ? s_parsed.title
229 : s_parsed.email[0] ? s_parsed.email
231 snprintf(s_qrSubtitle,
sizeof(s_qrSubtitle),
"%s", sub);
233 ui::showQRCode(raw, s_qrTitle, s_qrSubtitle[0] ? s_qrSubtitle :
nullptr);
284 cdc::msg::MessageTransfer::instance().beginInteractiveSend(
285 "text/vcard",
reinterpret_cast<const uint8_t*
>(own),
286 static_cast<uint32_t
>(len));
311 reinterpret_cast<void*
>(
static_cast<uintptr_t
>(slot))};
324 s_activeSlot =
static_cast<uint16_t
>(
reinterpret_cast<uintptr_t
>(userData));
351 if (len == 0)
return;
352 cdc::msg::MessageTransfer::instance().beginInteractiveSend(
353 "text/vcard",
reinterpret_cast<const uint8_t*
>(raw),
354 static_cast<uint32_t
>(len));
362 uint16_t slot = *
static_cast<uint16_t*
>(userData);
406 s_activeSlot =
static_cast<uint16_t
>(
reinterpret_cast<uintptr_t
>(userData));
435 return ui::tr(
"mod_vcard.my_vcard");
484 esp_timer_create_args_t args = {
487 .dispatch_method = ESP_TIMER_TASK,
488 .name =
"vcard_idle",
489 .skip_unhandled_events =
true,
508 if (strncmp(line,
"---", 3) == 0) {
517 Console::printf(
"OK: vCard updated\r\n");
519 Console::printf(
"ERROR: %s\r\n", err[0] ? err :
"Invalid vCard");
523 Console::showPrompt();
527 if (strcmp(line,
"ABORT") == 0 || strcmp(line,
"VCARD_ABORT") == 0) {
528 Console::printf(
"ABORTED\r\n");
530 Console::showPrompt();
534 size_t lineLen = strlen(line);
540 Console::printf(
"ERROR: vCard too large\r\n");
542 Console::showPrompt();
559 int slot = atoi(args);
563 Console::printf(
"ERROR: no vCard at id %d\r\n", slot);
569 Console::printf(
"Paste vCard 4.0, end with '---' on a new line "
570 "(or 'ABORT' to cancel):\r\n");
585 while ((next = strchr(line,
'\n')) !=
nullptr) {
587 Console::printf(
"%s\r\n", line);
591 Console::printf(
"%s\r\n", line);
608 int slot = atoi(args);
611 Console::printf(
"ERROR: no vCard at id %d\r\n", slot);
620 Console::printf(
"BEGIN:VCARD\r\n");
621 Console::printf(
"VERSION:4.0\r\n");
622 Console::printf(
"N:;;\r\n");
623 Console::printf(
"FN:\r\n");
624 Console::printf(
"NOTE:\r\n");
625 Console::printf(
"TEL;TYPE=HOME:\r\n");
626 Console::printf(
"TEL;TYPE=CELL:\r\n");
627 Console::printf(
"TEL;TYPE=WORK:\r\n");
628 Console::printf(
"EMAIL:\r\n");
629 Console::printf(
"URL:\r\n");
630 Console::printf(
"ORG:\r\n");
631 Console::printf(
"TITLE:\r\n");
632 Console::printf(
"X-SOCIALPROFILE:\r\n");
633 Console::printf(
"IMPP:telegram:\r\n");
634 Console::printf(
"IMPP:signal:\r\n");
635 Console::printf(
"IMPP:matrix:\r\n");
636 Console::printf(
"IMPP:threema:\r\n");
637 Console::printf(
"END:VCARD\r\n");
655 Console::printf(
"No received vCards\r\n");
660 for (uint16_t i = 0; i < count; i++) {
662 Console::printf(
"%2u %s\r\n", slots[i],
name);
677 int slot = atoi(args);
680 Console::printf(
"ERROR: no vCard at id %d\r\n", slot);
683 Console::printf(
"OK: vCard %d deleted\r\n", slot);
688 Console::printf(
"OK: vCard deleted\r\n");
690 Console::printf(
"ERROR: Failed to delete vCard\r\n");
695 {
"SET",
"[id]",
"Set own vCard, or overwrite received vCard <id> (multiline paste, end with '---' or 'ABORT')",
cmdVcardSet},
696 {
"GET",
"[id]",
"Show own vCard, or received vCard <id>",
cmdVcardGet},
698 {
"DELETE",
"[id]",
"Delete own vCard, or received vCard <id>",
cmdVcardDelete},
699 {
nullptr,
nullptr,
nullptr,
nullptr},
711 reg.registerCommand({
"VCARD",
712 "vCard storage: SET/GET/LIST/DELETE",
734 LOG_I(
TAG,
"Initializing vCard module");
741 cdc::msg::MessageTransfer::instance().registerHandler(
765 cdc::msg::MessageTransfer::instance().unregisterHandler(
"text/vcard");
776 if (!items || maxItems == 0)
return 0;
779 ui::tr(
"mod_vcard.title"),
805 if (!items || maxItems == 0)
return 0;
836 if (!out)
return false;
838 cJSON_AddNumberToObject(out,
"schema_ver",
kSchemaVer);
844 cJSON_AddStringToObject(out,
"own", buf);
848 cJSON* received = cJSON_AddArrayToObject(out,
"received");
849 if (!received)
return any;
853 for (uint16_t i = 0; i < count; i++) {
855 cJSON* item = cJSON_CreateString(buf);
857 cJSON_AddItemToArray(received, item);
877 if (!cJSON_IsString(je) || !je->valuestring || je->valuestring[0] ==
'\0')
return false;
879 const char* text = je->valuestring;
880 size_t len = strlen(text);
901 const cJSON* schemaVer = cJSON_GetObjectItemCaseSensitive(in,
"schema_ver");
902 if (cJSON_IsNumber(schemaVer) &&
static_cast<int>(schemaVer->valuedouble) !=
kSchemaVer) {
903 LOG_W(
TAG,
"vCard backup schema_ver %d != expected %d, skipping",
904 static_cast<int>(schemaVer->valuedouble),
kSchemaVer);
910 const cJSON* own = cJSON_GetObjectItemCaseSensitive(in,
"own");
911 if (cJSON_IsString(own) && own->valuestring && own->valuestring[0] !=
'\0') {
916 LOG_W(
TAG,
"vCard import: own vCard rejected (%s)", err);
921 const cJSON* received = cJSON_GetObjectItemCaseSensitive(in,
"received");
936 auto&
module = cdc::mod_vcard::VcardModule::instance();
Internationalization with English fallbacks in code and overlay translations loaded at runtime from a...
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
void mod_vcard_register()
Registers vCard module initializer in global module registry.
Shared RAII wrappers for firmware resources.
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
#define LOG_I(tag, fmt,...)
bool registerModule(IModule *module)
Registers a module instance in the runtime registry.
static ModuleRegistry & instance()
Returns the singleton module registry instance.
void registerInitializer(ModuleInitFunc initFunc)
Registers a deferred module initializer callback.
void onTick(uint32_t nowMs) override
Periodic vCard module tick forwarding BLE state machine.
void stop() override
Stops vCard BLE service and module runtime.
bool init() override
Initializes module UI strings, serial commands, and BLE service hooks.
static VcardModule & instance()
Returns singleton vCard module instance.
bool exportBackup(cJSON *out) override
Exports the own vCard and all received vCards into the backup section.
uint8_t getMenuItems(core::ModuleMenuItem *items, uint8_t maxItems) override
Provides tools-menu entry for vCard module.
bool start() override
Starts vCard module service.
core::IModule::BackupResult importBackup(const cJSON *in) override
Restores the own vCard and received vCards from the backup section.
const char * getName() const override
uint8_t getLockScreenContextItems(core::LockScreenContextItem *items, uint8_t maxItems) override
Provides the lock-screen quick action that shows the owner vCard as a QR code.
static void start(ui::IView *returnAnchor)
Starts the wizard with an empty struct.
static void editReceived(ui::IView *returnAnchor, uint16_t slot, DoneCallback onDone)
Starts the wizard prefilled with a stored contact for editing.
static void configure(const char *const *titleKeys, const char *savedKey, const char *failedKey)
Configures the wizard with i18n keys. Must be called before start() or edit() so step titles can be l...
static void startReceived(ui::IView *returnAnchor, DoneCallback onDone)
Starts the wizard to create a new stored contact (received list).
static void edit(ui::IView *returnAnchor)
Starts the wizard prefilled with the currently stored own vCard. Falls back to start() when no vCard ...
static void showPrompt()
Prints standard shell prompt.
static void printf(const char *format,...) __attribute__((format(printf
Prints formatted text to console.
virtual void setLineInterceptor(LineInterceptor interceptor)
static I18n & instance()
Singleton accessor.
void registerEnglishTable(const I18nEntry *entries, std::size_t count)
Append English entries to the lookup table.
void setOnMenu(MenuCallback onMenu, void *userData=nullptr)
static constexpr uint16_t MAX_TEXT_LEN
void init(const char *title, const char *text)
static ViewStack & instance()
Returns singleton view-stack instance.
void popToAnchor(IView *anchor)
Pops views until the specified anchor view is the current view.
void push(IView *view, void *context=nullptr)
static constexpr int kSchemaVer
Schema version written to and expected from the vCard backup section.
static void vcard_session_clear()
static bool deliverVcard(const uint8_t *data, uint32_t len, const char *, const char *)
Delivers a received vCard into the contact store.
static void onReceivedSelect(uint16_t index, void *userData)
Opens the detail view (with action menu) for the selected contact.
static void ctxReceivedQr()
Context-menu action: show the active contact as a QR code.
static void onReceivedDeleteConfirm(void *userData)
Confirm-dialog handler: deletes the contact and refreshes the list.
static constexpr int64_t VCARD_IDLE_LIMIT_US
static void cmdVcardGet(const char *args)
Serial command printing a stored vCard.
static void ctxReceivedAdd()
Context-menu action: create a new stored contact via the wizard.
static void onMainMenuSelect(uint16_t index, void *userData)
Handles main-menu actions for local vCard operations and sharing.
static void rebuildMainMenu()
Rebuilds the vCard main menu.
static int s_vcardSetSlot
static void ctxReceivedDelete()
Context-menu action: confirm and delete the active contact.
MainMenuItem
Main-menu item identifiers.
static void onMyVcardLockscreenSelect()
Lock-screen quick action: shows the own vCard as a QR code. Falls back to a toast when no vCard has b...
static ui::ListItem s_recvItems[VCARD_MAX_CARDS]
static ui::ListView s_mainMenu
View instances used by vCard module UI flow.
static void cmdVcard(const char *args)
static esp_timer_handle_t s_vcardIdleTimer
static void registerSerialCommands()
Registers serial commands exposed by vCard module.
static char s_vcardBuf[VCARD_MAX_LEN+64]
static void onReceivedMenu(uint16_t index, void *userData)
List context menu (key 3): add a contact; for a selected entry also edit / forward / delete it.
static void vcardPrintLines(char *out)
Prints a NUL-terminated vCard buffer line by line as CRLF output.
static bool importReceivedVcard(const cJSON *je, void *user)
Imports one received vCard string into storage.
static ui::ListView s_receivedMenu
static void showVcardQr(const char *raw, const char *fallbackTitle)
Shows a vCard as a QR code, titled with the contact name.
static void registerStrings()
static void openReceivedList()
Lazily wires callbacks, rebuilds and pushes the received-contacts list.
static bool vcardLineInterceptor(const char *line)
Intercepts multiline vCard paste input, accumulates lines, and stops at "---".
static void ctxReceivedEdit()
Context-menu action: edit the active stored contact via the wizard.
static uint16_t s_recvCount
static void cmdVcardDelete(const char *args)
Serial command deleting a stored vCard.
constexpr ui::I18nEntry kStrings[]
static const char *const s_wizardStepKeys[16]
static const serial::SubCommand kVcardSubs[]
static uint16_t s_recvSlots[VCARD_MAX_CARDS]
static bool s_receivedInitialized
static const char * getMyVcardLockscreenLabel()
Returns the localized label for the lock-screen quick action.
static void cmdVcardSet(const char *args)
Serial command entering multiline vCard paste mode.
static void vcard_arm_idle_timer()
static ui::ListItem s_mainMenuItems[MENU_COUNT]
static void showVcardDetails(const char *title, const char *raw, bool withActions)
Renders a vCard's parsed fields into a scrollable InfoView.
static bool s_vcardInputMode
static bool s_viewsInitialized
static void cmdVcardList(const char *args)
Serial command listing received vCards as "<id> <display name>".
static void onReceivedViewMenu(void *userData)
Detail-view context menu (key 3): edit / forward / QR / delete the currently shown contact.
static uint16_t s_activeSlot
static void ctxReceivedForward()
Context-menu action: forward the active contact to a nearby badge.
static void vcard_idle_fired(void *)
static char s_recvLabels[VCARD_MAX_CARDS][64]
static void rebuildReceivedList()
Rebuilds the received-contacts list from the store (sorted by name).
ICommandRegistry & getCommandRegistry()
Returns singleton command-registry interface.
void dispatchSubCommand(const char *parent, const char *args, const SubCommand *table)
Routes a sub-command line to its handler.
const char * tr(const char *key)
Look up a translation by string key.
cdc::core::IModule::BackupResult importJsonArray(const cJSON *array, BackupEntryHandler handler, void *user)
Iterates a JSON backup array best-effort and tallies the outcome.
void showConfirm(const char *message, ConfirmView::ConfirmCallback onConfirm, ConfirmView::CancelCallback onCancel=nullptr, ConfirmView::Icon icon=ConfirmView::Icon::QUESTION, void *userData=nullptr)
Shows a shared modal confirmation dialog instance.
ContextMenuView * showContextMenu(const char *title, const ContextMenuItem *items, uint8_t count)
Shows the shared context menu instance as modal.
void showToastSuccess(const char *message, uint16_t durationMs=1500)
Shows a success toast message.
QRCodeView * showQRCode(const char *data, const char *title=nullptr, const char *subtitle=nullptr, const char *hint=nullptr)
Shows a shared QR code view instance.
void showToastInfo(const char *message, uint16_t durationMs=1500)
Shows an informational toast message.
void showToastError(const char *message, uint16_t durationMs=1500)
Shows an error toast message.
Per-module restore outcome reported by importBackup().
uint16_t failed
Records skipped due to errors.
uint16_t imported
Records restored successfully.
Lock screen context menu item registered by a module.
Single English translation entry.
Structured representation of an own vCard for editor/wizard use.
size_t vcard_store_get_own(char *out, size_t max_len)
Retrieves local own-vCard text.
bool vcard_store_has_own(void)
Returns whether local own-vCard exists.
bool vcard_store_set_own(const char *vcard, size_t len, char *err, size_t err_len)
Stores local own-vCard after validation and field filtering.
bool vcard_store_update(uint16_t slot, const char *vcard, size_t len, char *err, size_t err_len)
Overwrites the vCard stored at slot in place after validation.
size_t vcard_store_get(uint16_t slot, char *out, size_t max_len)
Retrieves raw vCard text from slot.
bool vcard_store_clear_own(void)
Deletes local own-vCard from storage.
bool vcard_parse_to_struct(const char *raw, vcard_data_t *out)
Parses vCard 4.0 raw text into a structured vcard_data_t.
bool vcard_store_add(const char *vcard, size_t len, char *err, size_t err_len)
Adds peer vCard to first free slot after validation and duplicate check.
uint16_t vcard_store_get_sorted(uint16_t *out_slots, uint16_t max_slots)
Returns slot indices of stored cards sorted by last name.
bool vcard_store_contains(const char *vcard, size_t len)
Reports whether an exact-text vCard is already stored.
bool vcard_store_get_display(uint16_t slot, char *out, size_t max_len)
Retrieves cached display label for slot.
bool vcard_store_delete(uint16_t slot)
Deletes peer vCard at slot index.