Skip to content

Commit 545c7e6

Browse files
MarekPietapdunaj
authored andcommitted
scripts: hid_configurator: Move DFU to separate file
Change moves DFU related code to separate file. Jira:DESK-593 Signed-off-by: Marek Pieta <[email protected]>
1 parent d89be11 commit 545c7e6

File tree

3 files changed

+309
-291
lines changed

3 files changed

+309
-291
lines changed

scripts/hid_configurator/configurator_cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
from configurator_core import DEVICE
1010
from configurator_core import get_device_pid, open_device
11-
from configurator_core import fwinfo, fwreboot, change_config, fetch_config
12-
from configurator_core import dfu_transfer, get_dfu_image_version
11+
from configurator_core import change_config, fetch_config
12+
from modules.dfu import fwinfo, fwreboot, dfu_transfer, get_dfu_image_version
1313
from led_stream import send_continuous_led_stream, send_music_led_stream
1414

1515

scripts/hid_configurator/configurator_core.py

Lines changed: 1 addition & 289 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,15 @@
33
#
44
# SPDX-License-Identifier: LicenseRef-BSD-5-Clause-Nordic
55

6-
import os
76
import sys
87
import hid
98
import struct
109
import time
11-
import zlib
1210
import random
1311
import re
1412

1513
import logging
1614
import collections
17-
import imgtool.image as img
1815

1916
from enum import IntEnum
2017

@@ -49,21 +46,11 @@
4946
BLE_BOND_PEER_ERASE = 0x0
5047
BLE_BOND_PEER_SEARCH = 0x1
5148

52-
DFU_START = 0x0
53-
DFU_DATA = 0x1
54-
DFU_SYNC = 0x2
55-
DFU_REBOOT = 0x3
56-
DFU_IMGINFO = 0x4
57-
5849
LED_STREAM_DATA = 0x0
5950

60-
FLASH_PAGE_SIZE = 4096
61-
6251
POLL_INTERVAL_DEFAULT = 0.02
6352
POLL_RETRY_COUNT = 200
6453

65-
DFU_SYNC_RETRIES = 3
66-
DFU_SYNC_INTERVAL = 1
6754

6855
PMW3360_OPTIONS = {
6956
'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):
265252

266253
return Response(rcpt, event_id, status, event_data)
267254

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)
290255

291256
class ConfigParser:
292257
""" Class used to simplify "read-modify-write" handling of parameter structs.
@@ -460,6 +425,7 @@ def get_option_format(device_type, module_id):
460425

461426
return format
462427

428+
463429
def open_device(device_type):
464430
dev = None
465431

@@ -488,30 +454,6 @@ def open_device(device_type):
488454
return dev
489455

490456

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-
515457
def change_config(dev, recipient, config_name, config_value, device_options, module_id):
516458
config_opts = device_options[config_name]
517459
opt_id = config_opts.event_id
@@ -573,236 +515,6 @@ def fetch_config(dev, recipient, config_name, device_options, module_id):
573515
else:
574516
return success, ConfigParser(fetched_data, *format[opt_id]).config_get(config_name)
575517

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-
806518

807519
class Step:
808520
def __init__(self, r, g, b, substep_count, substep_time):

0 commit comments

Comments
 (0)