forked from ysard/MyOwnBricks
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathheader_checksum.py
259 lines (217 loc) · 9 KB
/
header_checksum.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# MyOwnBricks is a library for the emulation of PoweredUp sensors on microcontrollers
# Copyright (C) 2021-2023 Ysard - <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Handy functions implementing the UART protocol about messages headers, modes
sizes and checksums.
Doc:
https://github.com/pybricks/technical-info/blob/master/uart-protocol.md
"""
LUMP_MSG_TYPE_MASK = 0xC0
lump_msg_type_t = {
"LUMP_MSG_TYPE_SYS": 0 << 6,
"LUMP_MSG_TYPE_CMD": 1 << 6,
"LUMP_MSG_TYPE_INFO": 2 << 6,
"LUMP_MSG_TYPE_DATA": 3 << 6,
}
LUMP_MSG_SIZE_MASK = 0x38
lump_msg_size_t = {
"LUMP_MSG_SIZE_1": 0 << 3, # 0
"LUMP_MSG_SIZE_2": 1 << 3, # 8
"LUMP_MSG_SIZE_4": 2 << 3, # 16
"LUMP_MSG_SIZE_8": 3 << 3, # 24
"LUMP_MSG_SIZE_16": 4 << 3, # 32
"LUMP_MSG_SIZE_32": 5 << 3, # 40
}
LUMP_MSG_CMD_MASK = 0x07
lump_cmd_t = {
"LUMP_CMD_TYPE": 0x0,
"LUMP_CMD_MODES": 0x1,
"LUMP_CMD_SPEED": 0x2,
"LUMP_CMD_SELECT": 0x3,
"LUMP_CMD_WRITE": 0x4,
"LUMP_CMD_UNK1": 0x5,
"LUMP_CMD_EXT_MODE": 0x6,
"LUMP_CMD_VERSION": 0x7,
}
color_distance_modes = {
"COLOR/SPEC1": 0, # read 1x int8_t
"PROX/DEBUG": 1, # read 1x int8_t
"COUNT/CALIB": 2, # read 1x int32_t
"REFLT": 3, # read 1x int8_t
"AMBI ": 4, # read 1x int8_t
"COL_O": 5, # writ 1x int8_t
"RGB_I": 6, # read 3x int16_t
"IR_TX": 7, # writ 1x int16_t
"SPEC1": 8, # rrwr 4x int8_t
"DEBUG": 9, # ?? 2x int16_t
"CALIB": 10 # ?? 8x int16_t
}
rev_lump_msg_type_t = {v: k for k, v in lump_msg_type_t.items()}
rev_lump_cmd_t = {v: k for k, v in lump_cmd_t.items()}
rev_lump_msg_size_t = {v: k for k, v in lump_msg_size_t.items()}
rev_color_distance_modes = {v: k for k, v in color_distance_modes.items()}
def get_hub_header(msg_type, cmd, msg_size):
"""Get the header for the corresponding message parameters
This code is for hub side.
.. note:: This is an implementation inspired by ev3_uart_begin_tx_msg() in
https://github.com/pybricks/pybricks-micropython/blob/master/lib/pbio/src/uartdev.c
:param msg_type: Type of message found in lump_msg_type_t.
:param size: Size of message, should fit in (1, 2, 8, 16, 32).
:param cmd: Command, cand be in lump_cmd_t but can also be mode for "set mode"
queries.
:type msg_type: <int>
:type size: <int>
:type cmd: <int>
:return: 1 or 2 bytes:
1 header: for all messages except write queries
2 headers: only for write queries with msg type LUMP_MSG_TYPE_DATA
:rtype: <int>
"""
header1 = None
header2 = None
length = msg_size
if msg_size == 0 or msg_size > 32:
raise ValueError("Message size must be > 0 and <= 32")
if msg_type == lump_msg_type_t["LUMP_MSG_TYPE_DATA"]:
# Write message: send fixed 1st part of message
header1 = _get_hub_header(
lump_msg_type_t["LUMP_MSG_TYPE_CMD"],
lump_msg_size_t["LUMP_MSG_SIZE_1"],
lump_cmd_t["LUMP_CMD_EXT_MODE"]
)
if msg_size == 1:
size = lump_msg_size_t["LUMP_MSG_SIZE_1"]
elif msg_size == 2:
size = lump_msg_size_t["LUMP_MSG_SIZE_2"]
elif msg_size <= 4:
size = lump_msg_size_t["LUMP_MSG_SIZE_4"]
length = 4
elif msg_size <= 8:
size = lump_msg_size_t["LUMP_MSG_SIZE_8"]
length = 8
elif msg_size <= 16:
size = lump_msg_size_t["LUMP_MSG_SIZE_16"]
length = 16
elif msg_size <= 32:
size = lump_msg_size_t["LUMP_MSG_SIZE_32"]
length = 32
header2 = _get_hub_header(msg_type, size, cmd)
print("size", msg_size, "size used", rev_lump_msg_size_t[size], "length", length,
"headers", "" if not header1 else hex(header1), hex(header2))
return header1, header2
def _get_hub_header(msg_type, size, cmd):
"""Return header byte; This code is for hub side.
.. note:: This an implementation of ev3_uart_set_msg_hdr in
https://github.com/pybricks/pybricks-micropython/blob/master/lib/pbio/src/uartdev.c
.. seealso:: `get_hub_header`
:param msg_type: Type of message found in lump_msg_type_t.
:param size: Size of message, should fit in (1, 2, 8, 16, 32).
:param cmd: Command, cand be in lump_cmd_t but can also be mode for "set mode"
queries.
:type msg_type: <int>
:type size: <int>
:type cmd: <int>
:return: Header byte
:rtype: <int>
"""
return (msg_type & LUMP_MSG_TYPE_MASK) | (size & LUMP_MSG_SIZE_MASK) | (cmd & LUMP_MSG_CMD_MASK)
def get_size(header):
"""Get message size from the given header
.. note:: This is an implementation inspired by ev3_uart_get_msg_size() in
https://github.com/pybricks/pybricks-micropython/blob/master/lib/pbio/src/uartdev.c
:param header: Header byte.
:type header: <int>
:return: Size. LUMP_MSG_TYPE_SYS and LUMP_MSG_TYPE_INFO have special processing.
:rtype: <int>
"""
if header & LUMP_MSG_TYPE_MASK == lump_msg_type_t["LUMP_MSG_TYPE_SYS"]:
# print("LUMP_MSG_TYPE_SYS ?")
return 1
size = (1 << ((header >> 3) & 0x7)) + 2
if header & LUMP_MSG_TYPE_MASK == lump_msg_type_t["LUMP_MSG_TYPE_INFO"]:
# print("LUMP_MSG_TYPE_INFO ?")
size += 1
return size
def parse_device_header(header):
"""Parse a header emitted from a device to the hub
Extract message type, mode/command and size.
The command part of LUMP_MSG_TYPE_DATA messages is called a mode.
.. seealso:: pbio_uartdev_parse_msg() in
https://github.com/pybricks/pybricks-micropython/blob/master/lib/pbio/src/uartdev.c
https://github.com/pybricks/technical-info/blob/master/uart-protocol.md
:return: Tuple of type, mode, cmd, size values.
:rtype: <tuple <str>, <int>, <str>, <int>>
"""
msg_type = rev_lump_msg_type_t[header & LUMP_MSG_TYPE_MASK]
mode = header & LUMP_MSG_CMD_MASK
cmd = rev_lump_cmd_t[mode]
msg_size = get_size(header)
if msg_type == "LUMP_MSG_TYPE_DATA":
# cmd message is useless in this case, it's assimilated to the mode (integer value)
# Also display a meaning mapping of modes for the color & distance sensor
print("device header:", hex(header), "=> type", msg_type, "mode", mode,
f"({rev_color_distance_modes[mode]})", "tot size", msg_size)
elif msg_type == "LUMP_MSG_TYPE_INFO":
# /!\ Meaning of the header is mainly due to the byte next to the header,
# which is unknown here.
# Also, the mode obtained is modulo INFO_MODE_PLUS_8, which is also set
# in the next byte in the message.
# cmd message is also useless it's the info_type data that is useful,
# also in the next byte.
print("device header:", hex(header), "=> type", msg_type, "mode",
"{}/{}".format(mode, mode + 8), "tot size", msg_size)
else:
print("device header:", hex(header), "=> type", msg_type, "cmd", cmd,
"tot size", msg_size)
return msg_type, mode, cmd, msg_size
def get_device_header(msg_type, mode, msg_size):
"""Return header byte: This code is for device side.
.. seealso:: `_get_hub_header`
.. warning:: For some reason, msg_size is multiplied by 3 to fit in
expected sizes decoded in the hub.
(There are restrictions of sizes due to the masks used).
:param msg_size: payload size only, WITHOUT header & checksum.
:type msg_type: <int>
:type mode: <int>
:type msg_size: <int>
:return: Header byte
:rtype: <int>
"""
msg_size *= 3
return (msg_type & LUMP_MSG_TYPE_MASK) | (msg_size & LUMP_MSG_SIZE_MASK) | (mode & LUMP_MSG_CMD_MASK)
def get_all_possible_device_headers():
"""Bruteforce all headers accepted by the hub
Since the header is in 1 byte, we test all 256 combinations.
Used to show size restrictions of messages. This could explain 0 padding
of some messages shorter than their real transmitted size.
Note that some combinations of settings for a header will never happen
due to mask constraints on the bits.
"""
for i in range(256):
ret = parse_device_header(i)
if ret == ("LUMP_MSG_TYPE_DATA", 6, 8): # < this will never happen
print(ret, i)
raise ValueError
def get_cheksum(data):
"""Compute checksum for the given data
:param data: Iterable of values (header and message data are expected)
:return: checksum
:rtype: <int>
"""
checksum = 0xFF
for i in data:
checksum ^= i
#print(checksum, hex(checksum))
return checksum