[6320] | 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 | |
---|