From 3061597f59b0b300b6e4933a6dc3d50b764f8869 Mon Sep 17 00:00:00 2001 From: "Ticklemonster Dad (aka Matt)" Date: Sun, 25 Feb 2024 14:10:50 +0800 Subject: [PATCH 1/2] First build of PBAP phone book features for BM83. Includes phone dialer menu from feature/phone branch - but does not include directory menus. Phone book can be tested using CLI only ... "BT PB" (phone book), "BT CH" (call history) and "BT FAV" (favorites) --- firmware/application/lib/bt.c | 60 ++ firmware/application/lib/bt.h | 4 + firmware/application/lib/bt/bt_bm83.c | 7 + firmware/application/lib/bt/bt_bm83.h | 6 + firmware/application/lib/bt/bt_bm83_pbap.c | 732 +++++++++++++++++++++ firmware/application/lib/bt/bt_bm83_pbap.h | 77 +++ firmware/application/lib/locale.c | 2 + firmware/application/lib/locale.h | 4 +- firmware/application/ui/bmbt.c | 213 +++++- firmware/application/ui/bmbt.h | 6 +- 10 files changed, 1108 insertions(+), 3 deletions(-) create mode 100644 firmware/application/lib/bt/bt_bm83_pbap.c create mode 100644 firmware/application/lib/bt/bt_bm83_pbap.h diff --git a/firmware/application/lib/bt.c b/firmware/application/lib/bt.c index c27c33f9..28cc0219 100644 --- a/firmware/application/lib/bt.c +++ b/firmware/application/lib/bt.c @@ -3,9 +3,12 @@ * Author: Ted Salmon * Description: * Implementation of the abstract Bluetooth Module API + * History: + * Feb 2024 (Matt C) - Added phone book access (currently for BM83 only) */ #include "bt.h" #include "locale.h" +#include "bt/bt_bm83_pbap.h" /** * BTInit() @@ -459,6 +462,63 @@ void BTCommandToggleVoiceRecognition(BT_t *bt) } } +/** + * BTGetCommandReqPhonebookFrom + * @param bt - pointer to the bluetooth module + * @param offset - index to start + * + * Requests a phone book entries starting from the provided offset + */ +void BTCommandReqPhonebookFrom(BT_t *bt, uint8_t offset) { + if (bt->type == BT_BTM_TYPE_BM83) { + // TODO: Manage paging in the menu + BM83CommandPBAPPullPhoneBook(bt, PBAP_TYPE_PHONEBOOK, offset); + } else { + LogDebug(LOG_SOURCE_BT, "Get Phonebook - not implemented for BC127"); + } +} + +/** + * BTCommandReqPhonebook() + * @param bt - pointer to the module object + * + * Requests the first entries from the phone book. + * A convenience function for BTCommandReqPhonebookFrom with 0 offset + */ +void BTCommandReqPhonebook(BT_t *bt) +{ + BTCommandReqPhonebookFrom(bt, 0); +} +/** + * BTCommandReqFavorites() + * @param bt - A pointer to the bluetooth module object + * + * Requests the "Top-8" from phone favorites. + */ +void BTCommandReqFavorites(BT_t *bt) +{ + if (bt->type == BT_BTM_TYPE_BM83) { + // TODO: Manage paging in the menu + BM83CommandPBAPPullPhoneBook(bt, PBAP_TYPE_FAVORITE_CONTACTS, 0); + } else { + LogDebug(LOG_SOURCE_BT, "Get Favorites - not implemented for BC127"); + } +} +/** + * BTCommandReqRecent() + * @param bt - A pointer to the bluetooth module object + * + * Requests the combined call history from phone + */ +void BTCommandReqRecent(BT_t *bt) +{ + if (bt->type == BT_BTM_TYPE_BM83) { + BM83CommandPBAPPullPhoneBook(bt, PBAP_TYPE_COMBINED_CALL_HISTORY, 0); + } else { + LogDebug(LOG_SOURCE_BT, "Get Call History - not implemented for BC127"); + } +} + /** * BTHasActiveMacId() * Description: diff --git a/firmware/application/lib/bt.h b/firmware/application/lib/bt.h index 2af2c56b..149b40b8 100644 --- a/firmware/application/lib/bt.h +++ b/firmware/application/lib/bt.h @@ -36,4 +36,8 @@ void BTCommandSetDiscoverable(BT_t *, unsigned char); void BTCommandToggleVoiceRecognition(BT_t *); uint8_t BTHasActiveMacId(BT_t *); void BTProcess(BT_t *); +void BTCommandReqPhonebookFrom(BT_t *, uint8_t); +void BTCommandReqPhonebook(BT_t *); +void BTCommandReqFavorites(BT_t *); +void BTCommandReqRecent(BT_t *); #endif /* BT_H */ diff --git a/firmware/application/lib/bt/bt_bm83.c b/firmware/application/lib/bt/bt_bm83.c index 93e66454..ff967341 100644 --- a/firmware/application/lib/bt/bt_bm83.c +++ b/firmware/application/lib/bt/bt_bm83.c @@ -3,8 +3,11 @@ * Author: Ted Salmon * Description: * Implementation of the Microchip BM83 Bluetooth UART API + * Revision History: + * Feb 2024 (Matt C) - Forward PBAP events to handler in bt_phonebook.c */ #include "bt_bm83.h" +#include "bt_bm83_pbap.h" #include "../locale.h" int8_t BTBM83MicGainTable[] = { @@ -1331,6 +1334,10 @@ void BM83Process(BT_t *bt) if (event == BM83_EVT_REPORT_TYPE_CODEC) { BM83ProcessEventReportTypeCodec(bt, eventData, dataLength); } + /* Phone Book events */ + if (event == BM83_EVT_PBAPC_EVENT) { + BM83ProcessEventPhoneBook(bt, eventData, dataLength); + } } } UARTReportErrors(&bt->uart); diff --git a/firmware/application/lib/bt/bt_bm83.h b/firmware/application/lib/bt/bt_bm83.h index a4ccfe75..229dbe8c 100644 --- a/firmware/application/lib/bt/bt_bm83.h +++ b/firmware/application/lib/bt/bt_bm83.h @@ -354,4 +354,10 @@ void BM83ProcessDataGetAllAttributes(BT_t *, uint8_t *, uint8_t, uint16_t); void BM83Process(BT_t *); void BM83SendCommand(BT_t *, uint8_t *, size_t); +/* Phone book commands */ +void BM83CommandPBAPOpenSession(BT_t *); +void BM83CommandPBAPCloseSession(BT_t *); +void BM83CommandPullVcardListingReq(BT_t *, uint8_t); +void BM83CommandPullVcardEntryReq(BT_t *, char*); + #endif /* BM83_H */ diff --git a/firmware/application/lib/bt/bt_bm83_pbap.c b/firmware/application/lib/bt/bt_bm83_pbap.c new file mode 100644 index 00000000..c9bb7abd --- /dev/null +++ b/firmware/application/lib/bt/bt_bm83_pbap.c @@ -0,0 +1,732 @@ +/* + * File: bt_bm83_pbap.c + * Author: Matt C + * Description: + * Implementation of phone book access for the Microchip BM83 Bluetooth UART API + * Attempts to enable the existing phone functions including: + * - Directory (access one at a time for IKE/MID, or 8 at a time for BM) + * - Top-8 (favorites) + * - Last Numbers (call history) + * + * Assumes that there is not enough memory available to download the entire + * phone book on connection (TODO - test this assumption?) + * + * Data storage uses a fixed allocation of PHONE_BOOK_MAX_ENTRIES (8) + * each with up to PHONE_BOOK_MAX_NUMBERS (3) - to match the BMBT display. + * + * Not yet attempted: + * - SMS messages + * - Adding menus + * - Updating dial buffer with selected contact (bt->dialBuffer)? + * + * Revision History: + * Feb 2024 (Matt C) - Initial build + */ + +#include +#include "../utils.h" +#include "./bt_bm83.h" +#include "./bt_bm83_pbap.h" +#include "../event.h" + +// Simple state machine +enum Phonebook_Status { + Disconnected, + Connected, + Waiting +}; +static enum Phonebook_Status _pb_status = Disconnected; +static struct { + uint8_t isValid; + uint8_t type; + uint16_t offset; +} _pb_onConnect; + +// Phonebook message buffer (to assemble multi-part messages) +static uint8_t _pb_buffer[PHONE_BOOK_MAX_BUFFER]; +static uint16_t _pb_buffer_offset = 0; + +// TO DO: Strings that should be localised... +const char* DEFAULT_TEL_TYPE_NAME = "TEL"; + +static struct PHONEBOOK_ENTRY_TYPE _phonebook[PHONE_BOOK_MAX_ENTRIES]; + + +// +// Event packet header structures +// + +// common header shapes... +struct PBAPC_HEADER_ID { + uint8_t device_id; +}; +struct PBAPC_HEADER_ID_STATUS { + uint8_t device_id; + uint8_t status; +}; +// header for receiving phone book or vCard lists +struct PBAPC_HEADER_PHONEBOOK { + uint8_t device_id; + uint8_t is_end_of_body; + uint16_t flags; + uint16_t phone_book_size; + uint8_t new_missed_calls; + uint8_t primary_version_counter[16]; + uint8_t secondary_version_counter[16]; + uint8_t database_id[16]; +}; +// header for a single vCard +struct PBAPC_HEADER_VCARD { + uint8_t device_id; + uint8_t is_end_of_body; + uint16_t flags; + uint8_t database_id[16]; +}; +// header for a supported features event +struct PBAPC_HEADER_FEATURES { + uint8_t device_id; + uint8_t supported_repos; + uint8_t supported_features[4]; + uint8_t profile_version_major; + uint8_t profile_version_minor; +}; +// the combined event header +struct PBAPC_EVENT_HEADER { + uint8_t event_code; + union { + struct PBAPC_HEADER_ID event_idOnly; + struct PBAPC_HEADER_ID_STATUS event_idStatus; + struct PBAPC_HEADER_PHONEBOOK event_phonebook; + struct PBAPC_HEADER_VCARD event_vcard; + struct PBAPC_HEADER_FEATURES event_features; + }; +}; + + +/** + * debugEntries + * - used to output the phone book to the debug console + */ +static void debugEntries() { + LogDebug(LOG_SOURCE_BT, "Phone Book Entries"); + for (int i = 0; i < PHONE_BOOK_MAX_ENTRIES; i++) { + LogRawDebug(LOG_SOURCE_BT, "[%d] %s", i, _phonebook[i].name ? _phonebook[i].name : "(null)"); + for (int j = 0; j < PHONE_BOOK_MAX_NUMBERS; j++) { + if (_phonebook[i].phone[j].number[0] != 0) { + LogRawDebug(LOG_SOURCE_BT, ", %s:%s", _phonebook[i].phone[j].type, _phonebook[i].phone[j].number); + } + } + LogRawDebug(LOG_SOURCE_BT, "\r\n"); + } +} + +/** + * parseVCards + * a very simple parser to read a few key fields from the returned vCards + * Expects the buffer (_pb_buffer) to be a UTF-8 string that meets vCard spec. + */ +void parseVCards() +{ + LogDebug(LOG_SOURCE_BT, "Parsing vCard Buffer..."); +// LogDebug(LOG_SOURCE_BT, "%s", _pb_buffer); + + // break the buffer into lines + char *lptr; + char *l = strtok_r((char *)_pb_buffer, "\r\n", &lptr); + uint8_t idx = 0; + uint8_t jdx = 0; + + struct PHONEBOOK_ENTRY_TYPE entry; + + while(l != NULL) { + char *tptr; + char *prop = strtok_r(l, ":", &tptr); + char *prop0 = strtok(prop, ";"); + char *prop1 = strtok(NULL, ";"); + char *value = strtok_r(NULL, ":", &tptr); + + if ( UtilsStricmp(prop, "BEGIN") == 0 && + UtilsStricmp(value, "VCARD") == 0 ) { + // BEGIN:VCARD + // clear the old entry at this index... + memset(&entry, 0, sizeof(struct PHONEBOOK_ENTRY_TYPE)); + jdx = 0; + } + else if (UtilsStricmp(prop, "END") == 0 + && UtilsStricmp(value, "VCARD") == 0) + { + // save it only if there is at least a name and a number + if (strlen(entry.name) > 0 && strlen(entry.phone[0].number) > 0) { + memcpy(&(_phonebook[idx]), &entry, sizeof(struct PHONEBOOK_ENTRY_TYPE)); + idx++; + } else { + LogDebug(LOG_SOURCE_BT, "- Missing data name=%s phone0=%s", entry.name, entry.phone[0].number); + } + if (idx > PHONE_BOOK_MAX_ENTRIES) { + LogWarning("Too many phone book entries (%d > %d)", idx, PHONE_BOOK_MAX_ENTRIES); + break; + } + } +// else if (UtilsStricmp(prop, "VERSION") == 0) { +// LogDebug(LOG_SOURCE_BT, " VERSION: %s", value); +// } + else if (UtilsStricmp(prop, "FN") == 0) { + // FN: Formatted Name - use this one + strncpy(entry.name, value, BT_CALLER_ID_FIELD_SIZE - 1); + } + else if (UtilsStricmp(prop0, "X-IRMC-CALL-DATETIME") == 0) { + LogDebug(LOG_SOURCE_BT, "Call %s at: %s", prop1, value); + } + else if (UtilsStricmp(prop0, "TEL") == 0) { + if (prop1) { + char *eq = strstr(prop1, "="); + if (eq) prop1 = (eq + 1); + } + + if (jdx < PHONE_BOOK_MAX_ENTRIES) { + if (value) strncpy(entry.phone[jdx].number, value, BT_CALLER_ID_FIELD_SIZE - 1); + if (prop1) { + strncpy(entry.phone[jdx].type, prop1, BT_CALLER_ID_FIELD_SIZE - 1); + } else { + strncpy(entry.phone[jdx].type, DEFAULT_TEL_TYPE_NAME, BT_CALLER_ID_FIELD_SIZE - 1); + } + jdx++; + } else { + LogWarning("Too many phone numbers (> %d)", PHONE_BOOK_MAX_NUMBERS ); + } + } + + l = strtok_r(NULL, "\r\n", &lptr); + } + + // DEBUG + debugEntries(); + + // TODO - Send an event for anyone who wants to know? + // EventTriggerCallback(BT_EVENT_PHONEBOOK_UPDATE) +} + + +/** + * BM83CommandPBAPOpenSession + * @param bt - the Bluetooth module + * + * Requests to open a phone book session (if not already open) + */ +void BM83CommandPBAPOpenSession(BT_t *bt) +{ + if (_pb_status != Disconnected) return; + + uint8_t command[] = { + BM83_CMD_PBAPC_CMD, + BM83_PBAP_OP_OPEN_SESSION, + bt->activeDevice.deviceId & 0xF // Linked Database, the lower nibble + }; + BM83SendCommand(bt, command, sizeof(command)); +} + +/** + * BM83CommandPBAPCloseSession + * @param bt - the Bluetooth module + * + * Requests to close the phone book session (if open) + */ +void BM83CommandPBAPCloseSession(BT_t *bt) +{ + if (_pb_status == Disconnected) return; + + uint8_t command[] = { + BM83_CMD_PBAPC_CMD, + BM83_PBAP_OP_CLOSE_SESSION, + bt->activeDevice.deviceId & 0xF // Linked Database, the lower nibble + }; + BM83SendCommand(bt, command, sizeof(command)); +} + +/** + * BM83CommandPBAPAbort + * @param bt - the Bluetooth module + * + * sends the abort command (to stop a running phone book operation) + */ +void BM83CommandPBAPAbort(BT_t *bt) +{ + if (_pb_status == Disconnected) return; + + uint8_t command[] = { + BM83_CMD_PBAPC_CMD, + BM83_PBAP_OP_ABORT, + bt->activeDevice.deviceId & 0xF // Linked Database, the lower nibble + }; + BM83SendCommand(bt, command, sizeof(command)); +} + + +// +// Packet header parsing... +// +void parseIdHeader(struct PBAPC_HEADER_ID *header, uint8_t *data) +{ + header->device_id = *(data); +} +void parseIdStatusHeader(struct PBAPC_HEADER_ID_STATUS *header, uint8_t *data) +{ + header->device_id = data[0]; + header->status = data[1]; +} +void parsePhonebookHeader(struct PBAPC_HEADER_PHONEBOOK *header, uint8_t *data) +{ + header->device_id = data[0]; + header->is_end_of_body = data[1]; + header->flags = (data[2] << 8) + data[3]; + if (header->flags & FLAGS_PB_SIZE) { + header->phone_book_size = (data[4] << 8) + data[5]; + } else { + header->phone_book_size = 0; + } + header->new_missed_calls = data[6]; + if (header->flags & FLAGS_PB_PRIMARY_VER) { + memcpy(header->primary_version_counter, data + 7, 16); + } else { + memset(header->primary_version_counter, 0, 16); + } + if (header->flags & FLAGS_PB_SECONDARY_VER) { + memcpy(header->secondary_version_counter, data + 23, 16); + } else { + memset(header->secondary_version_counter, 0, 16); + } + if (header->flags & FLAGS_PB_DATABASE_ID) { + memcpy(header->database_id, data + 39, 16); + } else { + memset(header->database_id, 0, 16); + } +} +void parseVCardHeader(struct PBAPC_HEADER_VCARD *header, uint8_t *data) +{ + header->device_id = data[0]; + header->is_end_of_body = data[1]; + header->flags = (data[2] << 8) + data[3]; + if (header->flags & 0x0400) { + memcpy(header->database_id, data + 4, 16); + } else { + memset(header->database_id, 0, 16); + } +} +void parseFeaturesHeader(struct PBAPC_HEADER_FEATURES *header, uint8_t *data) +{ + header->device_id = data[0]; + header->supported_repos = data[1]; + memcpy(header->supported_features, data+2, 4); + header->profile_version_major = data[6]; + header->profile_version_minor = data[7]; +} + + +// +// Buffer management +// +static void clearBuffer() +{ + memset(_pb_buffer, 0, PHONE_BOOK_MAX_BUFFER); + _pb_buffer_offset = 0; +} +static int appendBuffer(uint8_t *data, size_t size) +{ + if (_pb_buffer_offset + size < PHONE_BOOK_MAX_BUFFER) { + memcpy(_pb_buffer + _pb_buffer_offset, data, size); + _pb_buffer_offset += size; + } else { + return -1; + } + + return 0; +} + +/** + * BM83CommandPBAPPullPhoneBook + * @param bt - the bluetooth module + * @param type - the type of phone book (e.g. PBAP_TYPE_PHONEBOOK) + * @param offset - the starting offset + * + * sends a command to fetch the next set of phonebook entries + */ +void BM83CommandPBAPPullPhoneBook(BT_t *bt, uint8_t type, uint16_t offset) +{ + if (_pb_status == Disconnected) { + // add this to an "onConnect" + _pb_onConnect.isValid = 1; + _pb_onConnect.type = type; + _pb_onConnect.offset = offset; + + BM83CommandPBAPOpenSession(bt); + return; + } + if (!_pb_status == Connected) { + LogInfo(LOG_SOURCE_BT, "Attempted to get a phone book without a connection"); + return; + } + + uint8_t command[] = { + BM83_CMD_PBAPC_CMD, + BM83_PBAP_OP_PULL_PHONEBOOK, + bt->activeDevice.deviceId & 0xF, // Linked Database, the lower nibble + 0x00, // repository 0x00 = Phone, 0x01 = SIM + type, // object type (1 byte)) + 0x40, // Flags (2 bytes): b14 = max_list_count + 0x07, // Flags (2 bytes): b0 = properties, b1 = format, b2 = offset + (PHONE_BOOK_MAX_ENTRIES & 0xFF00) >> 8, // max entries (2 bytes)) + (PHONE_BOOK_MAX_ENTRIES & 0xFF), + 0x87, // Property selector (8 bytes) - tel, fn, n, version + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, // Format (1 byte): 0x00 = vCard2.1, 0x01 = vCard3.0 + (offset & 0xFF00) >> 8, // List start offset: (2 bytes)) + (offset & 0xFF), + 0x00, // vCard selector (8 bytes) - not used + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00 // vCard selector operator (1 byte) - not used + }; + + _pb_status = Waiting; + BM83SendCommand(bt, command, sizeof(command)); +} +void BM83CommandPBAPContPhonebook(BT_t *bt) { + uint8_t command[] = { + BM83_CMD_PBAPC_CMD, + BM83_PBAP_OP_PULL_PHONEBOOK, + bt->activeDevice.deviceId & 0xF, // Linked Database, the lower nibble + 0x00, // repository 0x00 = Phone, 0x01 = SIM + 0x00, // object type (1 byte) + 0x00, // Flags (2 bytes): ignore for continue) + 0x00, + 0x00, // Max entries (2 bytes): ignored + 0x00, + 0x00, // Property selector (8 bytes): ignore + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, // Format (1 byte): 0x00 = vCard2.1, 0x01 = vCard3.0 + 0x00, // List start offset (2 bytes): ignore + 0x00, + 0x00, // vCard selector (8 bytes) - not used + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00 // vCard selector operator (1 byte) - not used + }; + BM83SendCommand(bt, command, sizeof(command)); +} + + +/** + * BM83ProcessEventPhoneBook + * @param bt - the bluetooth module + * @param data - message bytes + * @param length - message length + * + * Phonebook events may be fragmented and spread across multiple messages. + * Fragmented messages are assembled into the buffer + * Full messages are processed from the buffer. + * If they are not a "last" message, then strip the headers and keep the data + * Once all vcard messages are received, then process the vCards in the buffer. + * + */ +void BM83ProcessEventPhoneBook(BT_t *bt, uint8_t *data, uint16_t length) +{ + uint8_t event_type = data[0]; + uint16_t total_length = (data[1] << 8) + data[2]; + uint16_t payload_length = (data[3] << 8) + data[4]; + + LogDebug(LOG_SOURCE_BT, "PBAP Packet: Type=%x Length=%d, Payload=%d", + event_type, total_length, payload_length + ); + + static uint16_t payload_start = 0; + static struct PBAPC_EVENT_HEADER event; + + // First fragment - make sure we can fit the full fragment + // and store it if we can + if (event_type == BM83_PBAP_EVENT_TYPE_FRAGMENT_START) { + _pb_status = Waiting; + + if (_pb_buffer_offset + total_length + 1 > PHONE_BOOK_MAX_BUFFER) { + LogWarning("BT PBAP - Not enough memory for packet (%d)", + total_length + ); + BM83CommandPBAPAbort(bt); + } + else if (length < 6 || + (data[5] == BM83_PBAP_EVENT_PULL_PHONEBOOK_RSP && length < 56)) + { + LogWarning("BT PBAP - truncated packet received"); + BM83CommandPBAPAbort(bt); + } + else if (data[5] != BM83_PBAP_EVENT_PULL_PHONEBOOK_RSP) { + LogWarning("BT Phonebook - opcode %x with fragments is not supported", + data[5] + ); + BM83CommandPBAPAbort(bt); + } + else + { + // we are receiving a multi-fragment phonebook response + // this is the first part - save the header ... + event.event_code = data[5]; + parsePhonebookHeader(&(event.event_phonebook), data + 6); + + // ... and start filling the vcard buffer with the data... + if (payload_length > 61) { + payload_start = _pb_buffer_offset; + appendBuffer((data+61),(payload_length-56)); + } + + LogDebug(LOG_SOURCE_BT, + "+ First %d data bytes: %d of %d", + payload_length, + payload_length, + total_length + ); + } + return; + } + + if (event_type == BM83_PBAP_EVENT_TYPE_FRAGMENT_CONTINUE) { + if (payload_start + total_length + 1 > PHONE_BOOK_MAX_BUFFER) { + LogDebug(LOG_SOURCE_BT, " Skipped %d bytes...", payload_length); + } else { + appendBuffer((data + 5), payload_length); + LogDebug(LOG_SOURCE_BT, "+ Added %d bytes to buffer: %d of %d", + payload_length, _pb_buffer_offset - payload_start + 56, total_length); + } + return; + } + + if (event_type == BM83_PBAP_EVENT_TYPE_FRAGMENT_END) { + if (payload_start + total_length + 1 > PHONE_BOOK_MAX_BUFFER) { + LogDebug(LOG_SOURCE_BT, " Skipped %d bytes...", payload_length); + + // the fragment is finished - but we couldn't buffer it + // reset for the next try... + clearBuffer(); + return; + } + else + { + appendBuffer((data+5), payload_length); + _pb_buffer[_pb_buffer_offset] = 0; + LogDebug(LOG_SOURCE_BT, "+ Final %d bytes to buffer: %d of %d", + payload_length, _pb_buffer_offset - payload_start + 56, total_length); + } + } + + if (event_type == BM83_PBAP_EVENT_TYPE_SINGLE) { + _pb_status = Connected; + + event.event_code = data[5]; + switch (event.event_code) { + case BM83_PBAP_EVENT_CONNECTED: + case BM83_PBAP_EVENT_ERROR_RSP: + parseIdStatusHeader(&(event.event_idStatus), data + 6); + break; + case BM83_PBAP_EVENT_DISCONNECTED: + case BM83_PBAP_EVENT_SET_PHONEBOOK_RSP: + case BM83_PBAP_EVENT_ABORT_RSP: + parseIdHeader(&(event.event_idOnly), data + 6); + break; + case BM83_PBAP_EVENT_PULL_PHONEBOOK_RSP: + case BM83_PBAP_EVENT_PULL_VCARD_LIST_RSP: + parsePhonebookHeader(&(event.event_phonebook), data + 6); + // copy the data body into the buffer + payload_start = _pb_buffer_offset; + if (appendBuffer(data + 61, payload_length - 56) < 0) { + LogDebug(LOG_SOURCE_BT, " Not enough memory. Skipping packet"); + // dump the buffer and start again... + clearBuffer(); + payload_start = 0; + return; + } + break; + case BM83_PBAP_EVENT_PULL_VCARD_ENTRY_RSP: + parseVCardHeader(&(event.event_vcard), data + 6); + // copy the data body into the buffer + payload_start = _pb_buffer_offset; + if (appendBuffer(data + 19, payload_length - 19) < 0) { + LogDebug(LOG_SOURCE_BT, " Not enough memory. Skipping packet"); + // dump the buffer and start again... + clearBuffer(); + payload_start = 0; + return; + } + break; + case BM83_PBAP_EVENT_SUPPORTED_FEATURES: + parseFeaturesHeader(&(event.event_features), data + 6); + break; + default: + LogWarning("BT Phone Book Unknown message type %x", + event.event_code + ); + } + } + + // respond to the data packet (handles both assembled and single packets) + switch(event.event_code) { + case BM83_PBAP_EVENT_CONNECTED: + if (event.event_idStatus.status != 0) { + LogInfo(LOG_SOURCE_BT, "PBAP Session connection error %x", + event.event_idStatus.status + ); + _pb_status = Disconnected; + clearBuffer(); + } else { + LogDebug(LOG_SOURCE_BT, "PBAP Session connection success (%x)", + event.event_idStatus.status + ); + _pb_status = Connected; + clearBuffer(); + + // Make an onConnect command (if needed) + if (_pb_onConnect.isValid == 1) { + _pb_onConnect.isValid = 0; // don't do it again + BM83CommandPBAPPullPhoneBook(bt, _pb_onConnect.type, _pb_onConnect.offset); + } + + } + break; + + case BM83_PBAP_EVENT_DISCONNECTED: + LogDebug(LOG_SOURCE_BT, "BT: PBAP Session disconnected"); + _pb_status = Disconnected; + clearBuffer(); + break; + + case BM83_PBAP_EVENT_PULL_PHONEBOOK_RSP: + LogDebug( + LOG_SOURCE_BT, + "Received Phone Book Response: deviceId=%d, isEndOfBody=%s, flags=%04x", + event.event_phonebook.device_id, + (event.event_phonebook.is_end_of_body == 0) ? "0 [more to come]" : "1 [last packet]", + event.event_phonebook.flags + ); + + if ((event.event_phonebook.flags & FLAGS_PB_SIZE) > 0) { + LogDebug( + LOG_SOURCE_BT, + "Phone Book Size: %d", + event.event_phonebook.phone_book_size + ); + } + if ((event.event_phonebook.flags & (FLAGS_PB_PRIMARY_VER | FLAGS_PB_SECONDARY_VER)) > 0) { + LogRawDebug( + LOG_SOURCE_BT, + "Phone Book Versions " + ); + for (int i = 0; i < 16; i++) { + LogRawDebug(LOG_SOURCE_BT, "%02x", event.event_phonebook.primary_version_counter[i]); + } + LogRawDebug(LOG_SOURCE_BT, "."); + for (int i = 0; i < 16; i++) { + LogRawDebug(LOG_SOURCE_BT, "%02x", event.event_phonebook.secondary_version_counter[i]); + } + LogRawDebug(LOG_SOURCE_BT, "\r\n"); + } + if ((event.event_phonebook.flags & FLAGS_PB_DATABASE_ID) > 0) { + LogRawDebug( + LOG_SOURCE_BT, + "Phone Book database ID: " + ); + for (int i = 0; i < 16; i++) { + LogRawDebug(LOG_SOURCE_BT, "LOG_SOURCE_BT, %02x", event.event_phonebook.database_id[i]); + }; + LogRawDebug(LOG_SOURCE_BT, "\r\n"); + } + + if (event.event_phonebook.is_end_of_body == 0) { + // need to request continuation packets (not documented in BM83) + // assume this follows Obex (same opcode, final bit, no headers) + LogDebug(LOG_SOURCE_BT, "More Phone Book data to come..."); + BM83CommandPBAPContPhonebook(bt); + } + if (event.event_phonebook.is_end_of_body == 1) { + // this is the last packet. Process the vcard data (in the buffer) + parseVCards(); + clearBuffer(); + EventTriggerCallback(BT_EVENT_PHONEBOOK_UPDATE, 0); + + // Disconnect the session now that we have the last chunk + BM83CommandPBAPCloseSession(bt); + } + + break; + + case BM83_PBAP_EVENT_ABORT_RSP: + // Abort should leave us ready for next command + _pb_status = Connected; + clearBuffer(); + break; + + case BM83_PBAP_EVENT_ERROR_RSP: + LogDebug(LOG_SOURCE_BT, "PBAP Error %x", event.event_idStatus.status); + + // some event types indicate a connection failure + // if we see these, assume we are disconnected + _pb_status = ( + event.event_idStatus.status == 0x48 || + event.event_idStatus.status == 0x52 || + event.event_idStatus.status == 0x53 || + event.event_idStatus.status == 0x54 || + event.event_idStatus.status == 0x55 + ) ? Disconnected : Connected; + + clearBuffer(); + break; + + case BM83_PBAP_EVENT_SUPPORTED_FEATURES: + LogDebug(LOG_SOURCE_BT, + "PBAP Supported Features: device(%d) %s%s%s%s features(%x%x%x%x) version (%d.%d)", + event.event_features.device_id, + (event.event_features.supported_repos & 0x01) > 0 ? "[Local]" : "", + (event.event_features.supported_repos & 0x02) > 0 ? "[SIM]" : "", + (event.event_features.supported_repos & 0x04) > 0 ? "[Speed Dial]" : "", + (event.event_features.supported_repos & 0x08) > 0 ? "[Favorites]" : "", + event.event_features.supported_features[0], + event.event_features.supported_features[1], + event.event_features.supported_features[2], + event.event_features.supported_features[3], + event.event_features.profile_version_major, + event.event_features.profile_version_minor + ); + _pb_status = Connected; + clearBuffer(); + break; + + default: + // received an unknown but complete message + LogDebug(LOG_SOURCE_BT, "PBAP Command %x - not handled", event.event_code); + _pb_status = Connected; + clearBuffer(); + + } +} + +struct PHONEBOOK_ENTRY_TYPE *BM83GetPhonebookEntries() { + return _phonebook; +} \ No newline at end of file diff --git a/firmware/application/lib/bt/bt_bm83_pbap.h b/firmware/application/lib/bt/bt_bm83_pbap.h new file mode 100644 index 00000000..5aa1c518 --- /dev/null +++ b/firmware/application/lib/bt/bt_bm83_pbap.h @@ -0,0 +1,77 @@ +/* + * File: bt_bm83_pbap.h + * Author: Matt C + * Comments: Bluetooth PBAP phone book access functions for BM83 chip + * Revision history: + * v0.1 Feb 2024 (Matt C) - initial attempt + */ + +// This is a guard condition so that contents of this file are not included +// more than once. +#ifndef BT_PHONEBOOK_H +#define BT_PHONEBOOK_H +#include "bt_common.h" + +#define BT_EVENT_PHONEBOOK_UPDATE 18 + +#define BM83_PBAP_OP_OPEN_SESSION 0x00 +#define BM83_PBAP_OP_CLOSE_SESSION 0x01 +#define BM83_PBAP_OP_PULL_PHONEBOOK 0x02 +#define BM83_PBAP_OP_PULL_LIST 0x03 +#define BM83_PBAP_OP_PULL_ENTRY 0x04 +#define BM83_PBAP_OP_SET_PHONEBOOK 0x05 +#define BM83_PBAP_OP_ABORT 0x06 + +#define BM83_PBAP_EVENT_TYPE_SINGLE 0x00 +#define BM83_PBAP_EVENT_TYPE_FRAGMENT_START 0x01 +#define BM83_PBAP_EVENT_TYPE_FRAGMENT_CONTINUE 0x02 +#define BM83_PBAP_EVENT_TYPE_FRAGMENT_END 0x03 + +#define BM83_PBAP_EVENT_CONNECTED 0x00 +#define BM83_PBAP_EVENT_DISCONNECTED 0x01 +#define BM83_PBAP_EVENT_PULL_PHONEBOOK_RSP 0x02 +#define BM83_PBAP_EVENT_PULL_VCARD_LIST_RSP 0x03 +#define BM83_PBAP_EVENT_PULL_VCARD_ENTRY_RSP 0x04 +#define BM83_PBAP_EVENT_SET_PHONEBOOK_RSP 0x05 +#define BM83_PBAP_EVENT_ABORT_RSP 0x06 +#define BM83_PBAP_EVENT_ERROR_RSP 0x07 +#define BM83_PBAP_EVENT_SUPPORTED_FEATURES 0x08 + +#define PBAP_TYPE_PHONEBOOK 0x00 +#define PBAP_TYPE_INCOMING_CALL_HISTORY 0x01 +#define PBAP_TYPE_OUTGOING_CALL_HISTORY 0x02 +#define PBAP_TYPE_MISSED_CALL_HISTORY 0x03 +#define PBAP_TYPE_COMBINED_CALL_HISTORY 0x04 +#define PBAP_TYPE_SPEED_DIAL 0x05 +#define PBAP_TYPE_FAVORITE_CONTACTS 0x06 + +#define PHONE_BOOK_MAX_ENTRIES 8 +#define PHONE_BOOK_MAX_NUMBERS 3 +#define PHONE_BOOK_MAX_BUFFER 2048 + +#define FLAGS_PB_SIZE 0x0080 +#define FLAGS_PB_PRIMARY_VER 0x0100 +#define FLAGS_PB_SECONDARY_VER 0x0200 +#define FLAGS_PB_DATABASE_ID 0x0800 + +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +// Phone book structure in-memory +struct PHONE_NUMBER_TYPE { + char type[BT_CALLER_ID_FIELD_SIZE]; + char number[BT_CALLER_ID_FIELD_SIZE]; +}; + +struct PHONEBOOK_ENTRY_TYPE { + uint8_t index; + char name[BT_CALLER_ID_FIELD_SIZE]; + struct PHONE_NUMBER_TYPE phone[PHONE_BOOK_MAX_NUMBERS]; +}; + +void BM83CommandPBAPPullPhoneBook(BT_t *, uint8_t, uint16_t); +struct PHONEBOOK_ENTRY_TYPE *BM83GetPhonebookEntries(); + +// only exposed for use by CLI +void BM83ProcessEventPhoneBook(BT_t *, uint8_t *, uint16_t); + +#endif /* BT_PHONEBOOK_H */ diff --git a/firmware/application/lib/locale.c b/firmware/application/lib/locale.c index bea150fa..8c67a43c 100644 --- a/firmware/application/lib/locale.c +++ b/firmware/application/lib/locale.c @@ -85,6 +85,8 @@ static char *LOCALE_LANG_ENGLISH[] = { "Call", "Autozoom: %s", "PDC: %s", + "Your current location is:", + "Emergency - call 112", }; static char *LOCALE_LANG_FRENCH[] = { diff --git a/firmware/application/lib/locale.h b/firmware/application/lib/locale.h index ef3dc8dc..52c11636 100644 --- a/firmware/application/lib/locale.h +++ b/firmware/application/lib/locale.h @@ -88,8 +88,10 @@ #define LOCALE_STRING_CALL 75 #define LOCALE_STRING_AUTOZOOM 76 #define LOCALE_STRING_PDC 77 +#define LOCALE_STRING_EMERGENCY_POSITION 78 +#define LOCALE_STRING_EMERGENCY_HEADER 79 -#define LOCALE_STRING_MAX_INDEX 77 +#define LOCALE_STRING_MAX_INDEX 79 char *LocaleGetText(uint16_t); #endif /* LOCALE_H */ diff --git a/firmware/application/ui/bmbt.c b/firmware/application/ui/bmbt.c index aeceed87..2b2855f3 100644 --- a/firmware/application/ui/bmbt.c +++ b/firmware/application/ui/bmbt.c @@ -1942,6 +1942,19 @@ void BMBTIBusBMBTButtonPress(void *ctx, uint8_t *pkt) BTCommandCallAccept(context->bt); } else if (context->bt->callStatus == BT_CALL_OUTGOING) { BTCommandCallEnd(context->bt); + } else { + if (context->menu == BMBT_MENU_DIAL && + context->bt->dialBuffer[0] != 0 ) + { + // invoke dialing + LogDebug(LOG_SOURCE_UI, "BMBT Telephone request to dial: %s", context->bt->dialBuffer); + BTCommandDial(context->bt, context->bt->dialBuffer, NULL); + } else { + // render phone screen + LogDebug(LOG_SOURCE_UI, "BMBT Dial Button - switch to Dialer menu"); + IBusCommandTELSetGTDisplayMenu(context->ibus); + context->menu = BMBT_MENU_DIAL; + } } } else if (context->bt->callStatus == BT_CALL_INACTIVE && pkt[IBUS_PKT_DB1] == IBUS_DEVICE_BMBT_Button_TEL_Hold @@ -2037,9 +2050,9 @@ void BMBTIBusGTChangeUIRequest(void *ctx, uint8_t *pkt) if (ConfigGetSetting(CONFIG_SETTING_HFP) == CONFIG_SETTING_ON) { IBusCommandTELSetGTDisplayMenu(context->ibus); IBusCommandTELSetGTDisplayNumber(context->ibus, context->bt->dialBuffer); + } } } -} /** * BMBTIKESpeedRPMUpdate() @@ -2223,6 +2236,32 @@ void BMBTIBusMenuSelect(void *ctx, uint8_t *pkt) BMBTSettingsUpdateUI(context, selectedIdx); } } + else if (context->menu == BMBT_MENU_DIAL) { + BMBTDialScreenUI(context,selectedIdx,pkt); + } + else if (context->menu == BMBT_MENU_DIAL_EMERGENCY && + pkt[IBUS_PKT_CMD] == IBUS_CMD_GT_MENU_SELECT) { + // button presses on the Emergency / SOS screen + + if (pkt[4] == 0xF1 && pkt[6] == 0x10) { + // release the "back" button + LogDebug(LOG_SOURCE_UI, "Back button on Emergency screen"); + IBusCommandTELSetGTDisplayMenu(context->ibus); + context->menu = BMBT_MENU_DIAL; + } + else if (pkt[4] == 0xF1 && pkt[6] == 0x11) { + // release the "left" button + LogDebug(LOG_SOURCE_UI, "Left button on Emergency screen"); + } + else if (pkt[4] == 0xF1 && pkt[6] == 0x12) { + // release the "right" button + LogDebug(LOG_SOURCE_UI, "Right button on Emergency screen"); + } + else if (pkt[4] == 0xF1 && pkt[6] == 0x13) { + // release the "middle" button + LogDebug(LOG_SOURCE_UI, "Middle button on Emergency screen"); + } + } } /** @@ -2722,3 +2761,175 @@ void BMBTTimerScrollDisplay(void *ctx) } } } + +/** + * BMBTEmergencyScreen() + * Description: + * Render Emergency Screen + * Params: + * void *ctx - The context + * Returns: + * void + */ +void BMBTEmergencyScreen(BMBTContext_t *context) +{ + uint8_t msg[50]={IBUS_CMD_GT_WRITE_NO_CURSOR,0xF1,0x00}; + char *msg_body = (char *)(msg+4); + + msg[3]=0x60; + UtilsStrncpy(msg_body, LocaleGetText(LOCALE_STRING_EMERGENCY_POSITION), 50-4); + IBusSendCommand(context->ibus, IBUS_DEVICE_TEL, IBUS_DEVICE_GT, msg, strlen(msg_body)+5); + + msg[3]=0x41; + UtilsStrncpy(msg_body, context->ibus->telematicsLocale, 50-4); + IBusSendCommand(context->ibus, IBUS_DEVICE_TEL, IBUS_DEVICE_GT, msg, strlen(msg_body)+5); + + msg[3]=0x42; + UtilsStrncpy(msg_body, context->ibus->telematicsStreet, 50-4); + IBusSendCommand(context->ibus, IBUS_DEVICE_TEL, IBUS_DEVICE_GT, msg, strlen(msg_body)+5); + + msg[3]=0x44; + UtilsStrncpy(msg_body, context->ibus->telematicsLatitude, 50-4); + IBusSendCommand(context->ibus, IBUS_DEVICE_TEL, IBUS_DEVICE_GT, msg, strlen(msg_body)+5); + + msg[3]=0x45; + UtilsStrncpy(msg_body, context->ibus->telematicsLongtitude, 50-4); + IBusSendCommand(context->ibus, IBUS_DEVICE_TEL, IBUS_DEVICE_GT, msg, strlen(msg_body)+5); + + msg[2]=0x01; //back button + msg[3]=0x50; + msg[4]=0x01; + IBusSendCommand(context->ibus, IBUS_DEVICE_TEL, IBUS_DEVICE_GT, msg, 5); + + msg[0]=IBUS_CMD_GT_WRITE_WITH_CURSOR; + msg[2]=0x00; + msg[3]=0x00; + UtilsStrncpy(msg_body, LocaleGetText(LOCALE_STRING_EMERGENCY_HEADER), 50-4); + IBusSendCommand(context->ibus, IBUS_DEVICE_TEL, IBUS_DEVICE_GT, msg, strlen(msg_body)+5); + context->menu = BMBT_MENU_DIAL_EMERGENCY; +} + + +/** + * BMBTDialScreenUI() + * Description: + * Process UI interaction on Dial screen + * Params: + * void *ctx - The context + * unsigned char - keypress + * unsinged char * - full IBUS packet + * Returns: + * void + */ +//#define BMBT_LAYOUT_TEL_DIAL 0x42 +//#define BMBT_LAYOUT_TEL_DIRECTORY 0x43 +//#define BMBT_LAYOUT_TEL_TOP_8 0x80 +//#define BMBT_LAYOUT_TEL_LIST 0xf0 +//#define BMBT_LAYOUT_TEL_DETAIL 0xf1 + +#define BMBT_FUNCTION_TEL_NULL 0x00 +#define BMBT_FUNCTION_TEL_CONTACT 0x01 +#define BMBT_FUNCTION_TEL_DIGIT 0x02 +#define BMBT_FUNCTION_TEL_SOS 0x05 +#define BMBT_FUNCTION_TEL_NAVIGATION 0x07 +#define BMBT_FUNCTION_TEL_INFO 0x08 + +//#define BMBT_MASK_INDEX 0x1f +//#define BMBT_MASK_CLEAR 0x20 +//#define BMBT_MASK_BUFFER 0x40 +//#define BMBT_MASK_HIGHLIGHT 0x80 + +void BMBTDialScreenUI(void *ctx, uint8_t cmd, uint8_t *pkt) +{ + BMBTContext_t *context = (BMBTContext_t *) ctx; + + uint8_t size = strlen(context->bt->dialBuffer); + uint8_t changed = 0; + + if (pkt[5] == BMBT_FUNCTION_TEL_DIGIT) { + if ((cmd>=0x40)&&(cmd<=0x49)) { + // number released + if (sizebt->dialBuffer[size++]=cmd+'0'-0x40; + context->bt->dialBuffer[size]=0; + changed = 1; + } + } else if (cmd == 0x5A) { + // * released + if (sizebt->dialBuffer[size++]='*'; + context->bt->dialBuffer[size]=0; + changed = 1; + } + } else if (cmd == 0x3A) { + // * hold = + + if (sizebt->dialBuffer[size++]='+'; + context->bt->dialBuffer[size]=0; + changed = 1; + } + } else if (cmd == 0x5B) { + // # released + if (sizebt->dialBuffer[size++]='#'; + context->bt->dialBuffer[size]=0; + changed = 1; + } + } else if (cmd == 0x4A) { + // <- released = delete one char + if (size > 0) { + context->bt->dialBuffer[--size] = 0; + changed = 1; + } + } else if (cmd == 0x2A) { + // -< held = delete all + if (size > 0) { + size = 0; + context->bt->dialBuffer[0] = 0; + changed = 1; + } + } + } else if ((pkt[5] == BMBT_FUNCTION_TEL_SOS) && (cmd == 0x08)) { + // SOS - set buffer to SOS number ( 112, 911, eventually configurable ) + // eventually render full screen with coordinates and button to confirm + // and send also SMS with details + LogDebug(LOG_SOURCE_UI, "Navigate from [BMBT_MENU_DIAL] to [BMBT_DIAL_EMERGENCY]"); + BMBTEmergencyScreen(context); + + } else if ((pkt[5] == BMBT_FUNCTION_TEL_NAVIGATION) && (cmd == 0x1d)) { + // messaging selected + LogDebug(LOG_SOURCE_UI, "Navigate from [BMBT_MENU_DIAL] to [SMS]"); + + } else if ((pkt[5] == BMBT_FUNCTION_TEL_NAVIGATION) && (cmd == 0x1f)) { + // Directory Selected + LogDebug(LOG_SOURCE_UI, "Navigate from [BMBT_MENU_DIAL] to [BMBT_MENU_DIAL_DIRECTORY]"); + + } else if ((pkt[5] == BMBT_FUNCTION_TEL_INFO) && (cmd == 0x0a)) { + // Info Selected + LogDebug(LOG_SOURCE_UI, "Navigate from [BMBT_MENU_DIAL] to [TEL Info]"); + + // Sample messages + // C8 11 3B 24 91 b3 5a 5a 5a 5a 5a 5a # Strength + // C8 06 3B 24 96 "47" # call minutes + // C8 06 3B 24 97 "02" # call seconds + } else { + LogDebug(LOG_SOURCE_UI, "Dial screen command not handled. act=%02x cmd=%02x **", pkt[5], cmd); + } + + // show updated number + if (changed == 1) { + if (size>0) { + char msg[BT_DIAL_BUFFER_FIELD_SIZE+4] = {IBUS_TEL_CMD_NUMBER, 0x63, 0x00}; + snprintf(msg+3,BT_DIAL_BUFFER_FIELD_SIZE-1,"%s",context->bt->dialBuffer); + size+=5; + if (size>BT_DIAL_BUFFER_FIELD_SIZE+4) { + size = BT_DIAL_BUFFER_FIELD_SIZE+4; + } + IBusSendCommand(context->ibus, IBUS_DEVICE_TEL, IBUS_DEVICE_GT, (unsigned char *)msg, size); + } else { + const unsigned char msg2[] = {IBUS_TEL_CMD_NUMBER, 0x61, 0x20}; + IBusSendCommand(context->ibus, IBUS_DEVICE_TEL, IBUS_DEVICE_GT, msg2, sizeof(msg2)); + } + } + +} diff --git a/firmware/application/ui/bmbt.h b/firmware/application/ui/bmbt.h index e83e242b..45e6041c 100644 --- a/firmware/application/ui/bmbt.h +++ b/firmware/application/ui/bmbt.h @@ -39,6 +39,10 @@ #define BMBT_MENU_SETTINGS_COMFORT 7 #define BMBT_MENU_SETTINGS_CALLING 8 #define BMBT_MENU_SETTINGS_UI 9 +#define BMBT_MENU_DIAL 10 +#define BMBT_MENU_DIAL_EMERGENCY 11 +#define BMBT_MENU_DIAL_PHONEBOOK 12 +#define BMBT_MENU_DIAL_INFO 13 #define BMBT_MENU_IDX_BACK 7 #define BMBT_MENU_IDX_DASHBOARD 0 #define BMBT_MENU_IDX_DEVICE_SELECTION 1 @@ -76,7 +80,6 @@ #define BMBT_MENU_IDX_SETTINGS_IU_DASH_OBC 3 #define BMBT_MENU_IDX_SETTINGS_UI_MONITOR_OFF 4 #define BMBT_MENU_IDX_SETTINGS_UI_LANGUAGE 5 - #define BMBT_MENU_BUFFER_OK 0 #define BMBT_MENU_BUFFER_FLUSH 1 @@ -160,4 +163,5 @@ void BMBTIBusVehicleConfig(void *, uint8_t *); void BMBTTimerHeaderWrite(void *); void BMBTTimerMenuWrite(void *); void BMBTTimerScrollDisplay(void *); +void BMBTDialScreenUI(void *, uint8_t, uint8_t *); #endif /* BMBT_H */ From 1096e359b537c028ec729b472081886c8d18b110 Mon Sep 17 00:00:00 2001 From: "Ticklemonster Dad (aka Matt)" Date: Sun, 25 Feb 2024 14:10:50 +0800 Subject: [PATCH 2/2] First build of PBAP phone book features for BM83. Includes phone dialer menu from feature/phone branch - but does not include directory menus. Phone book can be tested using CLI only ... "BT PB" (phone book), "BT CH" (call history) and "BT FAV" (favorites) --- firmware/application/lib/bt.c | 60 ++ firmware/application/lib/bt.h | 4 + firmware/application/lib/bt/bt_bm83.c | 7 + firmware/application/lib/bt/bt_bm83_pbap.c | 735 ++++++++++++++++++ firmware/application/lib/bt/bt_bm83_pbap.h | 77 ++ firmware/application/lib/locale.c | 2 + firmware/application/lib/locale.h | 4 +- .../application/nbproject/configurations.xml | 4 +- firmware/application/ui/bmbt.c | 213 ++++- firmware/application/ui/bmbt.h | 6 +- firmware/application/ui/cli.c | 22 +- 11 files changed, 1129 insertions(+), 5 deletions(-) create mode 100644 firmware/application/lib/bt/bt_bm83_pbap.c create mode 100644 firmware/application/lib/bt/bt_bm83_pbap.h diff --git a/firmware/application/lib/bt.c b/firmware/application/lib/bt.c index c27c33f9..28cc0219 100644 --- a/firmware/application/lib/bt.c +++ b/firmware/application/lib/bt.c @@ -3,9 +3,12 @@ * Author: Ted Salmon * Description: * Implementation of the abstract Bluetooth Module API + * History: + * Feb 2024 (Matt C) - Added phone book access (currently for BM83 only) */ #include "bt.h" #include "locale.h" +#include "bt/bt_bm83_pbap.h" /** * BTInit() @@ -459,6 +462,63 @@ void BTCommandToggleVoiceRecognition(BT_t *bt) } } +/** + * BTGetCommandReqPhonebookFrom + * @param bt - pointer to the bluetooth module + * @param offset - index to start + * + * Requests a phone book entries starting from the provided offset + */ +void BTCommandReqPhonebookFrom(BT_t *bt, uint8_t offset) { + if (bt->type == BT_BTM_TYPE_BM83) { + // TODO: Manage paging in the menu + BM83CommandPBAPPullPhoneBook(bt, PBAP_TYPE_PHONEBOOK, offset); + } else { + LogDebug(LOG_SOURCE_BT, "Get Phonebook - not implemented for BC127"); + } +} + +/** + * BTCommandReqPhonebook() + * @param bt - pointer to the module object + * + * Requests the first entries from the phone book. + * A convenience function for BTCommandReqPhonebookFrom with 0 offset + */ +void BTCommandReqPhonebook(BT_t *bt) +{ + BTCommandReqPhonebookFrom(bt, 0); +} +/** + * BTCommandReqFavorites() + * @param bt - A pointer to the bluetooth module object + * + * Requests the "Top-8" from phone favorites. + */ +void BTCommandReqFavorites(BT_t *bt) +{ + if (bt->type == BT_BTM_TYPE_BM83) { + // TODO: Manage paging in the menu + BM83CommandPBAPPullPhoneBook(bt, PBAP_TYPE_FAVORITE_CONTACTS, 0); + } else { + LogDebug(LOG_SOURCE_BT, "Get Favorites - not implemented for BC127"); + } +} +/** + * BTCommandReqRecent() + * @param bt - A pointer to the bluetooth module object + * + * Requests the combined call history from phone + */ +void BTCommandReqRecent(BT_t *bt) +{ + if (bt->type == BT_BTM_TYPE_BM83) { + BM83CommandPBAPPullPhoneBook(bt, PBAP_TYPE_COMBINED_CALL_HISTORY, 0); + } else { + LogDebug(LOG_SOURCE_BT, "Get Call History - not implemented for BC127"); + } +} + /** * BTHasActiveMacId() * Description: diff --git a/firmware/application/lib/bt.h b/firmware/application/lib/bt.h index 2af2c56b..149b40b8 100644 --- a/firmware/application/lib/bt.h +++ b/firmware/application/lib/bt.h @@ -36,4 +36,8 @@ void BTCommandSetDiscoverable(BT_t *, unsigned char); void BTCommandToggleVoiceRecognition(BT_t *); uint8_t BTHasActiveMacId(BT_t *); void BTProcess(BT_t *); +void BTCommandReqPhonebookFrom(BT_t *, uint8_t); +void BTCommandReqPhonebook(BT_t *); +void BTCommandReqFavorites(BT_t *); +void BTCommandReqRecent(BT_t *); #endif /* BT_H */ diff --git a/firmware/application/lib/bt/bt_bm83.c b/firmware/application/lib/bt/bt_bm83.c index 93e66454..ff967341 100644 --- a/firmware/application/lib/bt/bt_bm83.c +++ b/firmware/application/lib/bt/bt_bm83.c @@ -3,8 +3,11 @@ * Author: Ted Salmon * Description: * Implementation of the Microchip BM83 Bluetooth UART API + * Revision History: + * Feb 2024 (Matt C) - Forward PBAP events to handler in bt_phonebook.c */ #include "bt_bm83.h" +#include "bt_bm83_pbap.h" #include "../locale.h" int8_t BTBM83MicGainTable[] = { @@ -1331,6 +1334,10 @@ void BM83Process(BT_t *bt) if (event == BM83_EVT_REPORT_TYPE_CODEC) { BM83ProcessEventReportTypeCodec(bt, eventData, dataLength); } + /* Phone Book events */ + if (event == BM83_EVT_PBAPC_EVENT) { + BM83ProcessEventPhoneBook(bt, eventData, dataLength); + } } } UARTReportErrors(&bt->uart); diff --git a/firmware/application/lib/bt/bt_bm83_pbap.c b/firmware/application/lib/bt/bt_bm83_pbap.c new file mode 100644 index 00000000..798a67e1 --- /dev/null +++ b/firmware/application/lib/bt/bt_bm83_pbap.c @@ -0,0 +1,735 @@ +/* + * File: bt_bm83_pbap.c + * Author: Matt C + * Description: + * Implementation of phone book access for the Microchip BM83 Bluetooth UART API + * Attempts to enable the existing phone functions including: + * - Directory (access one at a time for IKE/MID, or 8 at a time for BM) + * - Top-8 (favorites) + * - Last Numbers (call history) + * + * Assumes that there is not enough memory available to download the entire + * phone book on connection (TODO - test this assumption?) + * + * Data storage uses a fixed allocation of PHONE_BOOK_MAX_ENTRIES (8) + * each with up to PHONE_BOOK_MAX_NUMBERS (3) - to match the BMBT display. + * + * Not yet attempted: + * - SMS messages + * - Adding menus + * - Updating dial buffer with selected contact (bt->dialBuffer)? + * + * Revision History: + * Feb 2024 (Matt C) - Initial build + */ + +#include +#include "../utils.h" +#include "./bt_bm83.h" +#include "./bt_bm83_pbap.h" +#include "../event.h" + +// Simple state machine +enum Phonebook_Status { + Disconnected, + Connected, + Waiting +}; +static enum Phonebook_Status _pb_status = Disconnected; +static struct { + uint8_t isValid; + uint8_t type; + uint16_t offset; +} _pb_onConnect; + +// Phonebook message buffer (to assemble multi-part messages) +static uint8_t _pb_buffer[PHONE_BOOK_MAX_BUFFER]; +static uint16_t _pb_buffer_offset = 0; + +// TO DO: Strings that should be localised... +const char* DEFAULT_TEL_TYPE_NAME = "TEL"; + +static struct PHONEBOOK_ENTRY_TYPE _phonebook[PHONE_BOOK_MAX_ENTRIES]; + + +// +// Event packet header structures +// + +// common header shapes... +struct PBAPC_HEADER_ID { + uint8_t device_id; +}; +struct PBAPC_HEADER_ID_STATUS { + uint8_t device_id; + uint8_t status; +}; +// header for receiving phone book or vCard lists +struct PBAPC_HEADER_PHONEBOOK { + uint8_t device_id; + uint8_t is_end_of_body; + uint16_t flags; + uint16_t phone_book_size; + uint8_t new_missed_calls; + uint8_t primary_version_counter[16]; + uint8_t secondary_version_counter[16]; + uint8_t database_id[16]; +}; +// header for a single vCard +struct PBAPC_HEADER_VCARD { + uint8_t device_id; + uint8_t is_end_of_body; + uint16_t flags; + uint8_t database_id[16]; +}; +// header for a supported features event +struct PBAPC_HEADER_FEATURES { + uint8_t device_id; + uint8_t supported_repos; + uint8_t supported_features[4]; + uint8_t profile_version_major; + uint8_t profile_version_minor; +}; +// the combined event header +struct PBAPC_EVENT_HEADER { + uint8_t event_code; + union { + struct PBAPC_HEADER_ID event_idOnly; + struct PBAPC_HEADER_ID_STATUS event_idStatus; + struct PBAPC_HEADER_PHONEBOOK event_phonebook; + struct PBAPC_HEADER_VCARD event_vcard; + struct PBAPC_HEADER_FEATURES event_features; + }; +}; + + + +/* + * Buffer management functions + */ +static void clearBuffer() +{ + memset(_pb_buffer, 0, PHONE_BOOK_MAX_BUFFER); + _pb_buffer_offset = 0; +} +static int appendBuffer(uint8_t *data, size_t size) +{ + if (_pb_buffer_offset + size < PHONE_BOOK_MAX_BUFFER) { + memcpy(_pb_buffer + _pb_buffer_offset, data, size); + _pb_buffer_offset += size; + } else { + return -1; + } + + return 0; +} + + +/** + * debugEntries + * - used to output the phone book to the debug console + */ +static void debugEntries() { + LogDebug(LOG_SOURCE_BT, "Phone Book Entries"); + for (int i = 0; i < PHONE_BOOK_MAX_ENTRIES; i++) { + LogRawDebug(LOG_SOURCE_BT, "[%d] %s", i, _phonebook[i].name ? _phonebook[i].name : "(null)"); + for (int j = 0; j < PHONE_BOOK_MAX_NUMBERS; j++) { + if (_phonebook[i].phone[j].number[0] != 0) { + LogRawDebug(LOG_SOURCE_BT, ", %s:%s", _phonebook[i].phone[j].type, _phonebook[i].phone[j].number); + } + } + LogRawDebug(LOG_SOURCE_BT, "\r\n"); + } +} + +/** + * parseVCards + * a very simple parser to read a few key fields from the returned vCards + * Expects the buffer (_pb_buffer) to be a UTF-8 string that meets vCard spec. + */ +void parseVCards() +{ + LogDebug(LOG_SOURCE_BT, "Parsing vCard Buffer..."); +// LogDebug(LOG_SOURCE_BT, "%s", _pb_buffer); + + // clear the current phone book + memset(_phonebook, 0, sizeof(_phonebook)); + + // break the buffer into lines + char *lptr; + char *l = strtok_r((char *)_pb_buffer, "\r\n", &lptr); + uint8_t idx = 0; + uint8_t jdx = 0; + + struct PHONEBOOK_ENTRY_TYPE entry; + + while(l != NULL) { + char *tptr; + char *prop = strtok_r(l, ":", &tptr); + char *prop0 = strtok(prop, ";"); + char *prop1 = strtok(NULL, ";"); + char *value = strtok_r(NULL, ":", &tptr); + + if ( UtilsStricmp(prop, "BEGIN") == 0 && + UtilsStricmp(value, "VCARD") == 0 ) { + // BEGIN:VCARD + jdx = 0; + } + else if (UtilsStricmp(prop, "END") == 0 + && UtilsStricmp(value, "VCARD") == 0) + { + // save it only if there is at least a name and a number + if (strlen(entry.name) > 0 && strlen(entry.phone[0].number) > 0) { + memcpy(&(_phonebook[idx]), &entry, sizeof(struct PHONEBOOK_ENTRY_TYPE)); + idx++; + } else { + LogDebug(LOG_SOURCE_BT, "- Missing data name=%s phone0=%s", entry.name, entry.phone[0].number); + } + if (idx > PHONE_BOOK_MAX_ENTRIES) { + LogWarning("Too many phone book entries (%d > %d)", idx, PHONE_BOOK_MAX_ENTRIES); + break; + } + } +// else if (UtilsStricmp(prop, "VERSION") == 0) { +// LogDebug(LOG_SOURCE_BT, " VERSION: %s", value); +// } + else if (UtilsStricmp(prop, "FN") == 0) { + // FN: Formatted Name - use this one + strncpy(entry.name, value, BT_CALLER_ID_FIELD_SIZE - 1); + } + else if (UtilsStricmp(prop0, "X-IRMC-CALL-DATETIME") == 0) { + LogDebug(LOG_SOURCE_BT, "Call %s at: %s", prop1, value); + } + else if (UtilsStricmp(prop0, "TEL") == 0) { + if (prop1) { + char *eq = strstr(prop1, "="); + if (eq) prop1 = (eq + 1); + } + + if (jdx < PHONE_BOOK_MAX_ENTRIES) { + if (value) strncpy(entry.phone[jdx].number, value, BT_CALLER_ID_FIELD_SIZE - 1); + if (prop1) { + strncpy(entry.phone[jdx].type, prop1, BT_CALLER_ID_FIELD_SIZE - 1); + } else { + strncpy(entry.phone[jdx].type, DEFAULT_TEL_TYPE_NAME, BT_CALLER_ID_FIELD_SIZE - 1); + } + jdx++; + } else { + LogWarning("Too many phone numbers (> %d)", PHONE_BOOK_MAX_NUMBERS ); + } + } + + l = strtok_r(NULL, "\r\n", &lptr); + } + clearBuffer(); + + // DEBUG + debugEntries(); + + // TODO - Send an event for anyone who wants to know? + // EventTriggerCallback(BT_EVENT_PHONEBOOK_UPDATE) +} + + +/** + * BM83CommandPBAPOpenSession + * @param bt - the Bluetooth module + * + * Requests to open a phone book session (if not already open) + */ +void BM83CommandPBAPOpenSession(BT_t *bt) +{ + if (_pb_status != Disconnected) return; + + uint8_t command[] = { + BM83_CMD_PBAPC_CMD, + BM83_PBAP_OP_OPEN_SESSION, + bt->activeDevice.deviceId & 0xF // Linked Database, the lower nibble + }; + BM83SendCommand(bt, command, sizeof(command)); +} + +/** + * BM83CommandPBAPCloseSession + * @param bt - the Bluetooth module + * + * Requests to close the phone book session (if open) + */ +void BM83CommandPBAPCloseSession(BT_t *bt) +{ + if (_pb_status == Disconnected) return; + + uint8_t command[] = { + BM83_CMD_PBAPC_CMD, + BM83_PBAP_OP_CLOSE_SESSION, + bt->activeDevice.deviceId & 0xF // Linked Database, the lower nibble + }; + BM83SendCommand(bt, command, sizeof(command)); +} + +/** + * BM83CommandPBAPAbort + * @param bt - the Bluetooth module + * + * sends the abort command (to stop a running phone book operation) + */ +void BM83CommandPBAPAbort(BT_t *bt) +{ + if (_pb_status == Disconnected) return; + + uint8_t command[] = { + BM83_CMD_PBAPC_CMD, + BM83_PBAP_OP_ABORT, + bt->activeDevice.deviceId & 0xF // Linked Database, the lower nibble + }; + BM83SendCommand(bt, command, sizeof(command)); +} + + +// +// Packet header parsing... +// +void parseIdHeader(struct PBAPC_HEADER_ID *header, uint8_t *data) +{ + header->device_id = *(data); +} +void parseIdStatusHeader(struct PBAPC_HEADER_ID_STATUS *header, uint8_t *data) +{ + header->device_id = data[0]; + header->status = data[1]; +} +void parsePhonebookHeader(struct PBAPC_HEADER_PHONEBOOK *header, uint8_t *data) +{ + header->device_id = data[0]; + header->is_end_of_body = data[1]; + header->flags = (data[2] << 8) + data[3]; + if (header->flags & FLAGS_PB_SIZE) { + header->phone_book_size = (data[4] << 8) + data[5]; + } else { + header->phone_book_size = 0; + } + header->new_missed_calls = data[6]; + if (header->flags & FLAGS_PB_PRIMARY_VER) { + memcpy(header->primary_version_counter, data + 7, 16); + } else { + memset(header->primary_version_counter, 0, 16); + } + if (header->flags & FLAGS_PB_SECONDARY_VER) { + memcpy(header->secondary_version_counter, data + 23, 16); + } else { + memset(header->secondary_version_counter, 0, 16); + } + if (header->flags & FLAGS_PB_DATABASE_ID) { + memcpy(header->database_id, data + 39, 16); + } else { + memset(header->database_id, 0, 16); + } +} +void parseVCardHeader(struct PBAPC_HEADER_VCARD *header, uint8_t *data) +{ + header->device_id = data[0]; + header->is_end_of_body = data[1]; + header->flags = (data[2] << 8) + data[3]; + if (header->flags & 0x0400) { + memcpy(header->database_id, data + 4, 16); + } else { + memset(header->database_id, 0, 16); + } +} +void parseFeaturesHeader(struct PBAPC_HEADER_FEATURES *header, uint8_t *data) +{ + header->device_id = data[0]; + header->supported_repos = data[1]; + memcpy(header->supported_features, data+2, 4); + header->profile_version_major = data[6]; + header->profile_version_minor = data[7]; +} + +/** + * BM83CommandPBAPPullPhoneBook + * @param bt - the bluetooth module + * @param type - the type of phone book (e.g. PBAP_TYPE_PHONEBOOK) + * @param offset - the starting offset + * + * sends a command to fetch the next set of phonebook entries + */ +void BM83CommandPBAPPullPhoneBook(BT_t *bt, uint8_t type, uint16_t offset) +{ + if (_pb_status == Disconnected) { + // add this to an "onConnect" + _pb_onConnect.isValid = 1; + _pb_onConnect.type = type; + _pb_onConnect.offset = offset; + + BM83CommandPBAPOpenSession(bt); + return; + } + if (!_pb_status == Connected) { + LogInfo(LOG_SOURCE_BT, "Attempted to get a phone book without a connection"); + return; + } + + uint8_t command[] = { + BM83_CMD_PBAPC_CMD, + BM83_PBAP_OP_PULL_PHONEBOOK, + bt->activeDevice.deviceId & 0xF, // Linked Database, the lower nibble + 0x00, // repository 0x00 = Phone, 0x01 = SIM + type, // object type (1 byte)) + 0x54, // Flags (2 bytes): b14 = max_list_count, b12+b10 = vCard selector + 0x07, // Flags (2 bytes): b0 = properties, b1 = format, b2 = offset + (PHONE_BOOK_MAX_ENTRIES & 0xFF00) >> 8, // max entries (2 bytes)) + (PHONE_BOOK_MAX_ENTRIES & 0xFF), + 0x87, // Property selector (8 bytes) - tel, fn, n, version + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, // Format (1 byte): 0x00 = vCard2.1, 0x01 = vCard3.0 + (offset & 0xFF00) >> 8, // List start offset: (2 bytes)) + (offset & 0xFF), + 0x82, // vCard selector (8 bytes) - TEL (op) FN + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01 // vCard selector operator (1 byte) - (TEL) AND (FN) + }; + + _pb_status = Waiting; + BM83SendCommand(bt, command, sizeof(command)); +} +void BM83CommandPBAPContPhonebook(BT_t *bt) { + uint8_t command[] = { + BM83_CMD_PBAPC_CMD, + BM83_PBAP_OP_PULL_PHONEBOOK, + bt->activeDevice.deviceId & 0xF, // Linked Database, the lower nibble + 0x00, // repository 0x00 = Phone, 0x01 = SIM + 0x00, // object type (1 byte) + 0x00, // Flags (2 bytes): ignore for continue) + 0x00, + 0x00, // Max entries (2 bytes): ignored + 0x00, + 0x00, // Property selector (8 bytes): ignore + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, // Format (1 byte): 0x00 = vCard2.1, 0x01 = vCard3.0 + 0x00, // List start offset (2 bytes): ignore + 0x00, + 0x00, // vCard selector (8 bytes) - not used + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00 // vCard selector operator (1 byte) - not used + }; + BM83SendCommand(bt, command, sizeof(command)); +} + + +/** + * BM83ProcessEventPhoneBook + * @param bt - the bluetooth module + * @param data - message bytes + * @param length - message length + * + * Phonebook events may be fragmented and spread across multiple messages. + * Fragmented messages are assembled into the buffer + * Full messages are processed from the buffer. + * If they are not a "last" message, then strip the headers and keep the data + * Once all vcard messages are received, then process the vCards in the buffer. + * + */ +void BM83ProcessEventPhoneBook(BT_t *bt, uint8_t *data, uint16_t length) +{ + uint8_t event_type = data[0]; + uint16_t total_length = (data[1] << 8) + data[2]; + uint16_t payload_length = (data[3] << 8) + data[4]; + + LogDebug(LOG_SOURCE_BT, "PBAP Packet: Type=%x Length=%d, Payload=%d", + event_type, total_length, payload_length + ); + + static uint16_t payload_start = 0; + static struct PBAPC_EVENT_HEADER event; + + // First fragment - make sure we can fit the full fragment + // and store it if we can + if (event_type == BM83_PBAP_EVENT_TYPE_FRAGMENT_START) { + _pb_status = Waiting; + + if (_pb_buffer_offset + total_length + 1 > PHONE_BOOK_MAX_BUFFER) { + LogWarning("BT PBAP - Not enough memory for packet (%d)", + total_length + ); + BM83CommandPBAPAbort(bt); + } + else if (length < 6 || + (data[5] == BM83_PBAP_EVENT_PULL_PHONEBOOK_RSP && length < 56)) + { + LogWarning("BT PBAP - truncated packet received"); + BM83CommandPBAPAbort(bt); + } + else if (data[5] != BM83_PBAP_EVENT_PULL_PHONEBOOK_RSP) { + LogWarning("BT Phonebook - opcode %x with fragments is not supported", + data[5] + ); + BM83CommandPBAPAbort(bt); + } + else + { + // we are receiving a multi-fragment phonebook response + // this is the first part - save the header ... + event.event_code = data[5]; + parsePhonebookHeader(&(event.event_phonebook), data + 6); + + // ... and start filling the vcard buffer with the data... + if (payload_length > 61) { + payload_start = _pb_buffer_offset; + appendBuffer((data+61),(payload_length-56)); + } + + LogDebug(LOG_SOURCE_BT, + "+ First %d data bytes: %d of %d", + payload_length, + payload_length, + total_length + ); + } + return; + } + + if (event_type == BM83_PBAP_EVENT_TYPE_FRAGMENT_CONTINUE) { + if (payload_start + total_length + 1 > PHONE_BOOK_MAX_BUFFER) { + LogDebug(LOG_SOURCE_BT, " Skipped %d bytes...", payload_length); + } else { + appendBuffer((data + 5), payload_length); + LogDebug(LOG_SOURCE_BT, "+ Added %d bytes to buffer: %d of %d", + payload_length, _pb_buffer_offset - payload_start + 56, total_length); + } + return; + } + + if (event_type == BM83_PBAP_EVENT_TYPE_FRAGMENT_END) { + if (payload_start + total_length + 1 > PHONE_BOOK_MAX_BUFFER) { + LogDebug(LOG_SOURCE_BT, " Skipped %d bytes...", payload_length); + + // the fragment is finished - but we couldn't buffer it + // reset for the next try... + clearBuffer(); + return; + } + else + { + appendBuffer((data+5), payload_length); + _pb_buffer[_pb_buffer_offset] = 0; + LogDebug(LOG_SOURCE_BT, "+ Final %d bytes to buffer: %d of %d", + payload_length, _pb_buffer_offset - payload_start + 56, total_length); + } + } + + if (event_type == BM83_PBAP_EVENT_TYPE_SINGLE) { + _pb_status = Connected; + + event.event_code = data[5]; + switch (event.event_code) { + case BM83_PBAP_EVENT_CONNECTED: + case BM83_PBAP_EVENT_ERROR_RSP: + parseIdStatusHeader(&(event.event_idStatus), data + 6); + break; + case BM83_PBAP_EVENT_DISCONNECTED: + case BM83_PBAP_EVENT_SET_PHONEBOOK_RSP: + case BM83_PBAP_EVENT_ABORT_RSP: + parseIdHeader(&(event.event_idOnly), data + 6); + break; + case BM83_PBAP_EVENT_PULL_PHONEBOOK_RSP: + case BM83_PBAP_EVENT_PULL_VCARD_LIST_RSP: + parsePhonebookHeader(&(event.event_phonebook), data + 6); + // copy the data body into the buffer + payload_start = _pb_buffer_offset; + if (appendBuffer(data + 61, payload_length - 56) < 0) { + LogDebug(LOG_SOURCE_BT, " Not enough memory. Skipping packet"); + // dump the buffer and start again... + clearBuffer(); + payload_start = 0; + return; + } + break; + case BM83_PBAP_EVENT_PULL_VCARD_ENTRY_RSP: + parseVCardHeader(&(event.event_vcard), data + 6); + // copy the data body into the buffer + payload_start = _pb_buffer_offset; + if (appendBuffer(data + 19, payload_length - 19) < 0) { + LogDebug(LOG_SOURCE_BT, " Not enough memory. Skipping packet"); + // dump the buffer and start again... + clearBuffer(); + payload_start = 0; + return; + } + break; + case BM83_PBAP_EVENT_SUPPORTED_FEATURES: + parseFeaturesHeader(&(event.event_features), data + 6); + break; + default: + LogWarning("BT Phone Book Unknown message type %x", + event.event_code + ); + } + } + + // respond to the data packet (handles both assembled and single packets) + switch(event.event_code) { + case BM83_PBAP_EVENT_CONNECTED: + if (event.event_idStatus.status != 0) { + LogInfo(LOG_SOURCE_BT, "PBAP Session connection error %x", + event.event_idStatus.status + ); + _pb_status = Disconnected; + clearBuffer(); + } else { + LogDebug(LOG_SOURCE_BT, "PBAP Session connection success (%x)", + event.event_idStatus.status + ); + _pb_status = Connected; + clearBuffer(); + + // Make an onConnect command (if needed) + if (_pb_onConnect.isValid == 1) { + _pb_onConnect.isValid = 0; // don't do it again + BM83CommandPBAPPullPhoneBook(bt, _pb_onConnect.type, _pb_onConnect.offset); + } + + } + break; + + case BM83_PBAP_EVENT_DISCONNECTED: + LogDebug(LOG_SOURCE_BT, "BT: PBAP Session disconnected"); + _pb_status = Disconnected; + clearBuffer(); + break; + + case BM83_PBAP_EVENT_PULL_PHONEBOOK_RSP: + LogDebug( + LOG_SOURCE_BT, + "Received Phone Book Response: deviceId=%d, isEndOfBody=%s, flags=%04x", + event.event_phonebook.device_id, + (event.event_phonebook.is_end_of_body == 0) ? "0 [more to come]" : "1 [last packet]", + event.event_phonebook.flags + ); + + if ((event.event_phonebook.flags & FLAGS_PB_SIZE) > 0) { + LogDebug( + LOG_SOURCE_BT, + "Phone Book Size: %d", + event.event_phonebook.phone_book_size + ); + } + if ((event.event_phonebook.flags & (FLAGS_PB_PRIMARY_VER | FLAGS_PB_SECONDARY_VER)) > 0) { + LogRawDebug( + LOG_SOURCE_BT, + "Phone Book Versions " + ); + for (int i = 0; i < 16; i++) { + LogRawDebug(LOG_SOURCE_BT, "%02x", event.event_phonebook.primary_version_counter[i]); + } + LogRawDebug(LOG_SOURCE_BT, "."); + for (int i = 0; i < 16; i++) { + LogRawDebug(LOG_SOURCE_BT, "%02x", event.event_phonebook.secondary_version_counter[i]); + } + LogRawDebug(LOG_SOURCE_BT, "\r\n"); + } + if ((event.event_phonebook.flags & FLAGS_PB_DATABASE_ID) > 0) { + LogRawDebug( + LOG_SOURCE_BT, + "Phone Book database ID: " + ); + for (int i = 0; i < 16; i++) { + LogRawDebug(LOG_SOURCE_BT, "%02x", event.event_phonebook.database_id[i]); + }; + LogRawDebug(LOG_SOURCE_BT, "\r\n"); + } + + if (event.event_phonebook.is_end_of_body == 0) { + // need to request continuation packets (not documented in BM83) + // assume this follows Obex (same opcode, final bit, no headers) + LogDebug(LOG_SOURCE_BT, "More Phone Book data to come..."); + BM83CommandPBAPContPhonebook(bt); + } + if (event.event_phonebook.is_end_of_body == 1) { + // this is the last packet. Process the vcard data (in the buffer) + parseVCards(); + clearBuffer(); + EventTriggerCallback(BT_EVENT_PHONEBOOK_UPDATE, 0); + + // Disconnect the session now that we have the last chunk + BM83CommandPBAPCloseSession(bt); + } + + break; + + case BM83_PBAP_EVENT_ABORT_RSP: + // Abort should leave us ready for next command + _pb_status = Connected; + clearBuffer(); + break; + + case BM83_PBAP_EVENT_ERROR_RSP: + LogDebug(LOG_SOURCE_BT, "PBAP Error %x", event.event_idStatus.status); + + // some event types indicate a connection failure + // if we see these, assume we are disconnected + _pb_status = ( + event.event_idStatus.status == 0x48 || + event.event_idStatus.status == 0x52 || + event.event_idStatus.status == 0x53 || + event.event_idStatus.status == 0x54 || + event.event_idStatus.status == 0x55 + ) ? Disconnected : Connected; + + clearBuffer(); + break; + + case BM83_PBAP_EVENT_SUPPORTED_FEATURES: + LogDebug(LOG_SOURCE_BT, + "PBAP Supported Features: device(%d) %s%s%s%s features(%x%x%x%x) version (%d.%d)", + event.event_features.device_id, + (event.event_features.supported_repos & 0x01) > 0 ? "[Local]" : "", + (event.event_features.supported_repos & 0x02) > 0 ? "[SIM]" : "", + (event.event_features.supported_repos & 0x04) > 0 ? "[Speed Dial]" : "", + (event.event_features.supported_repos & 0x08) > 0 ? "[Favorites]" : "", + event.event_features.supported_features[0], + event.event_features.supported_features[1], + event.event_features.supported_features[2], + event.event_features.supported_features[3], + event.event_features.profile_version_major, + event.event_features.profile_version_minor + ); + _pb_status = Connected; + clearBuffer(); + break; + + default: + // received an unknown but complete message + LogDebug(LOG_SOURCE_BT, "PBAP Command %x - not handled", event.event_code); + _pb_status = Connected; + clearBuffer(); + + } +} + +struct PHONEBOOK_ENTRY_TYPE *BM83GetPhonebookEntries() { + return _phonebook; +} \ No newline at end of file diff --git a/firmware/application/lib/bt/bt_bm83_pbap.h b/firmware/application/lib/bt/bt_bm83_pbap.h new file mode 100644 index 00000000..5aa1c518 --- /dev/null +++ b/firmware/application/lib/bt/bt_bm83_pbap.h @@ -0,0 +1,77 @@ +/* + * File: bt_bm83_pbap.h + * Author: Matt C + * Comments: Bluetooth PBAP phone book access functions for BM83 chip + * Revision history: + * v0.1 Feb 2024 (Matt C) - initial attempt + */ + +// This is a guard condition so that contents of this file are not included +// more than once. +#ifndef BT_PHONEBOOK_H +#define BT_PHONEBOOK_H +#include "bt_common.h" + +#define BT_EVENT_PHONEBOOK_UPDATE 18 + +#define BM83_PBAP_OP_OPEN_SESSION 0x00 +#define BM83_PBAP_OP_CLOSE_SESSION 0x01 +#define BM83_PBAP_OP_PULL_PHONEBOOK 0x02 +#define BM83_PBAP_OP_PULL_LIST 0x03 +#define BM83_PBAP_OP_PULL_ENTRY 0x04 +#define BM83_PBAP_OP_SET_PHONEBOOK 0x05 +#define BM83_PBAP_OP_ABORT 0x06 + +#define BM83_PBAP_EVENT_TYPE_SINGLE 0x00 +#define BM83_PBAP_EVENT_TYPE_FRAGMENT_START 0x01 +#define BM83_PBAP_EVENT_TYPE_FRAGMENT_CONTINUE 0x02 +#define BM83_PBAP_EVENT_TYPE_FRAGMENT_END 0x03 + +#define BM83_PBAP_EVENT_CONNECTED 0x00 +#define BM83_PBAP_EVENT_DISCONNECTED 0x01 +#define BM83_PBAP_EVENT_PULL_PHONEBOOK_RSP 0x02 +#define BM83_PBAP_EVENT_PULL_VCARD_LIST_RSP 0x03 +#define BM83_PBAP_EVENT_PULL_VCARD_ENTRY_RSP 0x04 +#define BM83_PBAP_EVENT_SET_PHONEBOOK_RSP 0x05 +#define BM83_PBAP_EVENT_ABORT_RSP 0x06 +#define BM83_PBAP_EVENT_ERROR_RSP 0x07 +#define BM83_PBAP_EVENT_SUPPORTED_FEATURES 0x08 + +#define PBAP_TYPE_PHONEBOOK 0x00 +#define PBAP_TYPE_INCOMING_CALL_HISTORY 0x01 +#define PBAP_TYPE_OUTGOING_CALL_HISTORY 0x02 +#define PBAP_TYPE_MISSED_CALL_HISTORY 0x03 +#define PBAP_TYPE_COMBINED_CALL_HISTORY 0x04 +#define PBAP_TYPE_SPEED_DIAL 0x05 +#define PBAP_TYPE_FAVORITE_CONTACTS 0x06 + +#define PHONE_BOOK_MAX_ENTRIES 8 +#define PHONE_BOOK_MAX_NUMBERS 3 +#define PHONE_BOOK_MAX_BUFFER 2048 + +#define FLAGS_PB_SIZE 0x0080 +#define FLAGS_PB_PRIMARY_VER 0x0100 +#define FLAGS_PB_SECONDARY_VER 0x0200 +#define FLAGS_PB_DATABASE_ID 0x0800 + +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +// Phone book structure in-memory +struct PHONE_NUMBER_TYPE { + char type[BT_CALLER_ID_FIELD_SIZE]; + char number[BT_CALLER_ID_FIELD_SIZE]; +}; + +struct PHONEBOOK_ENTRY_TYPE { + uint8_t index; + char name[BT_CALLER_ID_FIELD_SIZE]; + struct PHONE_NUMBER_TYPE phone[PHONE_BOOK_MAX_NUMBERS]; +}; + +void BM83CommandPBAPPullPhoneBook(BT_t *, uint8_t, uint16_t); +struct PHONEBOOK_ENTRY_TYPE *BM83GetPhonebookEntries(); + +// only exposed for use by CLI +void BM83ProcessEventPhoneBook(BT_t *, uint8_t *, uint16_t); + +#endif /* BT_PHONEBOOK_H */ diff --git a/firmware/application/lib/locale.c b/firmware/application/lib/locale.c index bea150fa..8c67a43c 100644 --- a/firmware/application/lib/locale.c +++ b/firmware/application/lib/locale.c @@ -85,6 +85,8 @@ static char *LOCALE_LANG_ENGLISH[] = { "Call", "Autozoom: %s", "PDC: %s", + "Your current location is:", + "Emergency - call 112", }; static char *LOCALE_LANG_FRENCH[] = { diff --git a/firmware/application/lib/locale.h b/firmware/application/lib/locale.h index ef3dc8dc..52c11636 100644 --- a/firmware/application/lib/locale.h +++ b/firmware/application/lib/locale.h @@ -88,8 +88,10 @@ #define LOCALE_STRING_CALL 75 #define LOCALE_STRING_AUTOZOOM 76 #define LOCALE_STRING_PDC 77 +#define LOCALE_STRING_EMERGENCY_POSITION 78 +#define LOCALE_STRING_EMERGENCY_HEADER 79 -#define LOCALE_STRING_MAX_INDEX 77 +#define LOCALE_STRING_MAX_INDEX 79 char *LocaleGetText(uint16_t); #endif /* LOCALE_H */ diff --git a/firmware/application/nbproject/configurations.xml b/firmware/application/nbproject/configurations.xml index 99484d54..22e955ee 100644 --- a/firmware/application/nbproject/configurations.xml +++ b/firmware/application/nbproject/configurations.xml @@ -14,6 +14,7 @@ lib/bt/bt_bc127.h lib/bt/bt_bm83.h lib/bt/bt_common.h + lib/bt/bt_bm83_pbap.h lib/bt.h lib/char_queue.h @@ -65,6 +66,7 @@ lib/bt/bt_bm83.c lib/bt/bt_bc127.c lib/bt/bt_common.c + lib/bt/bt_bm83_pbap.c lib/bt.c lib/char_queue.c @@ -115,7 +117,7 @@ noID XC16 2.10 - 2 + 3 diff --git a/firmware/application/ui/bmbt.c b/firmware/application/ui/bmbt.c index aeceed87..2b2855f3 100644 --- a/firmware/application/ui/bmbt.c +++ b/firmware/application/ui/bmbt.c @@ -1942,6 +1942,19 @@ void BMBTIBusBMBTButtonPress(void *ctx, uint8_t *pkt) BTCommandCallAccept(context->bt); } else if (context->bt->callStatus == BT_CALL_OUTGOING) { BTCommandCallEnd(context->bt); + } else { + if (context->menu == BMBT_MENU_DIAL && + context->bt->dialBuffer[0] != 0 ) + { + // invoke dialing + LogDebug(LOG_SOURCE_UI, "BMBT Telephone request to dial: %s", context->bt->dialBuffer); + BTCommandDial(context->bt, context->bt->dialBuffer, NULL); + } else { + // render phone screen + LogDebug(LOG_SOURCE_UI, "BMBT Dial Button - switch to Dialer menu"); + IBusCommandTELSetGTDisplayMenu(context->ibus); + context->menu = BMBT_MENU_DIAL; + } } } else if (context->bt->callStatus == BT_CALL_INACTIVE && pkt[IBUS_PKT_DB1] == IBUS_DEVICE_BMBT_Button_TEL_Hold @@ -2037,9 +2050,9 @@ void BMBTIBusGTChangeUIRequest(void *ctx, uint8_t *pkt) if (ConfigGetSetting(CONFIG_SETTING_HFP) == CONFIG_SETTING_ON) { IBusCommandTELSetGTDisplayMenu(context->ibus); IBusCommandTELSetGTDisplayNumber(context->ibus, context->bt->dialBuffer); + } } } -} /** * BMBTIKESpeedRPMUpdate() @@ -2223,6 +2236,32 @@ void BMBTIBusMenuSelect(void *ctx, uint8_t *pkt) BMBTSettingsUpdateUI(context, selectedIdx); } } + else if (context->menu == BMBT_MENU_DIAL) { + BMBTDialScreenUI(context,selectedIdx,pkt); + } + else if (context->menu == BMBT_MENU_DIAL_EMERGENCY && + pkt[IBUS_PKT_CMD] == IBUS_CMD_GT_MENU_SELECT) { + // button presses on the Emergency / SOS screen + + if (pkt[4] == 0xF1 && pkt[6] == 0x10) { + // release the "back" button + LogDebug(LOG_SOURCE_UI, "Back button on Emergency screen"); + IBusCommandTELSetGTDisplayMenu(context->ibus); + context->menu = BMBT_MENU_DIAL; + } + else if (pkt[4] == 0xF1 && pkt[6] == 0x11) { + // release the "left" button + LogDebug(LOG_SOURCE_UI, "Left button on Emergency screen"); + } + else if (pkt[4] == 0xF1 && pkt[6] == 0x12) { + // release the "right" button + LogDebug(LOG_SOURCE_UI, "Right button on Emergency screen"); + } + else if (pkt[4] == 0xF1 && pkt[6] == 0x13) { + // release the "middle" button + LogDebug(LOG_SOURCE_UI, "Middle button on Emergency screen"); + } + } } /** @@ -2722,3 +2761,175 @@ void BMBTTimerScrollDisplay(void *ctx) } } } + +/** + * BMBTEmergencyScreen() + * Description: + * Render Emergency Screen + * Params: + * void *ctx - The context + * Returns: + * void + */ +void BMBTEmergencyScreen(BMBTContext_t *context) +{ + uint8_t msg[50]={IBUS_CMD_GT_WRITE_NO_CURSOR,0xF1,0x00}; + char *msg_body = (char *)(msg+4); + + msg[3]=0x60; + UtilsStrncpy(msg_body, LocaleGetText(LOCALE_STRING_EMERGENCY_POSITION), 50-4); + IBusSendCommand(context->ibus, IBUS_DEVICE_TEL, IBUS_DEVICE_GT, msg, strlen(msg_body)+5); + + msg[3]=0x41; + UtilsStrncpy(msg_body, context->ibus->telematicsLocale, 50-4); + IBusSendCommand(context->ibus, IBUS_DEVICE_TEL, IBUS_DEVICE_GT, msg, strlen(msg_body)+5); + + msg[3]=0x42; + UtilsStrncpy(msg_body, context->ibus->telematicsStreet, 50-4); + IBusSendCommand(context->ibus, IBUS_DEVICE_TEL, IBUS_DEVICE_GT, msg, strlen(msg_body)+5); + + msg[3]=0x44; + UtilsStrncpy(msg_body, context->ibus->telematicsLatitude, 50-4); + IBusSendCommand(context->ibus, IBUS_DEVICE_TEL, IBUS_DEVICE_GT, msg, strlen(msg_body)+5); + + msg[3]=0x45; + UtilsStrncpy(msg_body, context->ibus->telematicsLongtitude, 50-4); + IBusSendCommand(context->ibus, IBUS_DEVICE_TEL, IBUS_DEVICE_GT, msg, strlen(msg_body)+5); + + msg[2]=0x01; //back button + msg[3]=0x50; + msg[4]=0x01; + IBusSendCommand(context->ibus, IBUS_DEVICE_TEL, IBUS_DEVICE_GT, msg, 5); + + msg[0]=IBUS_CMD_GT_WRITE_WITH_CURSOR; + msg[2]=0x00; + msg[3]=0x00; + UtilsStrncpy(msg_body, LocaleGetText(LOCALE_STRING_EMERGENCY_HEADER), 50-4); + IBusSendCommand(context->ibus, IBUS_DEVICE_TEL, IBUS_DEVICE_GT, msg, strlen(msg_body)+5); + context->menu = BMBT_MENU_DIAL_EMERGENCY; +} + + +/** + * BMBTDialScreenUI() + * Description: + * Process UI interaction on Dial screen + * Params: + * void *ctx - The context + * unsigned char - keypress + * unsinged char * - full IBUS packet + * Returns: + * void + */ +//#define BMBT_LAYOUT_TEL_DIAL 0x42 +//#define BMBT_LAYOUT_TEL_DIRECTORY 0x43 +//#define BMBT_LAYOUT_TEL_TOP_8 0x80 +//#define BMBT_LAYOUT_TEL_LIST 0xf0 +//#define BMBT_LAYOUT_TEL_DETAIL 0xf1 + +#define BMBT_FUNCTION_TEL_NULL 0x00 +#define BMBT_FUNCTION_TEL_CONTACT 0x01 +#define BMBT_FUNCTION_TEL_DIGIT 0x02 +#define BMBT_FUNCTION_TEL_SOS 0x05 +#define BMBT_FUNCTION_TEL_NAVIGATION 0x07 +#define BMBT_FUNCTION_TEL_INFO 0x08 + +//#define BMBT_MASK_INDEX 0x1f +//#define BMBT_MASK_CLEAR 0x20 +//#define BMBT_MASK_BUFFER 0x40 +//#define BMBT_MASK_HIGHLIGHT 0x80 + +void BMBTDialScreenUI(void *ctx, uint8_t cmd, uint8_t *pkt) +{ + BMBTContext_t *context = (BMBTContext_t *) ctx; + + uint8_t size = strlen(context->bt->dialBuffer); + uint8_t changed = 0; + + if (pkt[5] == BMBT_FUNCTION_TEL_DIGIT) { + if ((cmd>=0x40)&&(cmd<=0x49)) { + // number released + if (sizebt->dialBuffer[size++]=cmd+'0'-0x40; + context->bt->dialBuffer[size]=0; + changed = 1; + } + } else if (cmd == 0x5A) { + // * released + if (sizebt->dialBuffer[size++]='*'; + context->bt->dialBuffer[size]=0; + changed = 1; + } + } else if (cmd == 0x3A) { + // * hold = + + if (sizebt->dialBuffer[size++]='+'; + context->bt->dialBuffer[size]=0; + changed = 1; + } + } else if (cmd == 0x5B) { + // # released + if (sizebt->dialBuffer[size++]='#'; + context->bt->dialBuffer[size]=0; + changed = 1; + } + } else if (cmd == 0x4A) { + // <- released = delete one char + if (size > 0) { + context->bt->dialBuffer[--size] = 0; + changed = 1; + } + } else if (cmd == 0x2A) { + // -< held = delete all + if (size > 0) { + size = 0; + context->bt->dialBuffer[0] = 0; + changed = 1; + } + } + } else if ((pkt[5] == BMBT_FUNCTION_TEL_SOS) && (cmd == 0x08)) { + // SOS - set buffer to SOS number ( 112, 911, eventually configurable ) + // eventually render full screen with coordinates and button to confirm + // and send also SMS with details + LogDebug(LOG_SOURCE_UI, "Navigate from [BMBT_MENU_DIAL] to [BMBT_DIAL_EMERGENCY]"); + BMBTEmergencyScreen(context); + + } else if ((pkt[5] == BMBT_FUNCTION_TEL_NAVIGATION) && (cmd == 0x1d)) { + // messaging selected + LogDebug(LOG_SOURCE_UI, "Navigate from [BMBT_MENU_DIAL] to [SMS]"); + + } else if ((pkt[5] == BMBT_FUNCTION_TEL_NAVIGATION) && (cmd == 0x1f)) { + // Directory Selected + LogDebug(LOG_SOURCE_UI, "Navigate from [BMBT_MENU_DIAL] to [BMBT_MENU_DIAL_DIRECTORY]"); + + } else if ((pkt[5] == BMBT_FUNCTION_TEL_INFO) && (cmd == 0x0a)) { + // Info Selected + LogDebug(LOG_SOURCE_UI, "Navigate from [BMBT_MENU_DIAL] to [TEL Info]"); + + // Sample messages + // C8 11 3B 24 91 b3 5a 5a 5a 5a 5a 5a # Strength + // C8 06 3B 24 96 "47" # call minutes + // C8 06 3B 24 97 "02" # call seconds + } else { + LogDebug(LOG_SOURCE_UI, "Dial screen command not handled. act=%02x cmd=%02x **", pkt[5], cmd); + } + + // show updated number + if (changed == 1) { + if (size>0) { + char msg[BT_DIAL_BUFFER_FIELD_SIZE+4] = {IBUS_TEL_CMD_NUMBER, 0x63, 0x00}; + snprintf(msg+3,BT_DIAL_BUFFER_FIELD_SIZE-1,"%s",context->bt->dialBuffer); + size+=5; + if (size>BT_DIAL_BUFFER_FIELD_SIZE+4) { + size = BT_DIAL_BUFFER_FIELD_SIZE+4; + } + IBusSendCommand(context->ibus, IBUS_DEVICE_TEL, IBUS_DEVICE_GT, (unsigned char *)msg, size); + } else { + const unsigned char msg2[] = {IBUS_TEL_CMD_NUMBER, 0x61, 0x20}; + IBusSendCommand(context->ibus, IBUS_DEVICE_TEL, IBUS_DEVICE_GT, msg2, sizeof(msg2)); + } + } + +} diff --git a/firmware/application/ui/bmbt.h b/firmware/application/ui/bmbt.h index e83e242b..45e6041c 100644 --- a/firmware/application/ui/bmbt.h +++ b/firmware/application/ui/bmbt.h @@ -39,6 +39,10 @@ #define BMBT_MENU_SETTINGS_COMFORT 7 #define BMBT_MENU_SETTINGS_CALLING 8 #define BMBT_MENU_SETTINGS_UI 9 +#define BMBT_MENU_DIAL 10 +#define BMBT_MENU_DIAL_EMERGENCY 11 +#define BMBT_MENU_DIAL_PHONEBOOK 12 +#define BMBT_MENU_DIAL_INFO 13 #define BMBT_MENU_IDX_BACK 7 #define BMBT_MENU_IDX_DASHBOARD 0 #define BMBT_MENU_IDX_DEVICE_SELECTION 1 @@ -76,7 +80,6 @@ #define BMBT_MENU_IDX_SETTINGS_IU_DASH_OBC 3 #define BMBT_MENU_IDX_SETTINGS_UI_MONITOR_OFF 4 #define BMBT_MENU_IDX_SETTINGS_UI_LANGUAGE 5 - #define BMBT_MENU_BUFFER_OK 0 #define BMBT_MENU_BUFFER_FLUSH 1 @@ -160,4 +163,5 @@ void BMBTIBusVehicleConfig(void *, uint8_t *); void BMBTTimerHeaderWrite(void *); void BMBTTimerMenuWrite(void *); void BMBTTimerScrollDisplay(void *); +void BMBTDialScreenUI(void *, uint8_t, uint8_t *); #endif /* BMBT_H */ diff --git a/firmware/application/ui/cli.c b/firmware/application/ui/cli.c index aefc245a..32695ddc 100644 --- a/firmware/application/ui/cli.c +++ b/firmware/application/ui/cli.c @@ -5,8 +5,10 @@ * Implement a CLI to pass commands to the device */ #include "cli.h" +#include "../lib/bt/bt_bm83_pbap.h" static CLI_t cli; +static uint16_t phonebook_offset = 0; /** * CLIInit() @@ -315,7 +317,21 @@ void CLICommandBTBM83(char **msgBuf, uint8_t *cmdSuccess, uint8_t delimCount) BM83CommandMusicControl(cli.bt, BM83_CMD_ACTION_PAUSE); } else if (UtilsStricmp(msgBuf[1], "RESTORE") == 0) { BM83CommandRestore(cli.bt); - } else { + + // adding some testing for recent calls and phone book + } else if (UtilsStricmp(msgBuf[1], "PB") == 0) { + phonebook_offset = 0; + BM83CommandPBAPPullPhoneBook(cli.bt, PBAP_TYPE_PHONEBOOK, phonebook_offset); + } else if (UtilsStricmp(msgBuf[1], "PBNEXT") == 0) { + phonebook_offset += 8; + BM83CommandPBAPPullPhoneBook(cli.bt, PBAP_TYPE_PHONEBOOK, phonebook_offset); + } else if (UtilsStricmp(msgBuf[1], "CH") == 0) { + BM83CommandPBAPPullPhoneBook(cli.bt, PBAP_TYPE_COMBINED_CALL_HISTORY, 0); + } else if (UtilsStricmp(msgBuf[1], "FAV") == 0) { + BM83CommandPBAPPullPhoneBook(cli.bt, PBAP_TYPE_FAVORITE_CONTACTS, 0); + } + + else { *cmdSuccess = 0; } } @@ -874,6 +890,10 @@ void CLIProcess() LogRaw(" BT PLAY - Send the AVRCP Play Command\r\n"); LogRaw(" BT PAUSE - Send the AVRCP Pause Command\r\n"); LogRaw(" BT RESTORE - Reset the BM83\r\n"); + LogRaw(" BT PB - Get first 8 Phone Book entries (PBAC)\r\n"); + LogRaw(" BT PBNEXT - Get next 8 Phone Book entries (PBAC)\r\n"); + LogRaw(" BT CH - Get Call History / Recent Calls (PBAC)\r\n"); + LogRaw(" BT FAV - Get Favorites / Top-8 (PBAC)\r\n"); } LogRaw(" BT AT command> - Send raw AT command\r\n"); LogRaw(" BT DIAL - Dial a number and display name\r\n");