|
3 | 3 | # |
4 | 4 | # SPDX-License-Identifier: LicenseRef-BSD-5-Clause-Nordic |
5 | 5 |
|
6 | | -import os |
7 | 6 | import sys |
8 | 7 | import hid |
9 | 8 | import struct |
10 | 9 | import time |
11 | | -import zlib |
12 | 10 | import random |
13 | 11 | import re |
14 | 12 |
|
15 | 13 | import logging |
16 | 14 | import collections |
17 | | -import imgtool.image as img |
18 | 15 |
|
19 | 16 | from enum import IntEnum |
20 | 17 |
|
|
49 | 46 | BLE_BOND_PEER_ERASE = 0x0 |
50 | 47 | BLE_BOND_PEER_SEARCH = 0x1 |
51 | 48 |
|
52 | | -DFU_START = 0x0 |
53 | | -DFU_DATA = 0x1 |
54 | | -DFU_SYNC = 0x2 |
55 | | -DFU_REBOOT = 0x3 |
56 | | -DFU_IMGINFO = 0x4 |
57 | | - |
58 | 49 | LED_STREAM_DATA = 0x0 |
59 | 50 |
|
60 | | -FLASH_PAGE_SIZE = 4096 |
61 | | - |
62 | 51 | POLL_INTERVAL_DEFAULT = 0.02 |
63 | 52 | POLL_RETRY_COUNT = 200 |
64 | 53 |
|
65 | | -DFU_SYNC_RETRIES = 3 |
66 | | -DFU_SYNC_INTERVAL = 1 |
67 | 54 |
|
68 | 55 | PMW3360_OPTIONS = { |
69 | 56 | 'downshift_run': ConfigOption((10, 2550), SENSOR_OPT_DOWNSHIFT_RUN, 'Run to Rest 1 switch time [ms]', int), |
@@ -265,28 +252,6 @@ def parse_response(response_raw): |
265 | 252 |
|
266 | 253 | return Response(rcpt, event_id, status, event_data) |
267 | 254 |
|
268 | | -class FwInfo: |
269 | | - def __init__(self, fetched_data): |
270 | | - fmt = '<BIBBHI' |
271 | | - assert struct.calcsize(fmt) <= EVENT_DATA_LEN_MAX |
272 | | - vals = struct.unpack(fmt, fetched_data) |
273 | | - flash_area_id, image_len, ver_major, ver_minor, ver_rev, ver_build_nr = vals |
274 | | - self.flash_area_id = flash_area_id |
275 | | - self.image_len = image_len |
276 | | - self.ver_major = ver_major |
277 | | - self.ver_minor = ver_minor |
278 | | - self.ver_rev = ver_rev |
279 | | - self.ver_build_nr = ver_build_nr |
280 | | - |
281 | | - def get_fw_version(self): |
282 | | - return (self.ver_major, self.ver_minor, self.ver_rev, self.ver_build_nr) |
283 | | - |
284 | | - def __str__(self): |
285 | | - return ('Firmware info\n' |
286 | | - ' FLASH area id: {}\n' |
287 | | - ' Image length: {}\n' |
288 | | - ' Version: {}.{}.{}.{}').format(self.flash_area_id, self.image_len, self.ver_major, self.ver_minor, |
289 | | - self.ver_rev, self.ver_build_nr) |
290 | 255 |
|
291 | 256 | class ConfigParser: |
292 | 257 | """ Class used to simplify "read-modify-write" handling of parameter structs. |
@@ -460,6 +425,7 @@ def get_option_format(device_type, module_id): |
460 | 425 |
|
461 | 426 | return format |
462 | 427 |
|
| 428 | + |
463 | 429 | def open_device(device_type): |
464 | 430 | dev = None |
465 | 431 |
|
@@ -488,30 +454,6 @@ def open_device(device_type): |
488 | 454 | return dev |
489 | 455 |
|
490 | 456 |
|
491 | | -def fwinfo(dev, recipient): |
492 | | - event_id = (EVENT_GROUP_DFU << GROUP_FIELD_POS) | (DFU_IMGINFO << TYPE_FIELD_POS) |
493 | | - |
494 | | - success, fetched_data = exchange_feature_report(dev, recipient, event_id, None, True) |
495 | | - |
496 | | - if success and fetched_data: |
497 | | - fw_info = FwInfo(fetched_data) |
498 | | - return fw_info |
499 | | - else: |
500 | | - return None |
501 | | - |
502 | | - |
503 | | -def fwreboot(dev, recipient): |
504 | | - event_id = (EVENT_GROUP_DFU << GROUP_FIELD_POS) | (DFU_REBOOT << TYPE_FIELD_POS) |
505 | | - success, _ = exchange_feature_report(dev, recipient, event_id, None, True) |
506 | | - |
507 | | - if success: |
508 | | - logging.debug('Firmware rebooted') |
509 | | - else: |
510 | | - logging.debug('FW reboot request failed') |
511 | | - |
512 | | - return success |
513 | | - |
514 | | - |
515 | 457 | def change_config(dev, recipient, config_name, config_value, device_options, module_id): |
516 | 458 | config_opts = device_options[config_name] |
517 | 459 | opt_id = config_opts.event_id |
@@ -573,236 +515,6 @@ def fetch_config(dev, recipient, config_name, device_options, module_id): |
573 | 515 | else: |
574 | 516 | return success, ConfigParser(fetched_data, *format[opt_id]).config_get(config_name) |
575 | 517 |
|
576 | | -def dfu_sync(dev, recipient): |
577 | | - event_id = (EVENT_GROUP_DFU << GROUP_FIELD_POS) | (DFU_SYNC << TYPE_FIELD_POS) |
578 | | - |
579 | | - success, fetched_data = exchange_feature_report(dev, recipient, event_id, None, True) |
580 | | - |
581 | | - if not success: |
582 | | - return None |
583 | | - |
584 | | - fmt = '<BIII' |
585 | | - assert struct.calcsize(fmt) <= EVENT_DATA_LEN_MAX |
586 | | - |
587 | | - if (fetched_data is None) or (len(fetched_data) < struct.calcsize(fmt)): |
588 | | - return None |
589 | | - |
590 | | - return struct.unpack(fmt, fetched_data) |
591 | | - |
592 | | - |
593 | | -def dfu_start(dev, recipient, img_length, img_csum, offset): |
594 | | - # Start DFU operation at selected offset. |
595 | | - # It can happen that device will reject this request - this will be |
596 | | - # verified by dfu sync at data exchange. |
597 | | - event_id = (EVENT_GROUP_DFU << GROUP_FIELD_POS) | (DFU_START << TYPE_FIELD_POS) |
598 | | - |
599 | | - event_data = struct.pack('<III', img_length, img_csum, offset) |
600 | | - |
601 | | - success = exchange_feature_report(dev, recipient, event_id, event_data, False) |
602 | | - |
603 | | - if success: |
604 | | - logging.debug('DFU started') |
605 | | - else: |
606 | | - logging.debug('DFU start failed') |
607 | | - |
608 | | - return success |
609 | | - |
610 | | - |
611 | | -def file_crc(dfu_image): |
612 | | - crc32 = 1 |
613 | | - |
614 | | - try: |
615 | | - img_file = open(dfu_image, 'rb') |
616 | | - except FileNotFoundError: |
617 | | - print("Wrong file or file path") |
618 | | - return None |
619 | | - |
620 | | - while True: |
621 | | - chunk_data = img_file.read(512) |
622 | | - if len(chunk_data) == 0: |
623 | | - break |
624 | | - crc32 = zlib.crc32(chunk_data, crc32) |
625 | | - |
626 | | - img_file.close() |
627 | | - |
628 | | - return crc32 |
629 | | - |
630 | | - |
631 | | -def dfu_sync_wait(dev, recipient, is_active): |
632 | | - if is_active: |
633 | | - dfu_state = 0x01 |
634 | | - else: |
635 | | - dfu_state = 0x00 |
636 | | - |
637 | | - for _ in range(DFU_SYNC_RETRIES): |
638 | | - dfu_info = dfu_sync(dev, recipient) |
639 | | - |
640 | | - if dfu_info is not None: |
641 | | - if dfu_info[0] != dfu_state: |
642 | | - # DFU may be transiting its state. This can happen when previous |
643 | | - # interrupted DFU operation is timing out. Sleep to allow it |
644 | | - # to settle the state. |
645 | | - time.sleep(DFU_SYNC_INTERVAL) |
646 | | - else: |
647 | | - break |
648 | | - |
649 | | - return dfu_info |
650 | | - |
651 | | - |
652 | | -def dfu_transfer(dev, recipient, dfu_image, progress_callback): |
653 | | - img_length = os.stat(dfu_image).st_size |
654 | | - dfu_info = dfu_sync_wait(dev, recipient, False) |
655 | | - |
656 | | - if not is_dfu_image_correct(dfu_image): |
657 | | - return False |
658 | | - |
659 | | - if is_dfu_operation_pending(dfu_info): |
660 | | - return False |
661 | | - |
662 | | - img_csum = file_crc(dfu_image) |
663 | | - |
664 | | - if not img_csum: |
665 | | - return False |
666 | | - |
667 | | - offset = get_dfu_operation_offset(dfu_image, dfu_info, img_csum) |
668 | | - |
669 | | - success = dfu_start(dev, recipient, img_length, img_csum, offset) |
670 | | - |
671 | | - if not success: |
672 | | - print('Cannot start DFU operation') |
673 | | - return False |
674 | | - |
675 | | - img_file = open(dfu_image, 'rb') |
676 | | - img_file.seek(offset) |
677 | | - |
678 | | - try: |
679 | | - offset, success = send_chunk(dev, img_csum, img_file, img_length, offset, recipient, success, progress_callback) |
680 | | - except Exception: |
681 | | - success = False |
682 | | - |
683 | | - img_file.close() |
684 | | - print('') |
685 | | - |
686 | | - if success: |
687 | | - print('DFU transfer completed') |
688 | | - success = False |
689 | | - |
690 | | - dfu_info = dfu_sync_wait(dev, recipient, False) |
691 | | - |
692 | | - if dfu_info is None: |
693 | | - print('Lost communication with the device') |
694 | | - else: |
695 | | - if dfu_info[0] != 0: |
696 | | - print('Device holds DFU active') |
697 | | - elif dfu_info[3] != offset: |
698 | | - print('Device holds incorrect image offset') |
699 | | - else: |
700 | | - success = True |
701 | | - return success |
702 | | - |
703 | | - |
704 | | -def send_chunk(dev, img_csum, img_file, img_length, offset, recipient, success, progress_callback): |
705 | | - event_id = (EVENT_GROUP_DFU << GROUP_FIELD_POS) | (DFU_DATA << TYPE_FIELD_POS) |
706 | | - |
707 | | - while offset < img_length: |
708 | | - if offset % FLASH_PAGE_SIZE == 0: |
709 | | - # Sync DFU state at regular intervals to ensure everything |
710 | | - # is all right. |
711 | | - success = False |
712 | | - dfu_info = dfu_sync(dev, recipient) |
713 | | - |
714 | | - if dfu_info is None: |
715 | | - print('Lost communication with the device') |
716 | | - break |
717 | | - if dfu_info[0] == 0: |
718 | | - print('DFU interrupted by device') |
719 | | - break |
720 | | - if (dfu_info[1] != img_length) or (dfu_info[2] != img_csum) or (dfu_info[3] != offset): |
721 | | - print('Invalid sync information') |
722 | | - break |
723 | | - |
724 | | - chunk_data = img_file.read(EVENT_DATA_LEN_MAX) |
725 | | - chunk_len = len(chunk_data) |
726 | | - |
727 | | - if chunk_len == 0: |
728 | | - break |
729 | | - |
730 | | - logging.debug('Send DFU request: offset {}, size {}'.format(offset, chunk_len)) |
731 | | - |
732 | | - progress_callback(int(offset / img_length * 1000)) |
733 | | - |
734 | | - success = exchange_feature_report(dev, recipient, event_id, chunk_data, False) |
735 | | - |
736 | | - if not success: |
737 | | - print('Lost communication with the device') |
738 | | - break |
739 | | - |
740 | | - offset += chunk_len |
741 | | - |
742 | | - return offset, success |
743 | | - |
744 | | - |
745 | | -def get_dfu_operation_offset(dfu_image, dfu_info, img_csum): |
746 | | - # Check if the previously interrupted DFU operation can be resumed. |
747 | | - img_length = os.stat(dfu_image).st_size |
748 | | - |
749 | | - if not img_csum: |
750 | | - return None |
751 | | - |
752 | | - if (dfu_info[1] == img_length) and (dfu_info[2] == img_csum) and (dfu_info[3] <= img_length): |
753 | | - print('Resume DFU at {}'.format(dfu_info[3])) |
754 | | - offset = dfu_info[3] |
755 | | - else: |
756 | | - offset = 0 |
757 | | - |
758 | | - return offset |
759 | | - |
760 | | - |
761 | | -def is_dfu_operation_pending(dfu_info): |
762 | | - # Check there is no other DFU operation. |
763 | | - if dfu_info is None: |
764 | | - print('Cannot start DFU, device not responding') |
765 | | - return True |
766 | | - |
767 | | - if dfu_info[0] != 0: |
768 | | - print('Cannot start DFU. DFU in progress or memory is not clean.') |
769 | | - print('Please stop ongoing DFU and wait until mouse cleans memory.') |
770 | | - return True |
771 | | - |
772 | | - return False |
773 | | - |
774 | | - |
775 | | -def is_dfu_image_correct(dfu_image): |
776 | | - if not os.path.isfile(dfu_image): |
777 | | - print('DFU image file does not exists') |
778 | | - return False |
779 | | - |
780 | | - img_length = os.stat(dfu_image).st_size |
781 | | - |
782 | | - if img_length <= 0: |
783 | | - print('DFU image is empty') |
784 | | - return False |
785 | | - |
786 | | - print('DFU image size: {} bytes'.format(img_length)) |
787 | | - |
788 | | - res, _ = img.Image.verify(dfu_image, None) |
789 | | - |
790 | | - if res != img.VerifyResult.OK: |
791 | | - print('DFU image is invalid') |
792 | | - return False |
793 | | - |
794 | | - return True |
795 | | - |
796 | | - |
797 | | -def get_dfu_image_version(dfu_image): |
798 | | - res, ver = img.Image.verify(dfu_image, None) |
799 | | - |
800 | | - if res != img.VerifyResult.OK: |
801 | | - print('Image in file is invalid') |
802 | | - return None |
803 | | - |
804 | | - return ver |
805 | | - |
806 | 518 |
|
807 | 519 | class Step: |
808 | 520 | def __init__(self, r, g, b, substep_count, substep_time): |
|
0 commit comments