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)