1 | # -*- coding: utf-8 -*- |
---|
2 | """ |
---|
3 | ------------------------------------------------------------------------------ |
---|
4 | Mango 802.11 Reference Design Experiments Framework |
---|
5 | - Transport / Network / Basic Node Commands |
---|
6 | ------------------------------------------------------------------------------ |
---|
7 | License: Copyright 2019 Mango Communications, Inc. All rights reserved. |
---|
8 | Use and distribution subject to terms in LICENSE.txt |
---|
9 | ------------------------------------------------------------------------------ |
---|
10 | |
---|
11 | This module provides class definitions for transport, network, and basic node |
---|
12 | commands. |
---|
13 | |
---|
14 | Functions (see below for more information): |
---|
15 | GetType() |
---|
16 | Identify() |
---|
17 | GetNodeInfo() |
---|
18 | SetupNetwork() |
---|
19 | ResetNetwork() |
---|
20 | Ping() |
---|
21 | TestPayloadSize() |
---|
22 | AddNodeGrpId() |
---|
23 | ClearNodeGrpId() |
---|
24 | |
---|
25 | Integer constants: |
---|
26 | GRPID_NODE, GRPID_TRANS - Command Groups |
---|
27 | GRP_NODE, GRP_TRANSPORT - Command Group Names |
---|
28 | |
---|
29 | Many other constants may be defined; please do not use these values when |
---|
30 | defining other sub-classes of CmdResp and BufferCmd. |
---|
31 | |
---|
32 | """ |
---|
33 | |
---|
34 | |
---|
35 | from . import message |
---|
36 | |
---|
37 | |
---|
38 | __all__ = ['NodeGetType', 'NodeIdentify', 'GetNodeInfo', |
---|
39 | 'NodeSetupNetwork', 'NodeResetNetwork', 'NodeGetTemperature', |
---|
40 | 'TransportPing', 'TestTransportMTU', |
---|
41 | 'TransportAddNodeGroupId', 'TransportClearNodeGroupId'] |
---|
42 | |
---|
43 | |
---|
44 | # Command Groups |
---|
45 | GROUP_NODE = 0x00 |
---|
46 | GROUP_TRANSPORT = 0x10 |
---|
47 | GROUP_USER = 0x20 |
---|
48 | |
---|
49 | |
---|
50 | # Command Group names |
---|
51 | GROUP_NAME_NODE = 'node' |
---|
52 | GROUP_NAME_TRANSPORT = 'transport' |
---|
53 | GROUP_NAME_USER = 'user' |
---|
54 | |
---|
55 | |
---|
56 | # Node Command IDs |
---|
57 | # - The C counterparts are found in *_node.h |
---|
58 | |
---|
59 | CMD_PARAM_NODE_TYPE_RSVD = 0xFFFFFFFF |
---|
60 | |
---|
61 | CMDID_NODE_TYPE = 0x000000 |
---|
62 | CMDID_NODE_INFO = 0x000001 |
---|
63 | |
---|
64 | CMDID_NODE_IDENTIFY = 0x000002 |
---|
65 | |
---|
66 | CMD_PARAM_NODE_IDENTIFY_ALL_NODES = 0xFFFFFFFF |
---|
67 | |
---|
68 | CMDID_NODE_CONFIG_SETUP = 0x000003 |
---|
69 | CMDID_NODE_CONFIG_RESET_MULTICAST = 0x000004 |
---|
70 | |
---|
71 | CMD_PARAM_NODE_NETWORK_RESET_ALL_NODES = 0xFFFFFFFF |
---|
72 | |
---|
73 | CMDID_NODE_TEMPERATURE = 0x000005 |
---|
74 | |
---|
75 | |
---|
76 | # Transport Command IDs |
---|
77 | # - The C counterparts are found in *_transport.h |
---|
78 | CMDID_TRANSPORT_PING = 0x000001 |
---|
79 | CMDID_TRANSPORT_SET_MAX_RESP_WORDS = 0x000002 |
---|
80 | CMDID_TRANSPORT_TEST_MTU = 0x000003 |
---|
81 | CMDID_TRANSPORT_NODE_GROUP_ID_ADD = 0x000100 |
---|
82 | CMDID_TRANSPORT_NODE_GROUP_ID_CLEAR = 0x000101 |
---|
83 | |
---|
84 | |
---|
85 | # Local Constants |
---|
86 | _CMD_GROUP_NODE = (GROUP_NODE << 24) |
---|
87 | _CMD_GROUP_TRANSPORT = (GROUP_TRANSPORT << 24) |
---|
88 | _CMD_GROUP_USER = (GROUP_USER << 24) |
---|
89 | |
---|
90 | |
---|
91 | |
---|
92 | # ----------------------------------------------------------------------------- |
---|
93 | # Node Commands |
---|
94 | # ----------------------------------------------------------------------------- |
---|
95 | class NodeGetType(message.Cmd): |
---|
96 | """Command to get the Type of the node""" |
---|
97 | def __init__(self): |
---|
98 | super(NodeGetType, self).__init__() |
---|
99 | self.command = _CMD_GROUP_NODE + CMDID_NODE_TYPE |
---|
100 | |
---|
101 | def process_resp(self, resp): |
---|
102 | if resp.resp_is_valid(num_args=3): |
---|
103 | args = resp.get_args() |
---|
104 | |
---|
105 | # Command returns 3 arguments: |
---|
106 | # Platform ID |
---|
107 | # High software application ID |
---|
108 | # Low software application ID |
---|
109 | # Return 3 IDs in tuple |
---|
110 | return (args[0], args[1], args[2]) |
---|
111 | |
---|
112 | else: |
---|
113 | raise Exception('ERROR: node returned invalid Node Type values: {}'.format(args)) |
---|
114 | |
---|
115 | |
---|
116 | class NodeIdentify(message.Cmd): |
---|
117 | """Command to blink the Node LEDs.""" |
---|
118 | def __init__(self, serial_number): |
---|
119 | super(NodeIdentify, self).__init__() |
---|
120 | self.command = _CMD_GROUP_NODE + CMDID_NODE_IDENTIFY |
---|
121 | |
---|
122 | if (serial_number == CMD_PARAM_NODE_IDENTIFY_ALL_NODES): |
---|
123 | (sn, sn_str) = (CMD_PARAM_NODE_IDENTIFY_ALL_NODES, "All nodes") |
---|
124 | platform_id = CMD_PARAM_NODE_IDENTIFY_ALL_NODES |
---|
125 | else: |
---|
126 | from . import util |
---|
127 | from .. import platform |
---|
128 | |
---|
129 | (sn, sn_str) = util.get_serial_number(serial_number) |
---|
130 | p = platform.lookup_platform_by_serial_num(serial_number) |
---|
131 | |
---|
132 | if p is None: |
---|
133 | raise Exception('ERROR: no registered platform matches serial number {0}') |
---|
134 | |
---|
135 | platform_id = p.platform_id |
---|
136 | |
---|
137 | self.id = sn_str |
---|
138 | self.add_args(platform_id) |
---|
139 | self.add_args(sn) |
---|
140 | |
---|
141 | def process_resp(self, resp): |
---|
142 | print("Blinking LEDs of node: {0}".format(self.id)) |
---|
143 | |
---|
144 | # End Class |
---|
145 | |
---|
146 | |
---|
147 | class GetNodeInfo(message.Cmd): |
---|
148 | """Command to get the hardware parameters from the node.""" |
---|
149 | def __init__(self): |
---|
150 | super(GetNodeInfo, self).__init__() |
---|
151 | self.command = _CMD_GROUP_NODE + CMDID_NODE_INFO |
---|
152 | |
---|
153 | def process_resp(self, resp): |
---|
154 | # NODE_INFO response format: |
---|
155 | # No arguments |
---|
156 | # payload[0:N-1]: Framework node info struct (same for all platforms) |
---|
157 | # payload[N:end]: Platform node info struct (platform-specific format) |
---|
158 | from wlan_exp.info import NodeInfo |
---|
159 | import wlan_exp.platform as platform |
---|
160 | |
---|
161 | # Parse the framework node info from the first N bytes of the response payload |
---|
162 | ni = NodeInfo() |
---|
163 | N = ni.sizeof() |
---|
164 | ni.deserialize(resp.resp_payload[0:N]) |
---|
165 | |
---|
166 | # Pass rest of payload to platform-specific InfoStruct |
---|
167 | # The current code only constructs one platform-specific node info struct |
---|
168 | # Additional info structs could be constructed here and appended to the return tuple |
---|
169 | p_id = ni['platform_id'] |
---|
170 | p_config = ni['platform_config'] |
---|
171 | p_ni = platform.get_platform_node_info_format(p_id, p_config) |
---|
172 | |
---|
173 | if p_ni is None: |
---|
174 | # node object is out-of-scope, so rely on serial number from NODE_INFO in error msg |
---|
175 | raise Exception('ERROR: no wlan_exp_platform found for platform_id {0} reported by node with s/n {1}'.format(p_id, ni['serial_number'])) |
---|
176 | |
---|
177 | p_ni.deserialize(resp.resp_payload[N:]) |
---|
178 | |
---|
179 | return (ni, p_ni) |
---|
180 | |
---|
181 | # End Class |
---|
182 | |
---|
183 | |
---|
184 | class NodeSetupNetwork(message.Cmd): |
---|
185 | """Command to perform initial network setup of a node.""" |
---|
186 | def __init__(self, node): |
---|
187 | super(NodeSetupNetwork, self).__init__() |
---|
188 | |
---|
189 | self.command = _CMD_GROUP_NODE + CMDID_NODE_CONFIG_SETUP |
---|
190 | self.add_args(node.platform_id) |
---|
191 | self.add_args(node.serial_number) |
---|
192 | self.add_args(node.node_id) |
---|
193 | self.add_args(node.transport.ip_to_int(node.transport.ip_address)) |
---|
194 | self.add_args(node.transport.unicast_port) |
---|
195 | self.add_args(node.transport.broadcast_port) |
---|
196 | |
---|
197 | def process_resp(self, resp): |
---|
198 | pass |
---|
199 | |
---|
200 | # End Class |
---|
201 | |
---|
202 | class NodeResetNetwork(message.Cmd): |
---|
203 | """Command to reset the network configuration of a node.""" |
---|
204 | def __init__(self, serial_number, output=False): |
---|
205 | super(NodeResetNetwork, self).__init__() |
---|
206 | self.command = _CMD_GROUP_NODE + CMDID_NODE_CONFIG_RESET_MULTICAST |
---|
207 | self.output = output |
---|
208 | |
---|
209 | if (serial_number == CMD_PARAM_NODE_NETWORK_RESET_ALL_NODES): |
---|
210 | (sn, sn_str) = (CMD_PARAM_NODE_NETWORK_RESET_ALL_NODES, "All nodes") |
---|
211 | platform_id = CMD_PARAM_NODE_NETWORK_RESET_ALL_NODES |
---|
212 | else: |
---|
213 | from . import util |
---|
214 | from .. import platform |
---|
215 | |
---|
216 | (sn, sn_str) = util.get_serial_number(serial_number) |
---|
217 | p = platform.lookup_platform_by_serial_num(serial_number) |
---|
218 | |
---|
219 | if p is None: |
---|
220 | raise Exception('ERROR: no registered platform matches serial number {0}') |
---|
221 | |
---|
222 | platform_id = p.platform_id |
---|
223 | |
---|
224 | self.id = sn_str |
---|
225 | self.add_args(platform_id) |
---|
226 | self.add_args(sn) |
---|
227 | |
---|
228 | def process_resp(self, resp): |
---|
229 | if (self.output): |
---|
230 | print("Reset network config of node: {0}".format(self.id)) |
---|
231 | |
---|
232 | # End Class |
---|
233 | |
---|
234 | |
---|
235 | class NodeGetTemperature(message.Cmd): |
---|
236 | """Command to get the temperature of a node. |
---|
237 | |
---|
238 | The response must be converted to Celsius with the given formula: |
---|
239 | ((double(temp)/65536.0)/0.00198421639) - 273.15 |
---|
240 | - http://www.xilinx.com/support/documentation/user_guides/ug370.pdf |
---|
241 | - 16 bit value where 10 MSBs are an ADC value |
---|
242 | """ |
---|
243 | def __init__(self): |
---|
244 | super(NodeGetTemperature, self).__init__() |
---|
245 | self.command = _CMD_GROUP_NODE + CMDID_NODE_TEMPERATURE |
---|
246 | |
---|
247 | def process_resp(self, resp): |
---|
248 | if resp.resp_is_valid(num_args=3): |
---|
249 | args = resp.get_args() |
---|
250 | curr_temp = ((float(args[0])/65536.0)/0.00198421639) - 273.15 |
---|
251 | min_temp = ((float(args[1])/65536.0)/0.00198421639) - 273.15 |
---|
252 | max_temp = ((float(args[2])/65536.0)/0.00198421639) - 273.15 |
---|
253 | return (curr_temp, min_temp, max_temp) |
---|
254 | else: |
---|
255 | return (0, 0, 0) |
---|
256 | |
---|
257 | # End Class |
---|
258 | |
---|
259 | |
---|
260 | # ----------------------------------------------------------------------------- |
---|
261 | # Transport Commands |
---|
262 | # ----------------------------------------------------------------------------- |
---|
263 | class TransportPing(message.Cmd): |
---|
264 | """Command to ping the node.""" |
---|
265 | def __init__(self): |
---|
266 | super(TransportPing, self).__init__() |
---|
267 | self.command = _CMD_GROUP_TRANSPORT + CMDID_TRANSPORT_PING |
---|
268 | |
---|
269 | def process_resp(self, resp): |
---|
270 | pass |
---|
271 | |
---|
272 | # End Class |
---|
273 | |
---|
274 | class TransportSetMaxRespWords(message.Cmd): |
---|
275 | """Sets the maximum number words the node may send in any response packet""" |
---|
276 | |
---|
277 | def __init__(self, max_words): |
---|
278 | super(TransportSetMaxRespWords, self).__init__() |
---|
279 | self.command = _CMD_GROUP_TRANSPORT + CMDID_TRANSPORT_SET_MAX_RESP_WORDS |
---|
280 | |
---|
281 | self.add_args(max_words) |
---|
282 | |
---|
283 | def process_resp(self, resp): |
---|
284 | # Response has no arguments - always successful if node responds |
---|
285 | return True |
---|
286 | |
---|
287 | class TestTransportMTU(message.Cmd): |
---|
288 | """Command to perform an uplink payload size test on a node.""" |
---|
289 | def __init__(self, req_mtu): |
---|
290 | super(TestTransportMTU, self).__init__() |
---|
291 | self.command = _CMD_GROUP_TRANSPORT + CMDID_TRANSPORT_TEST_MTU |
---|
292 | |
---|
293 | # Remember the requested MTU for comparison to the length of the |
---|
294 | # payload in the response packet |
---|
295 | self.req_mtu = req_mtu |
---|
296 | |
---|
297 | # Single argument is the MTU of the packet the node should send in |
---|
298 | # its reponse to this command. "MTU" in this context refers to the |
---|
299 | # entire Ethernet payload (raw bytes-on-wire minus 14-byte header and |
---|
300 | # 4-byte FCS). The the command argument below is computed from the |
---|
301 | # requested MTU less the size of the wlan_exp transport and response |
---|
302 | # headers that will precede the long payload in the response packet. |
---|
303 | |
---|
304 | import wlan_exp.transport.util as util |
---|
305 | |
---|
306 | # Response packet includes one u32 argument after |
---|
307 | # the cmdRespHdr, before the bogus payload (hence +4 below) |
---|
308 | self.num_hdr_bytes = util.get_total_header_len() + 4 |
---|
309 | |
---|
310 | self.add_args(int(req_mtu) - self.num_hdr_bytes) |
---|
311 | |
---|
312 | def process_resp(self, resp): |
---|
313 | args = resp.get_args() |
---|
314 | |
---|
315 | if resp.resp_is_valid(num_args=1): |
---|
316 | resp_req_len = int(args[0]) |
---|
317 | |
---|
318 | if resp_req_len == (self.req_mtu - self.num_hdr_bytes): |
---|
319 | # Response packet header arg matched requested size |
---|
320 | |
---|
321 | # Verify the response packet itself was the expected length |
---|
322 | # The 'resp' argument is the command response payload including |
---|
323 | # args and bogus payload. Response has 1 argument (4 bytes) |
---|
324 | # followed by bogus payload to fill requested MTU |
---|
325 | if (resp.length - 4) == resp_req_len: |
---|
326 | # Entire requested payload was received |
---|
327 | return True |
---|
328 | |
---|
329 | # Response packet was tuncated or malformed |
---|
330 | return False |
---|
331 | |
---|
332 | else: |
---|
333 | raise Exception('ERROR: TestTransportMTU response arguments invalid; args={}'.format(args)) |
---|
334 | |
---|
335 | # End Class |
---|
336 | |
---|
337 | class TransportAddNodeGroupId(message.Cmd): |
---|
338 | """Command to add a Node Group ID to the node so that it can process |
---|
339 | broadcast commands that are received from that node group.""" |
---|
340 | def __init__(self, group): |
---|
341 | super(TransportAddNodeGroupId, self).__init__() |
---|
342 | self.command = _CMD_GROUP_TRANSPORT + CMDID_TRANSPORT_NODE_GROUP_ID_ADD |
---|
343 | self.add_args(group) |
---|
344 | |
---|
345 | def process_resp(self, resp): |
---|
346 | pass |
---|
347 | |
---|
348 | # End Class |
---|
349 | |
---|
350 | |
---|
351 | class TransportClearNodeGroupId(message.Cmd): |
---|
352 | """Command to clear a Node Group ID to the node so that it can ignore |
---|
353 | broadcast commands that are received from that node group.""" |
---|
354 | def __init__(self, group): |
---|
355 | super(TransportClearNodeGroupId, self).__init__() |
---|
356 | self.command = _CMD_GROUP_TRANSPORT + CMDID_TRANSPORT_NODE_GROUP_ID_CLEAR |
---|
357 | self.add_args(group) |
---|
358 | |
---|
359 | def process_resp(self, resp): |
---|
360 | pass |
---|
361 | |
---|
362 | # End Class |
---|
363 | |
---|
364 | |
---|