Skip to content

Commit 6d9c9bd

Browse files
MarekPietapdunaj
authored andcommitted
scripts: hid_configurator: Move module configuration to separate files
Change moves module configuration related code to separate files. Jira:DESK-593 Signed-off-by: Marek Pieta <[email protected]>
1 parent ce3e8a5 commit 6d9c9bd

File tree

4 files changed

+320
-299
lines changed

4 files changed

+320
-299
lines changed

scripts/hid_configurator/configurator_cli.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
import argparse
77
import logging
88

9-
from configurator_core import DEVICE
10-
from configurator_core import get_device_pid, open_device
11-
from configurator_core import change_config, fetch_config
9+
from configurator_core import open_device
10+
from devices import DEVICE, get_device_pid
11+
from modules.config import change_config, fetch_config
1212
from modules.dfu import fwinfo, fwreboot, dfu_transfer, get_dfu_image_version
1313
from modules.led_stream import send_continuous_led_stream
1414
from modules.music_led_stream import send_music_led_stream

scripts/hid_configurator/configurator_core.py

Lines changed: 5 additions & 296 deletions
Original file line numberDiff line numberDiff line change
@@ -7,185 +7,27 @@
77
import hid
88
import struct
99
import time
10-
import re
10+
from enum import IntEnum
1111

1212
import logging
13-
import collections
14-
15-
from enum import IntEnum
1613

17-
ConfigOption = collections.namedtuple('ConfigOption', 'range event_id help type')
14+
from devices import get_device_pid, get_device_vid, get_device_type
1815

1916
REPORT_ID = 6
2017
REPORT_SIZE = 30
2118
EVENT_DATA_LEN_MAX = REPORT_SIZE - 6
2219

20+
POLL_INTERVAL_DEFAULT = 0.02
21+
POLL_RETRY_COUNT = 200
22+
2323
TYPE_FIELD_POS = 0
2424
GROUP_FIELD_POS = 6
2525
EVENT_GROUP_SETUP = 0x1
2626
EVENT_GROUP_DFU = 0x2
2727
EVENT_GROUP_LED_STREAM = 0x3
2828

2929
MOD_FIELD_POS = 3
30-
SETUP_MODULE_SENSOR = 0x1
31-
SETUP_MODULE_QOS = 0x2
32-
SETUP_MODULE_BLE_BOND = 0x3
33-
3430
OPT_FIELD_POS = 0
35-
SENSOR_OPT_CPI = 0x0
36-
SENSOR_OPT_DOWNSHIFT_RUN = 0x1
37-
SENSOR_OPT_DOWNSHIFT_REST1 = 0x2
38-
SENSOR_OPT_DOWNSHIFT_REST2 = 0x3
39-
40-
QOS_OPT_BLACKLIST = 0x0
41-
QOS_OPT_CHMAP = 0x1
42-
QOS_OPT_PARAM_BLE = 0x2
43-
QOS_OPT_PARAM_WIFI = 0x3
44-
45-
BLE_BOND_PEER_ERASE = 0x0
46-
BLE_BOND_PEER_SEARCH = 0x1
47-
48-
POLL_INTERVAL_DEFAULT = 0.02
49-
POLL_RETRY_COUNT = 200
50-
51-
52-
PMW3360_OPTIONS = {
53-
'downshift_run': ConfigOption((10, 2550), SENSOR_OPT_DOWNSHIFT_RUN, 'Run to Rest 1 switch time [ms]', int),
54-
'downshift_rest1': ConfigOption((320, 81600), SENSOR_OPT_DOWNSHIFT_REST1, 'Rest 1 to Rest 2 switch time [ms]', int),
55-
'downshift_rest2': ConfigOption((3200, 816000), SENSOR_OPT_DOWNSHIFT_REST2, 'Rest 2 to Rest 3 switch time [ms]', int),
56-
'cpi': ConfigOption((100, 12000), SENSOR_OPT_CPI, 'CPI resolution', int),
57-
}
58-
59-
PAW3212_OPTIONS = {
60-
'sleep1_timeout': ConfigOption((32, 512), SENSOR_OPT_DOWNSHIFT_RUN, 'Sleep 1 switch time [ms]', int),
61-
'sleep2_timeout': ConfigOption((20480, 327680), SENSOR_OPT_DOWNSHIFT_REST1, 'Sleep 2 switch time [ms]', int),
62-
'sleep3_timeout': ConfigOption((20480, 327680), SENSOR_OPT_DOWNSHIFT_REST2, 'Sleep 3 switch time [ms]', int),
63-
'cpi': ConfigOption((0, 2394), SENSOR_OPT_CPI, 'CPI resolution', int),
64-
}
65-
66-
QOS_OPTIONS = {
67-
'sample_count_min': ConfigOption((0, 65535), QOS_OPT_PARAM_BLE, 'Minimum number of samples needed for channel map processing', int),
68-
'min_channel_count': ConfigOption((2, 37), QOS_OPT_PARAM_BLE, 'Minimum BLE channel count', int),
69-
'weight_crc_ok': ConfigOption((-32767, 32767), QOS_OPT_PARAM_BLE, 'Weight of CRC OK [Fixed point with 1/100 scaling]', int),
70-
'weight_crc_error': ConfigOption((-32767, 32767), QOS_OPT_PARAM_BLE, 'Weight of CRC ERROR [Fixed point with 1/100 scaling]', int),
71-
'ble_block_threshold': ConfigOption((1, 65535), QOS_OPT_PARAM_BLE, 'Threshold relative to average rating for blocking BLE channels [Fixed point with 1/100 scaling]', int),
72-
'eval_max_count': ConfigOption((1, 37), QOS_OPT_PARAM_BLE, 'Maximum number of blocked channels that can be evaluated', int),
73-
'eval_duration': ConfigOption((1, 65535), QOS_OPT_PARAM_BLE, 'Duration of channel evaluation [seconds]', int),
74-
'eval_keepout_duration': ConfigOption((1, 65535), QOS_OPT_PARAM_BLE, 'Duration that a channel will be blocked before considered for re-evaluation [seconds]', int),
75-
'eval_success_threshold': ConfigOption((1, 65535), QOS_OPT_PARAM_BLE, 'Threshold relative to average rating for approving blocked BLE channel under evaluation [Fixed point with 1/100 scaling]', int),
76-
'wifi_rating_inc': ConfigOption((1, 65535), QOS_OPT_PARAM_WIFI, 'Wifi strength rating multiplier. Increase value to block wifi faster [Fixed point with 1/100 scaling]', int),
77-
'wifi_present_threshold': ConfigOption((1, 65535), QOS_OPT_PARAM_WIFI, 'Threshold relative to average rating for considering a wifi present [Fixed point with 1/100 scaling]', int),
78-
'wifi_active_threshold': ConfigOption((1, 65535), QOS_OPT_PARAM_WIFI, 'Threshold relative to average rating for considering a wifi active(blockable) [Fixed point with 1/100 scaling]', int),
79-
'channel_map': ConfigOption(('0', '0x1FFFFFFFFF'), QOS_OPT_CHMAP, '5-byte BLE channel map bitmask', str),
80-
'wifi_blacklist': ConfigOption(('0', '1,2,...,11'), QOS_OPT_BLACKLIST,'List of blacklisted wifi channels', str),
81-
}
82-
83-
BLE_BOND_OPTIONS_DONGLE = {
84-
'peer_erase': ConfigOption(None, BLE_BOND_PEER_ERASE, 'Trigger peer erase', None),
85-
'peer_search': ConfigOption(None, BLE_BOND_PEER_SEARCH, 'Trigger peer search', None),
86-
}
87-
88-
BLE_BOND_OPTIONS_DEVICE = {
89-
'peer_erase': ConfigOption(None, BLE_BOND_PEER_ERASE, 'Trigger peer erase', None),
90-
}
91-
92-
# Formatting details for QoS, which uses a struct containing multiple configuration values:
93-
# OPTION_ID: (struct format, struct member names, binary to human-readable conversion function, human-readable to binary conversion function)
94-
QOS_OPTIONS_FORMAT = {
95-
QOS_OPT_BLACKLIST: ('<H', ['wifi_blacklist'], lambda x: str([i for i in range(0,16) if x & (1 << i) != 0])[1:-1], lambda x: sum(map(lambda y: (1 << int(y)), re.findall(r'([\d]+)', x)))),
96-
QOS_OPT_CHMAP: ('<5s', ['channel_map'], lambda x: '0x{:02X}{:02X}{:02X}{:02X}{:02X}'.format(x[4],x[3],x[2],x[1],x[0]), None),
97-
QOS_OPT_PARAM_BLE: ('<HBhhHBHHH', ['sample_count_min', 'min_channel_count', 'weight_crc_ok', 'weight_crc_error', 'ble_block_threshold', 'eval_max_count', 'eval_duration', 'eval_keepout_duration', 'eval_success_threshold'], None, None),
98-
QOS_OPT_PARAM_WIFI: ('<HHH', ['wifi_rating_inc', 'wifi_present_threshold', 'wifi_active_threshold'], None, None),
99-
}
100-
101-
PCA20041_CONFIG = {
102-
'sensor' : {
103-
'id' : SETUP_MODULE_SENSOR,
104-
'options' : PMW3360_OPTIONS
105-
},
106-
107-
'ble_bond' : {
108-
'id' : SETUP_MODULE_BLE_BOND,
109-
'options' : BLE_BOND_OPTIONS_DEVICE
110-
}
111-
}
112-
113-
PCA20044_CONFIG = {
114-
'sensor' : {
115-
'id' : SETUP_MODULE_SENSOR,
116-
'options' : PAW3212_OPTIONS
117-
},
118-
119-
'ble_bond' : {
120-
'id' : SETUP_MODULE_BLE_BOND,
121-
'options' : BLE_BOND_OPTIONS_DEVICE
122-
}
123-
}
124-
125-
PCA20045_CONFIG = {
126-
'sensor' : {
127-
'id' : SETUP_MODULE_SENSOR,
128-
'options' : PAW3212_OPTIONS
129-
},
130-
131-
'ble_bond' : {
132-
'id' : SETUP_MODULE_BLE_BOND,
133-
'options' : BLE_BOND_OPTIONS_DEVICE
134-
}
135-
}
136-
137-
PCA20037_CONFIG = {
138-
'ble_bond' : {
139-
'id' : SETUP_MODULE_BLE_BOND,
140-
'options' : BLE_BOND_OPTIONS_DEVICE
141-
}
142-
}
143-
144-
PCA10059_CONFIG = {
145-
'qos' : {
146-
'id' : SETUP_MODULE_QOS,
147-
'options' : QOS_OPTIONS,
148-
'format' : QOS_OPTIONS_FORMAT
149-
},
150-
151-
'ble_bond' : {
152-
'id' : SETUP_MODULE_BLE_BOND,
153-
'options' : BLE_BOND_OPTIONS_DONGLE
154-
}
155-
}
156-
157-
DEVICE = {
158-
'desktop_mouse_nrf52832' : {
159-
'vid' : 0x1915,
160-
'pid' : 0x52DA,
161-
'config' : PCA20044_CONFIG,
162-
'stream_led_cnt' : 0,
163-
},
164-
'desktop_mouse_nrf52810' : {
165-
'vid' : 0x1915,
166-
'pid' : 0x52DB,
167-
'config' : PCA20045_CONFIG,
168-
'stream_led_cnt' : 0,
169-
},
170-
'gaming_mouse' : {
171-
'vid' : 0x1915,
172-
'pid' : 0x52DE,
173-
'config' : PCA20041_CONFIG,
174-
'stream_led_cnt' : 2,
175-
},
176-
'keyboard' : {
177-
'vid' : 0x1915,
178-
'pid' : 0x52DD,
179-
'config' : PCA20037_CONFIG,
180-
'stream_led_cnt' : 0,
181-
},
182-
'dongle' : {
183-
'vid' : 0x1915,
184-
'pid' : 0x52DC,
185-
'config' : PCA10059_CONFIG,
186-
'stream_led_cnt' : 0,
187-
}
188-
}
18931

19032

19133
class ConfigStatus(IntEnum):
@@ -250,46 +92,6 @@ def parse_response(response_raw):
25092
return Response(rcpt, event_id, status, event_data)
25193

25294

253-
class ConfigParser:
254-
""" Class used to simplify "read-modify-write" handling of parameter structs.
255-
For example when the python argument modifies one value within struct,
256-
and the remaining struct values should remain unchanged. """
257-
258-
def __init__(self, fetched_data, fmt, member_names, value_presenter, value_formatter):
259-
assert struct.calcsize(fmt) <= EVENT_DATA_LEN_MAX
260-
self.fmt = fmt
261-
262-
if value_presenter is not None:
263-
self.presenter = value_presenter
264-
else:
265-
self.presenter = lambda x: x
266-
267-
if value_formatter is not None:
268-
self.formatter = value_formatter
269-
else:
270-
self.formatter = lambda x: x
271-
272-
vals = struct.unpack(self.fmt, fetched_data)
273-
if len(member_names) != len(vals):
274-
raise ValueError('format does not match member name list')
275-
self.members = collections.OrderedDict()
276-
for key, val in zip(member_names, vals):
277-
self.members[key] = val
278-
279-
def __str__(self):
280-
return ''.join(map(lambda x: '{}: {}\n'.format(x[0], x[1]), self.members.items()))
281-
282-
def config_get(self, name):
283-
return self.presenter(self.members[name])
284-
285-
def config_update(self, name, val):
286-
if name not in self.members:
287-
raise KeyError
288-
self.members[name] = self.formatter(val)
289-
290-
def serialize(self):
291-
return struct.pack(self.fmt, *self.members.values())
292-
29395
def create_set_report(recipient, event_id, event_data):
29496
""" Function creating a report in order to set a specified configuration
29597
value.
@@ -332,10 +134,6 @@ def create_fetch_report(recipient, event_id):
332134
return report
333135

334136

335-
def check_range(value, value_range):
336-
return value_range[0] <= value <= value_range[1]
337-
338-
339137
def exchange_feature_report(dev, recipient, event_id, event_data, is_fetch,
340138
poll_interval=POLL_INTERVAL_DEFAULT):
341139
if is_fetch:
@@ -396,33 +194,6 @@ def exchange_feature_report(dev, recipient, event_id, event_data, is_fetch,
396194
return success
397195

398196

399-
def get_device_pid(device_type):
400-
return DEVICE[device_type]['pid']
401-
402-
403-
def get_device_vid(device_type):
404-
return DEVICE[device_type]['vid']
405-
406-
407-
def get_device_type(pid):
408-
for device_type in DEVICE:
409-
if DEVICE[device_type]['pid'] == pid:
410-
return device_type
411-
return None
412-
413-
def get_option_format(device_type, module_id):
414-
try:
415-
# Search through nested dicts and see if 'format' key exists for the config option
416-
format = [
417-
DEVICE[device_type]['config'][config]['format'] for config in DEVICE[device_type]['config'].keys()
418-
if DEVICE[device_type]['config'][config]['id'] == module_id
419-
][0]
420-
except KeyError:
421-
format = None
422-
423-
return format
424-
425-
426197
def open_device(device_type):
427198
dev = None
428199

@@ -451,67 +222,5 @@ def open_device(device_type):
451222
return dev
452223

453224

454-
def change_config(dev, recipient, config_name, config_value, device_options, module_id):
455-
config_opts = device_options[config_name]
456-
opt_id = config_opts.event_id
457-
event_id = (EVENT_GROUP_SETUP << GROUP_FIELD_POS) | (module_id << MOD_FIELD_POS) | (opt_id << OPT_FIELD_POS)
458-
value_range = config_opts.range
459-
logging.debug('Send request to update {}: {}'.format(config_name, config_value))
460-
461-
dev_name = get_device_type(recipient)
462-
format = get_option_format(dev_name, module_id)
463-
464-
if format is not None:
465-
# Read out first, then modify and write back (even if there is only one member in struct, to simplify code)
466-
success, fetched_data = exchange_feature_report(dev, recipient, event_id, None, True)
467-
if not success:
468-
return success
469-
470-
try:
471-
config = ConfigParser(fetched_data, *format[opt_id])
472-
config.config_update(config_name, config_value)
473-
event_data = config.serialize()
474-
except (ValueError, KeyError):
475-
print('Failed. Invalid value for {}'.format(config_name))
476-
return False
477-
else:
478-
if config_value is not None:
479-
if not check_range(config_value, value_range):
480-
print('Failed. Config value for {} must be in range {}'.format(config_name, value_range))
481-
return False
482-
event_data = struct.pack('<I', config_value)
483-
else:
484-
event_data = None
485-
486-
success = exchange_feature_report(dev, recipient, event_id, event_data, False)
487-
488-
if success:
489-
logging.debug('Config changed')
490-
else:
491-
logging.debug('Config change failed')
492-
493-
return success
494-
495-
496-
def fetch_config(dev, recipient, config_name, device_options, module_id):
497-
config_opts = device_options[config_name]
498-
opt_id = config_opts.event_id
499-
event_id = (EVENT_GROUP_SETUP << GROUP_FIELD_POS) | (module_id << MOD_FIELD_POS) | (opt_id << OPT_FIELD_POS)
500-
logging.debug('Fetch the current value of {} from the firmware'.format(config_name))
501-
502-
success, fetched_data = exchange_feature_report(dev, recipient, event_id, None, True)
503-
504-
if not success or not fetched_data:
505-
return success, None
506-
507-
dev_name = get_device_type(recipient)
508-
format = get_option_format(dev_name, module_id)
509-
510-
if format is None:
511-
return success, config_opts.type.from_bytes(fetched_data, byteorder='little')
512-
else:
513-
return success, ConfigParser(fetched_data, *format[opt_id]).config_get(config_name)
514-
515-
516225
if __name__ == '__main__':
517226
print("Please run configurator_cli.py or gui.py to start application")

0 commit comments

Comments
 (0)