|
7 | 7 | import hid |
8 | 8 | import struct |
9 | 9 | import time |
10 | | -import re |
| 10 | +from enum import IntEnum |
11 | 11 |
|
12 | 12 | import logging |
13 | | -import collections |
14 | | - |
15 | | -from enum import IntEnum |
16 | 13 |
|
17 | | -ConfigOption = collections.namedtuple('ConfigOption', 'range event_id help type') |
| 14 | +from devices import get_device_pid, get_device_vid, get_device_type |
18 | 15 |
|
19 | 16 | REPORT_ID = 6 |
20 | 17 | REPORT_SIZE = 30 |
21 | 18 | EVENT_DATA_LEN_MAX = REPORT_SIZE - 6 |
22 | 19 |
|
| 20 | +POLL_INTERVAL_DEFAULT = 0.02 |
| 21 | +POLL_RETRY_COUNT = 200 |
| 22 | + |
23 | 23 | TYPE_FIELD_POS = 0 |
24 | 24 | GROUP_FIELD_POS = 6 |
25 | 25 | EVENT_GROUP_SETUP = 0x1 |
26 | 26 | EVENT_GROUP_DFU = 0x2 |
27 | 27 | EVENT_GROUP_LED_STREAM = 0x3 |
28 | 28 |
|
29 | 29 | MOD_FIELD_POS = 3 |
30 | | -SETUP_MODULE_SENSOR = 0x1 |
31 | | -SETUP_MODULE_QOS = 0x2 |
32 | | -SETUP_MODULE_BLE_BOND = 0x3 |
33 | | - |
34 | 30 | 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 | | -} |
189 | 31 |
|
190 | 32 |
|
191 | 33 | class ConfigStatus(IntEnum): |
@@ -250,46 +92,6 @@ def parse_response(response_raw): |
250 | 92 | return Response(rcpt, event_id, status, event_data) |
251 | 93 |
|
252 | 94 |
|
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 | | - |
293 | 95 | def create_set_report(recipient, event_id, event_data): |
294 | 96 | """ Function creating a report in order to set a specified configuration |
295 | 97 | value. |
@@ -332,10 +134,6 @@ def create_fetch_report(recipient, event_id): |
332 | 134 | return report |
333 | 135 |
|
334 | 136 |
|
335 | | -def check_range(value, value_range): |
336 | | - return value_range[0] <= value <= value_range[1] |
337 | | - |
338 | | - |
339 | 137 | def exchange_feature_report(dev, recipient, event_id, event_data, is_fetch, |
340 | 138 | poll_interval=POLL_INTERVAL_DEFAULT): |
341 | 139 | if is_fetch: |
@@ -396,33 +194,6 @@ def exchange_feature_report(dev, recipient, event_id, event_data, is_fetch, |
396 | 194 | return success |
397 | 195 |
|
398 | 196 |
|
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 | | - |
426 | 197 | def open_device(device_type): |
427 | 198 | dev = None |
428 | 199 |
|
@@ -451,67 +222,5 @@ def open_device(device_type): |
451 | 222 | return dev |
452 | 223 |
|
453 | 224 |
|
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 | | - |
516 | 225 | if __name__ == '__main__': |
517 | 226 | print("Please run configurator_cli.py or gui.py to start application") |
0 commit comments