From 53d49742fa059b083ade800a64e6ceae818c0f6c Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 15 Jun 2021 16:42:23 -0500 Subject: [PATCH] drv/bluetooth: add special location for hub name This modifies the advertising data and device name characteristics to use the hub name from a special place in the firmware flash memory. The offset and size of this location is stored in the firmware metadata json file to allow modifying the hub name before flashing the firmware. The name must be zero-terminated since we are using strlen(), so the max size in the metadata includes the zero-termination byte. Issue: https://github.com/pybricks/support/issues/52 --- CHANGELOG.md | 7 ++++ bricks/stm32/common.ld | 5 +++ bricks/stm32/stm32.mk | 8 ++-- lib/pbio/drv/bluetooth/bluetooth_btstack.c | 37 ++++++++++++++++--- .../drv/bluetooth/bluetooth_stm32_bluenrg.c | 16 +++++--- .../drv/bluetooth/bluetooth_stm32_cc2640.c | 19 ++++++---- lib/pbio/drv/bluetooth/pybricks_service.gatt | 2 +- tools/metadata.py | 24 ++++++++++-- 8 files changed, 91 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc4e84180..d86fe629c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ # Changelog +## [Unreleased] + +### Added +- Added special location in firmware for storing hub name ([support#52]). Note: + Support will need to be added to tools separately to make use of this. + ## [3.0.0] - 2021-06-08 ### Added @@ -26,6 +32,7 @@ Prerelease changes are documented at [support#48]. [issue#21]: https://github.com/pybricks/pybricks-micropython/issues/21 [support#48]: https://github.com/pybricks/support/issues/48 +[support#52]: https://github.com/pybricks/support/issues/52 [support#321]: https://github.com/pybricks/support/issues/321 [support#347]: https://github.com/pybricks/support/issues/347 [support#352]: https://github.com/pybricks/support/issues/352 diff --git a/bricks/stm32/common.ld b/bricks/stm32/common.ld index 33fc9a129..2298b316b 100644 --- a/bricks/stm32/common.ld +++ b/bricks/stm32/common.ld @@ -73,6 +73,11 @@ SECTIONS . = ALIGN(4); } >RAM + .name : + { + *(.name) /* customizable hub name */ + } >FLASH + .user : { . = ALIGN(4); diff --git a/bricks/stm32/stm32.mk b/bricks/stm32/stm32.mk index 455aa2fc5..a32ac8f0a 100644 --- a/bricks/stm32/stm32.mk +++ b/bricks/stm32/stm32.mk @@ -587,7 +587,7 @@ $(BUILD)/firmware-no-checksum.elf: $(LD_FILES) $(OBJ) # firmware blob used to calculate checksum $(BUILD)/firmware-no-checksum.bin: $(BUILD)/firmware-no-checksum.elf - $(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data -j .user -j .checksum $^ $@ + $(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data -j .name -j .user -j .checksum $^ $@ $(BUILD)/firmware.elf: $(BUILD)/firmware-no-checksum.bin $(OBJ) $(ECHO) "RELINK $@" @@ -596,13 +596,13 @@ $(BUILD)/firmware.elf: $(BUILD)/firmware-no-checksum.bin $(OBJ) # firmware blob with main.mpy and checksum appended - can be flashed to hub $(BUILD)/firmware.bin: $(BUILD)/firmware.elf $(ECHO) "BIN creating firmware file" - $(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data -j .user -j .checksum $^ $@ + $(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data -j .name -j .user -j .checksum $^ $@ $(ECHO) "`wc -c < $@` bytes" # firmware blob without main.mpy or checksum - use as base for appending other .mpy $(BUILD)/firmware-base.bin: $(BUILD)/firmware-no-checksum.elf $(ECHO) "BIN creating firmware base file" - $(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data $^ $@ + $(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data -j .name $^ $@ $(ECHO) "`wc -c < $@` bytes" # firmware blob with different starting flash memory address for dual booting @@ -614,7 +614,7 @@ $(BUILD)/firmware-dual-boot-base.elf: $(LD_FILES) $(OBJ) $(DUAL_BOOT_OBJ) # firmware blob without main.mpy or checksum - use as base for appending other .mpy $(BUILD)/firmware-dual-boot-base.bin: $(BUILD)/firmware-dual-boot-base.elf $(ECHO) "BIN creating dual-boot firmware base file" - $(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data $^ $@ + $(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data -j .name $^ $@ $(ECHO) "`wc -c < $@` bytes" # firmware blob without main.mpy or checksum - use as base for appending other .mpy diff --git a/lib/pbio/drv/bluetooth/bluetooth_btstack.c b/lib/pbio/drv/bluetooth/bluetooth_btstack.c index d6b096751..299cdd48f 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_btstack.c +++ b/lib/pbio/drv/bluetooth/bluetooth_btstack.c @@ -31,7 +31,9 @@ #define HUB_VARIANT 0x0000 #endif -#define HUB_NAME "Pybricks Hub" +// hub name goes in special section so that it can be modified when flashing firmware +__attribute__((section(".name"))) +char pbdrv_bluetooth_hub_name[16] = "Pybricks Hub"; static hci_con_handle_t le_con_handle = HCI_CON_HANDLE_INVALID; static hci_con_handle_t pybricks_con_handle = HCI_CON_HANDLE_INVALID; @@ -134,6 +136,26 @@ static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packe } } +// ATT Client Read Callback for Dynamic Data +// - if buffer == NULL, don't copy data, just return size of value +// - if buffer != NULL, copy data and return number bytes copied +// @param offset defines start of attribute value +static uint16_t att_read_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) { + uint16_t att_value_len; + + switch (attribute_handle) { + case ATT_CHARACTERISTIC_GAP_DEVICE_NAME_01_VALUE_HANDLE: + att_value_len = strlen(pbdrv_bluetooth_hub_name); + if (buffer) { + memcpy(buffer, pbdrv_bluetooth_hub_name, att_value_len); + } + return att_value_len; + + default: + return 0; + } +} + void pbdrv_bluetooth_init(void) { static btstack_packet_callback_registration_t hci_event_callback_registration; @@ -161,7 +183,7 @@ void pbdrv_bluetooth_init(void) { sm_set_ir((uint8_t *)pdata->ir_key); // setup ATT server - att_server_init(profile_data, NULL, NULL); + att_server_init(profile_data, att_read_callback, NULL); device_information_service_server_init(); device_information_service_server_set_firmware_revision(PBIO_VERSION_STR); @@ -207,12 +229,17 @@ static void init_advertising_data(void) { // 0x00XX - Product ID Field - hub kind // 0x00XX - Product Version Field - product variant 0x50, 0x2a, 0x01, 0x97, 0x03, HUB_KIND, 0x00, 0x00, 0x00, - sizeof(HUB_NAME), BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME, + 0, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME, }; scan_resp_data[9] = HUB_VARIANT; - memcpy(&scan_resp_data[13], HUB_NAME, sizeof(HUB_NAME) - 1); - gap_scan_response_set_data(13 + sizeof(HUB_NAME) - 1, scan_resp_data); + + uint8_t hub_name_len = strlen(pbdrv_bluetooth_hub_name); + scan_resp_data[11] = hub_name_len + 1; + memcpy(&scan_resp_data[13], pbdrv_bluetooth_hub_name, hub_name_len); + _Static_assert(13 + sizeof(pbdrv_bluetooth_hub_name) - 1 <= 31, "scan response is 31 octet max"); + + gap_scan_response_set_data(13 + hub_name_len, scan_resp_data); } void pbdrv_bluetooth_start_advertising(void) { diff --git a/lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c b/lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c index df8221f4d..e5efe122d 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c +++ b/lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c @@ -30,8 +30,9 @@ #include #include -// name used for standard GAP device name characteristic -#define DEV_NAME "Pybricks Hub" +// hub name goes in special section so that it can be modified when flashing firmware +__attribute__((section(".name"))) +char pbdrv_bluetooth_hub_name[16] = "Pybricks Hub"; // used to identify which hub - Device Information Service (DIS). // 0x2A50 - service UUID - PnP ID characteristic UUID @@ -240,10 +241,13 @@ static PT_THREAD(set_discoverable(struct pt *pt, void *context)) { response_data[0] = sizeof(PNP_ID); response_data[1] = AD_TYPE_SERVICE_DATA; memcpy(&response_data[2], PNP_ID, sizeof(PNP_ID) - 1); - response_data[11] = sizeof(DEV_NAME); + uint8_t hub_name_len = strlen(pbdrv_bluetooth_hub_name); + response_data[11] = hub_name_len + 1; response_data[12] = AD_TYPE_COMPLETE_LOCAL_NAME; - memcpy(&response_data[13], DEV_NAME, sizeof(DEV_NAME) - 1); - hci_le_set_scan_response_data_begin(sizeof(response_data), response_data); + memcpy(&response_data[13], pbdrv_bluetooth_hub_name, hub_name_len); + _Static_assert(13 + sizeof(pbdrv_bluetooth_hub_name) - 1 <= 31, "scan response is 31 octet max"); + + hci_le_set_scan_response_data_begin(13 + hub_name_len, response_data); PT_WAIT_UNTIL(pt, hci_command_complete); hci_le_set_scan_response_data_end(); @@ -708,7 +712,7 @@ static PT_THREAD(hci_init(struct pt *pt)) { PT_WAIT_WHILE(pt, write_xfer_size); aci_gatt_update_char_value_begin(gap_service_handle, gap_dev_name_char_handle, - 0, strlen(DEV_NAME), DEV_NAME); + 0, strlen(pbdrv_bluetooth_hub_name), pbdrv_bluetooth_hub_name); PT_WAIT_UNTIL(pt, hci_command_complete); aci_gatt_update_char_value_end(); diff --git a/lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c b/lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c index 277d42e67..890da10c9 100644 --- a/lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c +++ b/lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c @@ -60,8 +60,9 @@ #define DBG(...) #endif -// name used for standard GAP device name characteristic -#define DEV_NAME "Pybricks Hub" +// hub name goes in special section so that it can be modified when flashing firmware +__attribute__((section(".name"))) +char pbdrv_bluetooth_hub_name[16] = "Pybricks Hub"; // used to identify which hub - Device Information Service (DIS). // 0x2A50 - service UUID - PnP ID characteristic UUID @@ -292,10 +293,13 @@ static PT_THREAD(set_discoverable(struct pt *pt, void *context)) { data[0] = sizeof(PNP_ID); // same as 1 + strlen(PNP_ID) data[1] = GAP_ADTYPE_SERVICE_DATA; memcpy(&data[2], PNP_ID, sizeof(PNP_ID)); - data[11] = sizeof(DEV_NAME); // same as 1 + strlen(DEV_NAME) + uint8_t hub_name_len = strlen(pbdrv_bluetooth_hub_name); + data[11] = hub_name_len + 1; data[12] = GAP_ADTYPE_LOCAL_NAME_COMPLETE; - memcpy(&data[13], DEV_NAME, sizeof(DEV_NAME)); - GAP_updateAdvertistigData(GAP_AD_TYPE_SCAN_RSP_DATA, sizeof(PNP_ID) + sizeof(DEV_NAME) + 2, data); + memcpy(&data[13], pbdrv_bluetooth_hub_name, hub_name_len); + _Static_assert(13 + sizeof(pbdrv_bluetooth_hub_name) - 1 <= 31, "scan response is 31 octet max"); + + GAP_updateAdvertistigData(GAP_AD_TYPE_SCAN_RSP_DATA, 13 + hub_name_len, data); PT_WAIT_UNTIL(pt, hci_command_complete); // ignoring response data @@ -546,8 +550,9 @@ static void handle_event(uint8_t *packet) { attReadRsp_t rsp; uint8_t buf[ATT_MTU_SIZE - 1]; - memcpy(&buf[0], DEV_NAME, sizeof(DEV_NAME)); - rsp.len = sizeof(DEV_NAME) - 1; + uint8_t hub_name_len = strlen(pbdrv_bluetooth_hub_name); + memcpy(&buf[0], pbdrv_bluetooth_hub_name, hub_name_len); + rsp.len = hub_name_len; rsp.pValue = buf; ATT_ReadRsp(connection_handle, &rsp); } else if (handle == gap_service_handle + 4) { diff --git a/lib/pbio/drv/bluetooth/pybricks_service.gatt b/lib/pbio/drv/bluetooth/pybricks_service.gatt index 49b921896..78a4109e8 100644 --- a/lib/pbio/drv/bluetooth/pybricks_service.gatt +++ b/lib/pbio/drv/bluetooth/pybricks_service.gatt @@ -1,5 +1,5 @@ PRIMARY_SERVICE, GAP_SERVICE -CHARACTERISTIC, GAP_DEVICE_NAME, READ, "Pybricks Hub" +CHARACTERISTIC, GAP_DEVICE_NAME, DYNAMIC | READ, // not importing device_information_service.gatt since we are not using all optional characteristics PRIMARY_SERVICE, ORG_BLUETOOTH_SERVICE_DEVICE_INFORMATION diff --git a/tools/metadata.py b/tools/metadata.py index 0abf0c4dd..495667394 100755 --- a/tools/metadata.py +++ b/tools/metadata.py @@ -1,15 +1,15 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: MIT -# Copyright (c) 2019-2020 The Pybricks Authors +# Copyright (c) 2019-2021 The Pybricks Authors """ Pybricks firmware metadata file generation tool. Generates a .json file with information about a Pybricks firmware binary blob. -v1.0.0: +v1.1.0: - metadata-version "1.0.0" + metadata-version "1.1.0" firmware-version output of `git describe --tags --dirty` device-id one of 0x40, 0x41, 0x80, 0x81 checksum-type one of "sum", "crc32" @@ -17,6 +17,8 @@ mpy-cross-options array of string user-mpy-offset number max-firmware-size number + hub-name-offset number [v1.1.0] + max-hub-name-size number [v1.1.0] """ import argparse @@ -36,7 +38,7 @@ # metadata file format version -VERSION = "1.0.0" +VERSION = "1.1.0" # hub-specific info HUB_INFO = { @@ -69,6 +71,8 @@ def generate( flash_origin = None # Starting address of firmware area in flash memory flash_length = None # Size of firmware area of flash memory + name_start = None # Starting address of custom hub name + name_size = None # Size reserved for custom hub name user_start = None # Starting address of user .mpy file for line in map_file.readlines(): @@ -78,6 +82,12 @@ def generate( flash_length = int(match[2], base=0) continue + match = re.match(r"^\.name\s+(0x[0-9A-Fa-f]{8,16})\s+(0x[0-9A-Fa-f]+)", line) + if match: + name_start = int(match[1], base=0) + name_size = int(match[2], base=0) + continue + match = re.match(r"^\.user\s+(0x[0-9A-Fa-f]{8,16})", line) if match: user_start = int(match[1], base=0) @@ -91,12 +101,18 @@ def generate( print("Failed to find 'FLASH' length", file=sys.stderr) exit(1) + if name_start is None: + print("Failed to find '.name' start address", file=sys.stderr) + exit(1) + if user_start is None: print("Failed to find '.user' start address", file=sys.stderr) exit(1) metadata["user-mpy-offset"] = user_start - flash_origin metadata["max-firmware-size"] = flash_length + metadata["hub-name-offset"] = name_start - flash_origin + metadata["max-hub-name-size"] = name_size json.dump(metadata, out_file, indent=4, sort_keys=True)