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.saved",
"Saved"},
54 {
"mod_vcard.given_name",
"First name"},
55 {
"mod_vcard.family_name",
"Last name"},
56 {
"mod_vcard.formatted_name",
"Display name"},
57 {
"mod_vcard.organization",
"Organization"},
58 {
"mod_vcard.position",
"Position"},
59 {
"mod_vcard.email",
"Email"},
60 {
"mod_vcard.tel_cell",
"Phone (Mobile)"},
61 {
"mod_vcard.tel_home",
"Phone (Home)"},
62 {
"mod_vcard.tel_work",
"Phone (Work)"},
63 {
"mod_vcard.url",
"Website"},
64 {
"mod_vcard.telegram",
"Telegram"},
65 {
"mod_vcard.signal",
"Signal"},
66 {
"mod_vcard.matrix",
"Matrix"},
67 {
"mod_vcard.threema",
"Threema"},
68 {
"mod_vcard.social_profile",
"Social Profile"},
69 {
"mod_vcard.note",
"Note"},
70 {
"mod_vcard.send",
"Send vCard"},
71 {
"mod_vcard.received",
"Contact (vCard)"},
72 {
"mod_vcard.received_title",
"Received vCards"},
73 {
"mod_vcard.no_received",
"No received vCards"},
74 {
"mod_vcard.show_qr",
"Show QR"},
75 {
"mod_vcard.forward",
"Forward"},
76 {
"mod_vcard.confirm_delete",
"Delete this contact?"},
130static const char*
mstr(uint16_t offset) {
131 if (offset >= std::size(
kStrings))
return "?";
171static void showVcardDetails(
const char* title,
const char* raw,
bool withActions);
172static void showVcardQr(
const char* raw,
const char* fallbackTitle);
183static bool deliverVcard(
const uint8_t* data, uint32_t len,
const char* ,
187 memcpy(buf, data, len);
191 LOG_W(
TAG,
"Failed to store received vCard: %s", err);
211 memset(&s_parsed, 0,
sizeof(s_parsed));
215 const int cap =
static_cast<int>(
sizeof(s_text));
216 auto append = [&](
const char* fmt,
const char* a,
const char* b) {
217 if (n >= cap - 1)
return;
218 int w = snprintf(s_text + n,
static_cast<size_t>(cap - n), fmt, a, b);
220 if (n > cap - 1) n = cap - 1;
223 if (s_parsed.formatted_name[0]) {
224 append(
"%s%s\n\n", s_parsed.formatted_name,
"");
225 }
else if (s_parsed.given_name[0] || s_parsed.family_name[0]) {
226 append(
"%s %s\n\n", s_parsed.given_name, s_parsed.family_name);
229 auto field = [&](uint16_t label,
const char* value) {
230 if (value[0]) append(
"%s: %s\n",
mstr(label), value);
246 if (n == 0) snprintf(s_text,
sizeof(s_text),
"%s", raw);
249 s_detailView.
init(title, s_text);
259static void showVcardQr(
const char* raw,
const char* fallbackTitle) {
261 static char s_qrTitle[96];
262 static char s_qrSubtitle[96];
264 memset(&s_parsed, 0,
sizeof(s_parsed));
267 if (s_parsed.formatted_name[0]) {
268 snprintf(s_qrTitle,
sizeof(s_qrTitle),
"%s", s_parsed.formatted_name);
269 }
else if (s_parsed.given_name[0] || s_parsed.family_name[0]) {
270 snprintf(s_qrTitle,
sizeof(s_qrTitle),
"%s %s",
271 s_parsed.given_name, s_parsed.family_name);
273 snprintf(s_qrTitle,
sizeof(s_qrTitle),
"%s", fallbackTitle);
276 const char* sub = s_parsed.organization[0] ? s_parsed.organization
277 : s_parsed.title[0] ? s_parsed.title
278 : s_parsed.email[0] ? s_parsed.email
280 snprintf(s_qrSubtitle,
sizeof(s_qrSubtitle),
"%s", sub);
282 ui::showQRCode(raw, s_qrTitle, s_qrSubtitle[0] ? s_qrSubtitle :
nullptr);
333 cdc::msg::MessageTransfer::instance().beginInteractiveSend(
334 "text/vcard",
reinterpret_cast<const uint8_t*
>(own),
335 static_cast<uint32_t
>(len));
360 reinterpret_cast<void*
>(
static_cast<uintptr_t
>(slot))};
373 s_activeSlot =
static_cast<uint16_t
>(
reinterpret_cast<uintptr_t
>(userData));
400 if (len == 0)
return;
401 cdc::msg::MessageTransfer::instance().beginInteractiveSend(
402 "text/vcard",
reinterpret_cast<const uint8_t*
>(raw),
403 static_cast<uint32_t
>(len));
411 uint16_t slot = *
static_cast<uint16_t*
>(userData);
455 s_activeSlot =
static_cast<uint16_t
>(
reinterpret_cast<uintptr_t
>(userData));
533 esp_timer_create_args_t args = {
536 .dispatch_method = ESP_TIMER_TASK,
537 .name =
"vcard_idle",
538 .skip_unhandled_events =
true,
557 if (strncmp(line,
"---", 3) == 0) {
566 Console::printf(
"OK: vCard updated\r\n");
568 Console::printf(
"ERROR: %s\r\n", err[0] ? err :
"Invalid vCard");
572 Console::showPrompt();
576 if (strcmp(line,
"ABORT") == 0 || strcmp(line,
"VCARD_ABORT") == 0) {
577 Console::printf(
"ABORTED\r\n");
579 Console::showPrompt();
583 size_t lineLen = strlen(line);
589 Console::printf(
"ERROR: vCard too large\r\n");
591 Console::showPrompt();
608 int slot = atoi(args);
612 Console::printf(
"ERROR: no vCard at id %d\r\n", slot);
618 Console::printf(
"Paste vCard 4.0, end with '---' on a new line "
619 "(or 'ABORT' to cancel):\r\n");
634 while ((next = strchr(line,
'\n')) !=
nullptr) {
636 Console::printf(
"%s\r\n", line);
640 Console::printf(
"%s\r\n", line);
657 int slot = atoi(args);
660 Console::printf(
"ERROR: no vCard at id %d\r\n", slot);
669 Console::printf(
"BEGIN:VCARD\r\n");
670 Console::printf(
"VERSION:4.0\r\n");
671 Console::printf(
"N:;;\r\n");
672 Console::printf(
"FN:\r\n");
673 Console::printf(
"NOTE:\r\n");
674 Console::printf(
"TEL;TYPE=HOME:\r\n");
675 Console::printf(
"TEL;TYPE=CELL:\r\n");
676 Console::printf(
"TEL;TYPE=WORK:\r\n");
677 Console::printf(
"EMAIL:\r\n");
678 Console::printf(
"URL:\r\n");
679 Console::printf(
"ORG:\r\n");
680 Console::printf(
"TITLE:\r\n");
681 Console::printf(
"X-SOCIALPROFILE:\r\n");
682 Console::printf(
"IMPP:telegram:\r\n");
683 Console::printf(
"IMPP:signal:\r\n");
684 Console::printf(
"IMPP:matrix:\r\n");
685 Console::printf(
"IMPP:threema:\r\n");
686 Console::printf(
"END:VCARD\r\n");
704 Console::printf(
"No received vCards\r\n");
709 for (uint16_t i = 0; i < count; i++) {
711 Console::printf(
"%2u %s\r\n", slots[i],
name);
726 int slot = atoi(args);
729 Console::printf(
"ERROR: no vCard at id %d\r\n", slot);
732 Console::printf(
"OK: vCard %d deleted\r\n", slot);
737 Console::printf(
"OK: vCard deleted\r\n");
739 Console::printf(
"ERROR: Failed to delete vCard\r\n");
744 {
"SET",
"[id]",
"Set own vCard, or overwrite received vCard <id> (multiline paste, end with '---' or 'ABORT')",
cmdVcardSet},
745 {
"GET",
"[id]",
"Show own vCard, or received vCard <id>",
cmdVcardGet},
747 {
"DELETE",
"[id]",
"Delete own vCard, or received vCard <id>",
cmdVcardDelete},
748 {
nullptr,
nullptr,
nullptr,
nullptr},
760 reg.registerCommand({
"VCARD",
761 "vCard storage: SET/GET/LIST/DELETE",
783 LOG_I(
TAG,
"Initializing vCard module");
790 cdc::msg::MessageTransfer::instance().registerHandler(
814 cdc::msg::MessageTransfer::instance().unregisterHandler(
"text/vcard");
825 if (!items || maxItems == 0)
return 0;
854 if (!items || maxItems == 0)
return 0;
885 if (!out)
return false;
887 cJSON_AddNumberToObject(out,
"schema_ver",
kSchemaVer);
893 cJSON_AddStringToObject(out,
"own", buf);
897 cJSON* received = cJSON_AddArrayToObject(out,
"received");
898 if (!received)
return any;
902 for (uint16_t i = 0; i < count; i++) {
904 cJSON* item = cJSON_CreateString(buf);
906 cJSON_AddItemToArray(received, item);
926 if (!cJSON_IsString(je) || !je->valuestring || je->valuestring[0] ==
'\0')
return false;
928 const char* text = je->valuestring;
929 size_t len = strlen(text);
950 const cJSON* schemaVer = cJSON_GetObjectItemCaseSensitive(in,
"schema_ver");
951 if (cJSON_IsNumber(schemaVer) &&
static_cast<int>(schemaVer->valuedouble) !=
kSchemaVer) {
952 LOG_W(
TAG,
"vCard backup schema_ver %d != expected %d, skipping",
953 static_cast<int>(schemaVer->valuedouble),
kSchemaVer);
959 const cJSON* own = cJSON_GetObjectItemCaseSensitive(in,
"own");
960 if (cJSON_IsString(own) && own->valuestring && own->valuestring[0] !=
'\0') {
965 LOG_W(
TAG,
"vCard import: own vCard rejected (%s)", err);
970 const cJSON* received = cJSON_GetObjectItemCaseSensitive(in,
"received");
985 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(StringResolver resolver, const uint16_t *titleOffsets, uint16_t savedOffset, uint16_t failedOffset)
Configures the wizard with i18n callbacks. Must be called before start() or edit() so step titles can...
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 uint16_t STR_SHOW_QR
static constexpr uint16_t STR_MY_VCARD
static constexpr uint16_t STR_NO_RECEIVED
static constexpr uint16_t STR_DECLINE
static constexpr uint16_t STR_SOCIAL_PROFILE
static constexpr uint16_t STR_NO_VCARD
static constexpr uint16_t STR_URL
static constexpr int kSchemaVer
Schema version written to and expected from the vCard backup section.
static const char * mstr(uint16_t offset)
static constexpr uint16_t STR_SCANNING
static constexpr uint16_t STR_SAVED
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 constexpr uint16_t STR_SIGNAL
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 uint16_t STR_NOTE
static constexpr uint16_t STR_STOP_ADV
static constexpr int64_t VCARD_IDLE_LIMIT_US
static constexpr uint16_t STR_VCARD
static void cmdVcardGet(const char *args)
Serial command printing a stored vCard.
static constexpr uint16_t STR_THREEMA
static constexpr uint16_t STR_EXCHANGE_FAIL
static constexpr uint16_t STR_FORWARD
static void ctxReceivedAdd()
Context-menu action: create a new stored contact via the wizard.
static constexpr uint16_t STR_TEL_HOME
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 constexpr uint16_t STR_TEL_WORK
static constexpr uint16_t STR_ACCEPT
static int s_vcardSetSlot
static void ctxReceivedDelete()
Context-menu action: confirm and delete the active contact.
static constexpr uint16_t STR_CONFIRM_DELETE
static constexpr uint16_t STR_EMAIL
static constexpr uint16_t STR_NO_PEERS
MainMenuItem
Main-menu item identifiers.
static constexpr uint16_t STR_POSITION
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 constexpr uint16_t STR_TEL_CELL
static ui::ListView s_mainMenu
View instances used by vCard module UI flow.
static void cmdVcard(const char *args)
static constexpr uint16_t STR_ORGANIZATION
static esp_timer_handle_t s_vcardIdleTimer
static constexpr uint16_t STR_EDIT_MY_VCARD
static constexpr uint16_t STR_RECEIVED
static void registerSerialCommands()
Registers serial commands exposed by vCard module.
static constexpr uint16_t STR_EXCHANGE_REQ
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 constexpr uint16_t STR_ADVERTISING
static constexpr uint16_t STR_CONNECTING
static bool importReceivedVcard(const cJSON *je, void *user)
Imports one received vCard string into storage.
static constexpr uint16_t STR_EXCHANGE
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 constexpr uint16_t STR_SCAN
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 serial::SubCommand kVcardSubs[]
static uint16_t s_recvSlots[VCARD_MAX_CARDS]
static bool s_receivedInitialized
static const uint16_t s_wizardStepOffsets[16]
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 constexpr uint16_t STR_NEARBY
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 constexpr uint16_t STR_TELEGRAM
static constexpr uint16_t STR_GIVEN_NAME
static bool s_vcardInputMode
static constexpr uint16_t STR_MATRIX
static bool s_viewsInitialized
static constexpr uint16_t STR_FAMILY_NAME
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 constexpr uint16_t STR_STOP_SCAN
static constexpr uint16_t STR_EXCHANGE_OK
static uint16_t s_activeSlot
static constexpr uint16_t STR_RECEIVED_TITLE
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 constexpr uint16_t STR_SEND
static void rebuildReceivedList()
Rebuilds the received-contacts list from the store (sorted by name).
static constexpr uint16_t STR_FORMATTED_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.