From 51a42ba03dbf020528433c4e37c8633dbc12e635 Mon Sep 17 00:00:00 2001 From: "Thomas Watteyne [linear]" Date: Thu, 24 Oct 2013 10:02:40 -0700 Subject: [PATCH] SmartMeshSDK 1.0.4.110 --- Gateway/DynNotifs.py | 303 ------- Gateway/DynStructs.py | 355 -------- Gateway/FormatUtils.py | 24 - Gateway/Gateway.py | 301 ------- Gateway/GatewayCli.py | 164 ---- Gateway/GatewayListener.py | 390 --------- Gateway/MuxMessage.py | 206 ----- Gateway/NetworkStateAnalyzer.py | 586 ------------- Gateway/NetworkTest/NetworkTest.py | 81 -- Gateway/NetworkTest/TestMultipleJoins.py | 37 - .../NetworkTest/TestNetworkAvailability.py | 59 -- Gateway/NetworkTest/TestNetworkReliability.py | 59 -- Gateway/NetworkTest/TestNumGoodNeighbors.py | 51 -- Gateway/NetworkTest/TestNumberLinks.py | 145 ---- .../NetworkTest/TestPerMoteAvailability.py | 77 -- Gateway/NetworkTest/TestSingleParent.py | 56 -- Gateway/NetworkTest/TestStabilityVsRssi.py | 88 -- .../NetworkTestPublisher.py | 25 - .../PublishDustLinkData.py | 49 -- .../NetworkTestPublisher/PublishLogFile.py | 58 -- Gateway/NetworkTestPublisher/PublishScreen.py | 26 - Gateway/NetworkTestResult.py | 47 -- Gateway/StructUtils.py | 148 ---- Gateway/snap_version.py | 8 - PKG-INFO | 2 +- SmartMeshSDK/ApiConnector.py | 2 +- SmartMeshSDK/ApiDefinition/ApiDefinition.py | 19 +- .../ApiDefinition/ByteArraySerializer.py | 196 +++-- .../ApiDefinition/HartMgrDefinition.py | 172 +++- .../ApiDefinition/HartMoteDefinition.py | 169 ++-- SmartMeshSDK/ApiDefinition/IpMgrDefinition.py | 157 ++-- .../ApiDefinition/IpMoteDefinition.py | 248 +----- SmartMeshSDK/ApiDefinition/xmlutils.py | 8 +- SmartMeshSDK/ApiException.py | 2 - SmartMeshSDK/AppUtils.py | 32 + SmartMeshSDK/FormatUtils.py | 71 ++ SmartMeshSDK/GenApiConnectors.py | 63 +- SmartMeshSDK/GenApiDocbook.py | 4 +- SmartMeshSDK/GenIpMgrSubscribe.py | 29 +- .../HartMgrConnector/HartMgrConnector.py | 239 +++++- .../HartMgrConnectorInternal.py | 16 +- SmartMeshSDK/HartMgrConnector/NotifReader.py | 2 +- .../HartMoteConnector/HartMoteConnector.py | 373 ++++++--- .../HartMoteConnectorInternal.py | 11 +- SmartMeshSDK/HrParser.py | 222 +++++ .../IpMgrConnectorMux/IpMgrConnectorMux.py | 418 ++++----- .../IpMgrConnectorMuxInternal.py | 16 +- .../IpMgrConnectorMux/IpMgrSubscribe.py | 14 +- .../IpMgrConnectorSerial.py | 418 ++++----- .../IpMgrConnectorSerialInternal.py | 13 +- .../IpMoteConnector/IpMoteConnector.py | 731 +++------------- .../IpMoteConnectorInternal.py | 15 +- .../LatencyCalculator.py | 6 +- SmartMeshSDK/LbrConnector/LbrConnector.py | 10 +- SmartMeshSDK/OTAP/__init__.py | 0 SmartMeshSDK/RateCalculator.py | 27 +- SmartMeshSDK/SerialConnector/Crc.py | 12 +- SmartMeshSDK/SerialConnector/Hdlc.py | 179 ++-- .../SerialConnector/SerialConnector.py | 21 +- .../DC2126AConverters/DC2126AConverters.py | 144 ++++ .../protocols/DC2126AConverters}/__init__.py | 0 .../protocols}/__init__.py | 0 .../protocols}/oap/OAPClient.py | 56 +- .../protocols}/oap/OAPDispatcher.py | 32 +- .../protocols}/oap/OAPMessage.py | 20 +- .../protocols}/oap/OAPNotif.py | 11 +- SmartMeshSDK/protocols/oap/__init__.py | 1 + .../{OTAP => protocols/otap}/FileParser.py | 12 +- .../{OTAP => protocols/otap}/FilterExpr.py | 0 .../{OTAP => protocols/otap}/GenStructs.py | 0 .../{OTAP => protocols/otap}/NotifWorker.py | 0 .../otap}/OTAPCommunicator.py | 104 ++- .../{OTAP => protocols/otap}/OTAPMessage.py | 0 .../{OTAP => protocols/otap}/OTAPMic.py | 0 .../{OTAP => protocols/otap}/OTAPStructs.py | 0 .../otap}/ReliableCommander.py | 34 +- .../protocols/otap}/__init__.py | 0 .../{OTAP => protocols/otap}/otap_version.py | 0 .../protocols/xivelyConnector}/__init__.py | 0 .../xivelyConnector/xivelyConnector.py | 792 ++++++++++++++++++ SmartMeshSDK/sdk_version.py | 2 +- bin/APIExplorer/APIExplorer.py | 119 ++- bin/DC2126A/DC2126A.py | 581 +++++++++++++ bin/HrListener/HrListener.py | 303 +++++++ bin/InstallTest/InstallTest.py | 16 +- bin/LBRConnection/LBRConnection.py | 109 +-- bin/LEDPing/LEDPing.py | 405 ++++----- bin/MgrListener/MgrListener.py | 124 +-- bin/MuxConfig/MuxConfig.py | 33 +- bin/MuxConfig/SerialMuxConfigs.py | 9 +- bin/OTAPCommunicator/OTAPCommunicator.py | 69 +- bin/PkGen/PkGen.py | 186 ++-- bin/SensorDataReceiver/SensorDataReceiver.py | 103 ++- bin/Simple/SimpleHartMote.py | 19 +- bin/Simple/SimpleIPMgr.py | 18 +- bin/Simple/SimpleIPMote.py | 20 +- bin/TempMonitor/TempMonitor.py | 193 +++-- bin/Upstream/Upstream.py | 133 +-- bin/Xively/Xively.py | 711 ++++++++++++++++ bin/logging.conf | 163 ++++ cryptopy/__init__.py | 6 + cryptopy/binascii_plus.py | 73 ++ cryptopy/setup.py | 48 ++ dustUI/dust.ico | Bin 0 -> 10134 bytes dustUI/dustFrame.py | 34 +- dustUI/dustFrameApi.py | 24 +- dustUI/dustFrameBrowse.py | 20 +- dustUI/dustFrameCommand.py | 29 +- dustUI/dustFrameConnection.py | 39 +- dustUI/dustFrameDC2126AConfiguration.py | 311 +++++++ dustUI/dustFrameDC2126AReport.py | 272 ++++++ dustUI/dustFrameFields.py | 42 +- dustUI/dustFrameForm.py | 118 +++ dustUI/dustFrameLBRConnection.py | 25 +- dustUI/dustFrameLEDPing.py | 143 ++-- dustUI/dustFrameMoteList.py | 321 ++++--- dustUI/dustFrameNotifications.py | 19 +- dustUI/dustFrameProgress.py | 164 ++++ dustUI/dustFrameResponse.py | 19 +- dustUI/dustFrameSensorData.py | 20 +- dustUI/dustFrameSensorTx.py | 24 +- dustUI/dustFrameTable.py | 18 +- dustUI/dustFrameText.py | 18 +- dustUI/dustGuiLib.py | 15 +- dustUI/dustStyle.py | 29 +- dustUI/dustWindow.py | 36 +- requirements.txt | 4 + setup.py | 200 +++-- 128 files changed, 7493 insertions(+), 6360 deletions(-) delete mode 100644 Gateway/DynNotifs.py delete mode 100644 Gateway/DynStructs.py delete mode 100644 Gateway/FormatUtils.py delete mode 100644 Gateway/Gateway.py delete mode 100644 Gateway/GatewayCli.py delete mode 100644 Gateway/GatewayListener.py delete mode 100644 Gateway/MuxMessage.py delete mode 100644 Gateway/NetworkStateAnalyzer.py delete mode 100644 Gateway/NetworkTest/NetworkTest.py delete mode 100644 Gateway/NetworkTest/TestMultipleJoins.py delete mode 100644 Gateway/NetworkTest/TestNetworkAvailability.py delete mode 100644 Gateway/NetworkTest/TestNetworkReliability.py delete mode 100644 Gateway/NetworkTest/TestNumGoodNeighbors.py delete mode 100644 Gateway/NetworkTest/TestNumberLinks.py delete mode 100644 Gateway/NetworkTest/TestPerMoteAvailability.py delete mode 100644 Gateway/NetworkTest/TestSingleParent.py delete mode 100644 Gateway/NetworkTest/TestStabilityVsRssi.py delete mode 100644 Gateway/NetworkTestPublisher/NetworkTestPublisher.py delete mode 100644 Gateway/NetworkTestPublisher/PublishDustLinkData.py delete mode 100644 Gateway/NetworkTestPublisher/PublishLogFile.py delete mode 100644 Gateway/NetworkTestPublisher/PublishScreen.py delete mode 100644 Gateway/NetworkTestResult.py delete mode 100644 Gateway/StructUtils.py delete mode 100644 Gateway/snap_version.py create mode 100644 SmartMeshSDK/AppUtils.py create mode 100644 SmartMeshSDK/FormatUtils.py create mode 100644 SmartMeshSDK/HrParser.py rename SmartMeshSDK/{IpMgrConnectorMux => }/LatencyCalculator.py (94%) delete mode 100644 SmartMeshSDK/OTAP/__init__.py create mode 100644 SmartMeshSDK/protocols/DC2126AConverters/DC2126AConverters.py rename {protocols/oap => SmartMeshSDK/protocols/DC2126AConverters}/__init__.py (100%) rename {Gateway/NetworkTest => SmartMeshSDK/protocols}/__init__.py (100%) rename {protocols => SmartMeshSDK/protocols}/oap/OAPClient.py (63%) rename {protocols => SmartMeshSDK/protocols}/oap/OAPDispatcher.py (93%) rename {protocols => SmartMeshSDK/protocols}/oap/OAPMessage.py (98%) rename {protocols => SmartMeshSDK/protocols}/oap/OAPNotif.py (97%) create mode 100644 SmartMeshSDK/protocols/oap/__init__.py rename SmartMeshSDK/{OTAP => protocols/otap}/FileParser.py (86%) rename SmartMeshSDK/{OTAP => protocols/otap}/FilterExpr.py (100%) rename SmartMeshSDK/{OTAP => protocols/otap}/GenStructs.py (100%) rename SmartMeshSDK/{OTAP => protocols/otap}/NotifWorker.py (100%) rename SmartMeshSDK/{OTAP => protocols/otap}/OTAPCommunicator.py (81%) rename SmartMeshSDK/{OTAP => protocols/otap}/OTAPMessage.py (100%) rename SmartMeshSDK/{OTAP => protocols/otap}/OTAPMic.py (100%) rename SmartMeshSDK/{OTAP => protocols/otap}/OTAPStructs.py (100%) rename SmartMeshSDK/{OTAP => protocols/otap}/ReliableCommander.py (82%) rename {Gateway/NetworkTestPublisher => SmartMeshSDK/protocols/otap}/__init__.py (100%) rename SmartMeshSDK/{OTAP => protocols/otap}/otap_version.py (100%) rename {Gateway => SmartMeshSDK/protocols/xivelyConnector}/__init__.py (100%) create mode 100644 SmartMeshSDK/protocols/xivelyConnector/xivelyConnector.py create mode 100644 bin/DC2126A/DC2126A.py create mode 100644 bin/HrListener/HrListener.py create mode 100644 bin/Xively/Xively.py create mode 100644 bin/logging.conf create mode 100644 cryptopy/__init__.py create mode 100644 cryptopy/binascii_plus.py create mode 100644 cryptopy/setup.py create mode 100644 dustUI/dust.ico create mode 100644 dustUI/dustFrameDC2126AConfiguration.py create mode 100644 dustUI/dustFrameDC2126AReport.py create mode 100644 dustUI/dustFrameForm.py create mode 100644 dustUI/dustFrameProgress.py create mode 100644 requirements.txt diff --git a/Gateway/DynNotifs.py b/Gateway/DynNotifs.py deleted file mode 100644 index 1e8eb55..0000000 --- a/Gateway/DynNotifs.py +++ /dev/null @@ -1,303 +0,0 @@ -''' -Dynamic API structures - -Build notifications from a description of their fields -''' - -import struct -import datetime -from array import array -from collections import namedtuple - -import DynStructs -from MuxMessage import API - -NOTIF_TYPES = ['ULPM Data', 'Event', 'Log', 'NotImpl', 'Data', 'IP', 'Health'] -EVENT_TYPES_STR = {API.EV_MOTE_RESET : 'Mote Reset' , - API.EV_NET_RESET : 'Net Reset' , - API.EV_CMD_FINISH : 'Cmd Finish' , - API.EV_MOTE_JOIN : 'Mote Join' , - API.EV_MOTE_OPER : 'Mote Oper' , - API.EV_MOTE_LOST : 'Mote Lost' , - API.EV_NET_TIME : 'Net Time' , - API.EV_PING_RESP : 'Ping', - API.EV_RSV1 : 'NotImpl' , - API.EV_MOTE_BANDWIDTH: 'Mote BW', - API.EV_PATH_CREATE : 'Path Create', - API.EV_PATH_DELETE : 'Path Delete', - API.EV_PACKET_SENT : 'Packet Sent', - API.EV_MOTE_CREATE : 'Mote Create', - API.EV_MOTE_DELETE : 'Mote Delete'} - -#[ Define Health Report Mote Commands ------------------------------------------- -MOTECMD_DEVHR_FIELDS = [ - DynStructs.ApiStructField('charge', 'int', 4), - DynStructs.ApiStructField('QOcc', 'int', 1), - DynStructs.ApiStructField('temperature', 'sint', 1), - DynStructs.ApiStructField('batteryVolt', 'int', 2), - DynStructs.ApiStructField('numTxOk', 'int', 2), - DynStructs.ApiStructField('numTxFail', 'int', 2), - DynStructs.ApiStructField('numRxOk', 'int', 2), - DynStructs.ApiStructField('numRxLost', 'int', 2), - DynStructs.ApiStructField('numMacDrop', 'int', 1), - DynStructs.ApiStructField('numTxBad', 'int', 1), - DynStructs.ApiStructField('badLink_frameId', 'int', 1), - DynStructs.ApiStructField('badLink_slot', 'int', 4), - DynStructs.ApiStructField('badLink_offset', 'int', 1), -] -MoteCmdDevHR = DynStructs.synthesize('MoteCmdDevHR', MOTECMD_DEVHR_FIELDS) - -MOTECMD_DSCVR_FILEDS = [ - DynStructs.ApiStructField('nbrId', 'int', 2), - DynStructs.ApiStructField('rsl', 'sint', 1), - DynStructs.ApiStructField('numRx', 'int', 1), -] -MoteCmdDscvr = DynStructs.synthesize('MoteCmdDscvr', MOTECMD_DSCVR_FILEDS) - -MOTECMD_NBRHR_FIELDS = [ - DynStructs.ApiStructField('nbrId', 'int', 2), - DynStructs.ApiStructField('nbrFlag', 'int', 1), - DynStructs.ApiStructField('rsl', 'sint', 1), - DynStructs.ApiStructField('numTxPk', 'int', 2), - DynStructs.ApiStructField('numTxFail', 'int', 2), - DynStructs.ApiStructField('numRxPk', 'int', 2), -] -MoteCmdNbrHR = DynStructs.synthesize('MoteCmdNbrHR', MOTECMD_NBRHR_FIELDS) -#] --------------------------------------------------------------------------------- - -QOcc = namedtuple('QOcc', 'avrg max') - -#[ Event structure ----------------------------------------------------------------- -# EV_CMD_FINISH, EV_PACKET_SENT -EVENT_CMD_FIELDS = [ - DynStructs.ApiStructField('callbackId', 'int', 4), - DynStructs.ApiStructField('result', 'int', 1), -] -EventCmd = DynStructs.synthesize('EventCmd', EVENT_CMD_FIELDS) - -# EV_PATH_CREATE, EV_PATH_DELETE -EVENT_PATH_FIELDS = [ - DynStructs.ApiStructField('source', 'array', 8), - DynStructs.ApiStructField('dest', 'array', 8), - DynStructs.ApiStructField('direction', 'int', 1), -] -EventPath = DynStructs.synthesize('EventPath', EVENT_PATH_FIELDS) - -# EV_PING_RESP -EVENT_PING_FIELDS = [ - DynStructs.ApiStructField('callbackId', 'int', 4), - DynStructs.ApiStructField('mac', 'array', 8), - DynStructs.ApiStructField('delay', 'sint', 4), - DynStructs.ApiStructField('voltage', 'int', 2), - DynStructs.ApiStructField('temperature', 'sint', 1), -] -EventPing = DynStructs.synthesize('EventPing', EVENT_PING_FIELDS) - -# EV_MOTE_RESET, EV_MOTE_JOIN, EV_MOTE_OPER, EV_MOTE_LOST, EV_MOTE_BANDWIDTH -EVENT_MOTE_FIELDS = [ - DynStructs.ApiStructField('mac', 'array', 8), -] -EventMote = DynStructs.synthesize('EventMote', EVENT_MOTE_FIELDS) - -# EV_MOTE_CREATE, EV_MOTE_DELETE -EVENT_MOTEID_FIELDS = [ - DynStructs.ApiStructField('mac', 'array', 8), - DynStructs.ApiStructField('moteId', 'int', 2), -] -EventMoteId = DynStructs.synthesize('EventMoteId', EVENT_MOTEID_FIELDS) - -# EV_NET_TIME -EVENT_TIME_FIELDS = [ - DynStructs.ApiStructField('uptime', 'int', 4), - DynStructs.ApiStructField('asn', 'array', 5), - DynStructs.ApiStructField('encoding', 'int', 1), - DynStructs.ApiStructField('utcSecs', 'int', 4), - DynStructs.ApiStructField('utcUsecs', 'int', 4), -] -EventTime = DynStructs.synthesize('EventTime', EVENT_TIME_FIELDS) - -#] --------------------------------------------------------------------------------- - -class Event(object): - '''Event notification class - - Attributes: - raw_data : original notification data as a string - event_id : event ID as integer - event_type : event type as integer - time : arrival timestamp as datetime - event_data : event data as an array of integer bytes - mac : (if appropriate) - parsed_data : event specific data - ''' - - def __init__(self, data): - self.raw_data = data - (self.event_id, self.event_type) = struct.unpack('!ib', data[0:5]) - # (most) events don't have a timestamp in the data - self.time = datetime.datetime.now() - # turn event_data into a list of byte values rather than a string - self.event_data = [ord(c) for c in data[5:]] - # shortcut, lots of events have a mac as the first dynamic element - self.parsed_data = None - self.mac = None - - if self.event_type in (API.EV_MOTE_RESET, API.EV_MOTE_JOIN, API.EV_MOTE_OPER, - API.EV_MOTE_LOST, API.EV_MOTE_BANDWIDTH): - self.parsed_data = DynStructs.parse(EventMote, data[5:]) - self.mac = self.parsed_data.mac - - elif self.event_type in (API.EV_MOTE_CREATE, API.EV_MOTE_DELETE): - self.parsed_data = DynStructs.parse(EventMoteId, data[5:]) - self.mac = self.parsed_data.mac - - elif self.event_type in (API.EV_PATH_CREATE, API.EV_PATH_DELETE): - self.parsed_data = DynStructs.parse(EventPath, data[5:]) - self.mac = self.parsed_data.source - - elif self.event_type in (API.EV_CMD_FINISH, API.EV_PACKET_SENT): - self.parsed_data = DynStructs.parse(EventCmd, data[5:]) - - elif self.event_type == API.EV_PING_RESP: - self.parsed_data = DynStructs.parse(EventPing, data[5:]) - - elif self.event_type == API.EV_NET_TIME: - self.parsed_data = DynStructs.parse(EventTime, data[5:]) - - def __str__(self): - if self.parsed_data: - data_str = str(self.parsed_data) - else: - data_str = ' '.join(['%02X' % b for b in self.event_data]) + '\n' - if self.event_type in EVENT_TYPES_STR: - evntType = EVENT_TYPES_STR[self.event_type] - else: - evntType = str(self.event_type) - return "EVENT: id=%d '%s'\n%s" % (self.event_id, evntType, data_str) - -class Data(object): - '''Data notification class (API protocol v4) - - Attributes: - raw_data : original notification data as a string - secs, usecs : packet timestamp (generated time) - mac : mac address as an array of integer bytes - src_port, dest_port : source and destination ports as integers - payload_str : payload in the raw string representation - payload : payload as an array of integer bytes - ''' - - def __init__(self, data): - self.raw_data = data - (self.secs, self.usecs) = struct.unpack('!ql', data[0:12]) - self.mac = [ord(c) for c in data[12:20]] - (self.src_port, self.dest_port) = struct.unpack('!HH', data[20:24]) - # - self.payload_str = data[24:] - # turn payload into an array of byte values rather than a string - self.payload = array('B', self.payload_str) - - def __str__(self): - mac_str = '-'.join(['%02X' % c for c in self.mac]) - payload_str = ' '.join(['%02X' % b for b in self.payload]) - return 'DATA: [%d.%d] src=%s:%x dest=%x: %s' % (self.secs, self.usecs, mac_str, self.src_port, self.dest_port, payload_str) - -class Data_v3(object): - '''Data notification class (API protocol v3) - - Attributes: - raw_data : original notification data as a string - secs, usecs : packet timestamp (generated time) - mac : mac address as an array of integer bytes - src_port, dest_port : source and destination ports as integers - payload_str : payload in the raw string representation - payload : payload as an array of integer bytes - ''' - - def __init__(self, data): - self.raw_data = data - (self.secs, self.usecs) = struct.unpack('!ll', data[1:9]) - self.mac = [ord(c) for c in data[9:17]] - (self.src_port, self.dest_port) = struct.unpack('!HH', data[17:21]) - # - self.payload_str = data[21:] - # turn payload into an array of byte values rather than a string - self.payload = array('B', self.payload_str) - - def __str__(self): - mac_str = '-'.join(['%02X' % c for c in self.mac]) - payload_str = ' '.join(['%02X' % b for b in self.payload]) - return 'DATA: [%d.%d] src=%s:%x dest=%x: %s' % (self.secs, self.usecs, mac_str, self.src_port, self.dest_port, payload_str) - - -# Health Report Class ================================================================= -class HealthReport(object): - '''Health Report notification class - - Attributes: - raw_data : original notification data as a string - mac : mac address as an array of integer bytes - devHR : device health report as named tuple - (charge, QOcc, temperature, batteryVolt, numTxOk, - numTxFail, numRxOk, numRxLost, numMacDrop, numTxBad, - badLink_frameId, badLink_offset) - QOcc : size ofmote queue as named tuple - (avrg, max) - nbrDscv[] : neighbors health reports as list of named tuples - (nbrId, nbrFlag, rsl, numTxPk, numTxFail, numRxPk) - dscvrDef[] : discovered neighbors as list of named tuples - (moteId, rsl, numRx) - ''' - - def __init__(self, data): - - self.raw_data = data - self.nbrDscv = [] - self.nbrHR = [] - self.devHR = () - self.QOcc = () - - if data: - self.mac = [ord(c) for c in data[0:8]] - i = 8 # Skip MAC - - # Parse Mote Commands - while (i+2 <= len(data)) : - (cmdId, cmdLen) = struct.unpack('!BB', data[i:i+2]) - if (i + 2 + cmdLen > len(data)) : - break; - - curPos = i + 2 # Skip CmdID:Length - - if (cmdId == 0x80) : - # Device HR - if (cmdLen >= MoteCmdDevHR.size) : - self.devHR = DynStructs.parse(MoteCmdDevHR, data[curPos : curPos+cmdLen]) - self.QOcc = QOcc(self.devHR.QOcc & 0xF, (self.devHR.QOcc >> 4) & 0xF) - - elif (cmdId == 0x82) : - # Discovery. curPos + 2 for skip two 1-byte field Number of neigbours, Number of Join neigbours - if (cmdLen >= 2) : - self.nbrDscv = DynStructs.parse_array(MoteCmdDscvr, data[curPos+2 : curPos + cmdLen]) - - elif (cmdId == 0x81) : - # Neighbors HR. curPos + 1 for skip one 1-byte field Number of neigbours - if (cmdLen >= 1) : - self.nbrHR = DynStructs.parse_array(MoteCmdNbrHR, data[curPos+1 : curPos + cmdLen]) - - i += cmdLen + 2; - - def __str__(self): - res = DynStructs.pair_to_string('mac', self.mac) - divider = '- - - - - - - - - - - -' - if (self.devHR) : - res += DynStructs.pair_to_string('Device HR', divider) + str(self.devHR) - res += DynStructs.pair_to_string('Qavrg', self.QOcc.avrg) - res += DynStructs.pair_to_string('Qmax', self.QOcc.max) - for f in self.nbrDscv : - res += DynStructs.pair_to_string('Neighbors Discovery', divider) + str(f) - for f in self.nbrHR : - res += DynStructs.pair_to_string('Neighbors HR', divider) + str(f) - return res - - diff --git a/Gateway/DynStructs.py b/Gateway/DynStructs.py deleted file mode 100644 index e90fb28..0000000 --- a/Gateway/DynStructs.py +++ /dev/null @@ -1,355 +0,0 @@ -''' -Mux API structures - -Build classes from a description of their fields -''' - -from collections import namedtuple -import struct - -from StructUtils import ApiStructField, parse, synthesize -# TODO: other code imports the functions below from DynStructs -from StructUtils import parse_array, pair_to_string, to_string -from MuxMessage import API - - -# List of functions that return a callback ID -callbackIdFun = [ API.START_LOCATION_CMD, - API.EXCH_NETWORKID_CMD, - API.SET_ADVERTISING_CMD, - API.SET_DFRAME_MULT_CMD ] - - -# TODO: the command type could be built into the struct class, then -# this dispatch could be a loop over all the struct classes -def parse_object(cmd_type, data): - if cmd_type is API.GET_SYS_INFO_CMD: - si = parse(SysInfo, data) - return si - - elif cmd_type is API.GET_NETWORK_INFO_CMD: - ni = parse(NetworkInfo, data) - return ni - - elif cmd_type is API.GET_NETWORK_CFG_CMD: - nc = parse(NetworkCfg, data) - return nc - - elif cmd_type is API.GET_MANAGER_INFO_CMD: - nc = parse(ManagerInfo, data) - return nc - - elif cmd_type is API.GET_MOTE_CFG_CMD: - mc = parse(MoteCfg, data) - return mc - - elif cmd_type is API.GET_MOTE_CFG_BY_ID_CMD: - mc = parse(MoteCfg, data) - return mc - - elif cmd_type is API.GET_MOTE_INFO_CMD: - mi = parse(MoteInfo, data) - return mi - - elif cmd_type is API.GET_NEXT_PATH_CMD: - pi = parse(Path, data) - return pi - elif cmd_type is API.GET_PATH_INFO_CMD: - # add a dummy path id prefix - pi = parse(Path, '\0\0' + data) - return pi - - elif cmd_type is API.GET_TIME_CMD: - mt = parse(MeshTime, data) - return mt - - elif cmd_type is API.GET_LICENSE_CMD: - lic = parse(LicenseInfo, data) - return lic - - elif cmd_type is API.GET_IP_CFG_CMD: - ipinfi = parse(IPInfo, data) - return ipinfi - - elif cmd_type is API.RESET_CMD: - resetcmd = parse(ResetCmd, data) - return resetcmd - - elif cmd_type in callbackIdFun: - cb = parse(CallbackId, data) - return cb - - else: - return data - - - -# struct spl_getSysInfoRsp { -# uint8_t mac[8]; // Dust IEEE address -# uint8_t hwModel; // Hardware model -# uint8_t hwRev; // otp.ic_version:4 | otp:sip_version:4 -# uint8_t swMajor; // Software version, major -# uint8_t swMinor; // Software version, minor -# uint8_t swPatch; // Software version, patch -# uint16_t swBuild; // Software version, build -# } - -SYSINFO_FIELDS = [ ApiStructField('mac', 'array', 8), - ApiStructField('hwModel', 'int', 1), - ApiStructField('hwRev', 'int', 1), - ApiStructField('swMajor', 'int', 1), - ApiStructField('swMinor', 'int', 1), - ApiStructField('swPatch', 'int', 1), - ApiStructField('swBuild', 'int', 2), - ] - -SysInfo = synthesize('SysInfo', SYSINFO_FIELDS) - - -# enum spl_frameProfile { -# PICARD_PROFILE_01 = 1, // fast build / medium operation -# ULPM_PROFILE_02 = 2, // deprecated, maps to PICARD_PROFILE_01 -# ULPM_PROFILE_03 = 3, // deprecated, maps to PICARD_PROFILE_01 -# ULPM_PROFILE_98 = 98, // deprecated, maps to PICARD_PROFILE_01 -# ULPM_PROFILE_99 = 99, // deprecated, maps to PICARD_PROFILE_01 -# } -# enum spl_loc_mode_t { -# SPL_LOC_OFF, -# SPL_LOC_ONDEMAND, -# SPL_LOC_RTLS -# } -# struct spl_getNetworkConfigRsp { -# uint16_t networkId; -# uint8_t accessPointPA; // boolean -# uint8_t frameProfile; // spl_frameProfile -# uint16_t maxMotes; -# uint16_t baseBandwidth; -# uint8_t downFrameMultVal; // power of 2 -# uint8_t numParents; -# uint8_t enableCCA; // boolean -# uint16_t channelBlacklist; // bitmap of available channels -# uint8_t autoStartNetwork; // boolean -# uint8_t locMode; // location mode (spl_loc_mode_t) -# uint8_t bbMode; // backbone frame mode (spl_bb_mode_t) -# uint8_t bbSize; // backbone frame size -# uint16_t bwMult; // backbone frame size -# } - -NETWORK_CFG_FIELDS = [ ApiStructField('networkId', 'int', 2), - ApiStructField('accessPointPA', 'boolean', 1), - ApiStructField('frameProfile', 'int', 1), - ApiStructField('maxMotes', 'int', 2), - ApiStructField('baseBandwidth', 'int', 2), - ApiStructField('downFrameMultValue', 'int', 1), - ApiStructField('numParents', 'int', 1), - ApiStructField('enableCCA', 'int', 1), - ApiStructField('channelBlacklist', 'int', 2), - ApiStructField('autoStartNetwork', 'boolean', 1), - ApiStructField('locMode', 'int', 1), - ApiStructField('bbMode', 'int', 1), - ApiStructField('bbSize', 'int', 1), - ApiStructField('radioTest', 'int', 1), - ApiStructField('bwMult', 'int', 2), - ] - -NetworkCfg = synthesize('NetworkCfg', NETWORK_CFG_FIELDS) - -# enum spl_advState { -# SPL_ADV_ON, -# SPL_ADV_OFF, -# } -# enum spl_multState { -# SPL_MULT_ON, -# SPL_MULT_OFF -# } -# struct spl_getNetworkInfoRsp { -# int16_t numMotes; -# uint16_t asnSize; // microseconds -# uint8_t advertisementState; // spl_advState -# uint8_t downFrameMultState; // spl_multState -# uint8_t netReliability; -# uint8_t netPathStability; -# uint32_t netLatency; // avg latency, ms -# int8_t netState; // current networ state (see spl_netState) -# } - -NETWORK_INFO_FIELDS = [ ApiStructField('numMotes', 'int', 2), - ApiStructField('asnSize', 'int', 2), - ApiStructField('advertisementState', 'int', 1), - ApiStructField('downFrameMultState', 'int', 1), - ApiStructField('netReliability', 'int', 1), - ApiStructField('netPathStability', 'int', 1), - ApiStructField('netLatency', 'int', 4), - ApiStructField('netState', 'int', 1), - ] - -NetworkInfo = synthesize('NetworkInfo', NETWORK_INFO_FIELDS) - - -# struct spl_getManagerStatsRsp { -# // Low level stats: -# uint16_t serTxCnt; // # of packets sent out on serial port - TODO: 16 bits seems small -# uint16_t serRxCnt; // # of packets received on serial port - TODO: 16 bits seems small -# uint16_t serRxCRCErr; // # of CRC errors -# uint16_t serRxOverruns; // # of overruns detected -# // Protocol-level stats: -# uint16_t apiEstabConn; // # of established Serial API connections -# uint16_t apiDropppedConn; // # of dropped Serial API connections -# uint16_t apiTxOk; // # of request packets sent on serial api for which ack or nack was received -# uint16_t apiTxFail; // # of packets for which there was no ack/nack -# uint16_t apiRxOk; // # of request packets that were received and acked/nacked -# uint16_t apiRxProtErr; // # of packets that were received and dropped due to invalid packet format -# } - -MANAGER_INFO_FIELDS = [ ApiStructField('serTxCnt', 'int', 2), - ApiStructField('serRxCnt', 'int', 2), - ApiStructField('serRxCRCErr', 'int', 2), - ApiStructField('serRxOverruns', 'int', 2), - ApiStructField('apiEstabConn', 'int', 2), - ApiStructField('apiDroppedConn', 'int', 2), - ApiStructField('apiTxOk', 'int', 2), - ApiStructField('apiTxFail', 'int', 2), - ApiStructField('apiRxOk', 'int', 2), - ApiStructField('apiRxProtErr', 'int', 2) - ] - -ManagerInfo = synthesize('ManagerInfo', MANAGER_INFO_FIELDS) - - -# enum spl_moteState { -# SPL_MOTE_STATE_LOST, -# SPL_MOTE_STATE_NEGOT, -# SPL_MOTE_STATE_RSV1, -# SPL_MOTE_STATE_RSV2, -# SPL_MOTE_STATE_OPERATIONAL, -# } -# enum spl_mobilityType { -# SPL_LOCATION_UNUSED, // mote is not used for location measurements -# SPL_LOCATION_FIXED, // mote has a known fixed location -# SPL_LOCATION_MOVABLE, // mote has an unknown location -# SPL_LOCATION_MOBILE, // mote is mobile mote, not participating in the network -# } -# struct spl_getMoteInfoRsp { -# byte_t mac[8]; -# uint16_t moteId; // id is used in neighbor health reports -# boolean isAP; // boolean -# uint8_t state; // spl_moteState -# uint8_t mobilityType; // spl_mobilityType -# boolean isRouting; // boolean -# } - -MOTECFG_FIELDS = [ ApiStructField('mac', 'array', 8), - ApiStructField('moteId', 'int', 2), - ApiStructField('isAP', 'boolean', 1), - ApiStructField('state', 'int', 1), - ApiStructField('mobilityType', 'int', 1), - ApiStructField('isRouting', 'boolean', 1), - ] - -MoteCfg = synthesize('MoteCfg', MOTECFG_FIELDS) - -# struct spl_getMoteInfoRsp { -# byte_t mac[8]; -# uint8_t state; -# uint8_t numNbrs; -# uint8_t numGoodNbrs; -# uint32_t requestedBw; -# uint32_t totalNeededBw; -# uint32_t assignedBw; -# uint32_t packetsReceived; -# uint32_t packetLost; -# uint32_t avgLatency; // average latency in ms -# } - -MOTEINFO_FIELDS = [ ApiStructField('mac', 'array', 8), - ApiStructField('state', 'int', 1), - ApiStructField('numNbrs', 'int', 1), - ApiStructField('numGoodNbrs', 'int', 1), - ApiStructField('requestedBw', 'int', 4), - ApiStructField('totalNeededBw', 'int', 4), - ApiStructField('assignedBw', 'int', 4), - ApiStructField('packetsReceived', 'int', 4), - ApiStructField('packetsLost', 'int', 4), - ApiStructField('avgLatency', 'int', 4), - ] - -MoteInfo = synthesize('MoteInfo', MOTEINFO_FIELDS) - - -# enum spl_pathDirection { -# SPL_PATH_DIR_UNCONNECTED, -# SPL_PATH_DIR_UNI, -# SPL_PATH_DIR_BI, -# } -# struct spl_getNextPathInfoRsp { -# uint16_t pathId; -# byte_t source[8]; -# byte_t dest[8]; -# uint8_t direction; // spl_pathDirection -# uint8_t numLinks; -# uint8_t quality; -# int8_t rssiSrcDest; // last rssi, source->dest -# int8_t rssiDestSrc; // last rssi, dest->src -# } - -PATH_FIELDS = [ ApiStructField('pathId', 'int', 2), - ApiStructField('source', 'array', 8), - ApiStructField('dest', 'array', 8), - ApiStructField('direction', 'int', 1), - ApiStructField('numLinks', 'int', 1), - ApiStructField('quality', 'int', 1), - ApiStructField('rssiSrcDest', 'sint', 1), - ApiStructField('rssiDestSrc', 'sint', 1), - ] - -Path = synthesize('Path', PATH_FIELDS) - - -MESHTIME_FIELDS = [ ApiStructField('uptime', 'int', 4), - ApiStructField('asn', 'asn', 5), - ApiStructField('encoding', 'int', 1), - ApiStructField('utcSecs', 'int', 4), - ApiStructField('utcUsecs', 'int', 4), - ] - -MeshTime = synthesize('MeshTime', MESHTIME_FIELDS) - - -MANAGERINFO_FIELDS = [ApiStructField('serTxCnt', 'int', 2), - ApiStructField('serRxCnt', 'int', 2), - ApiStructField('serRxCRCErr', 'int', 2), - ApiStructField('serRxOverruns', 'int', 2), - ApiStructField('apiEstabConn', 'int', 2), - ApiStructField('apiDropppedConn', 'int', 2), - ApiStructField('apiTxOk', 'int', 2), - ApiStructField('apiTxErr', 'int', 2), - ApiStructField('apiTxFail', 'int', 2), - ApiStructField('apiRxOk', 'int', 2), - ApiStructField('apiRxProtErr', 'int', 2), - ] -ManagerInfo = synthesize('ManagerInfo', MANAGERINFO_FIELDS) - - -IPINFO_FIELDS = [ ApiStructField('ip6addr', 'array', 16), - ApiStructField('ip6mask', 'array', 16), - ] -IPInfo = synthesize('IPInfo', IPINFO_FIELDS) - - -RESETCMD_FIELDS = [ ApiStructField('mac', 'array', 8), - ] -ResetCmd = synthesize('ResetCmd', RESETCMD_FIELDS) - - -LICENSE_FIELD = [ ApiStructField('license', 'array', 13), - ] -LicenseInfo = synthesize('LicenseInfo', LICENSE_FIELD) - - -CALLBACKID_FIELDS = [ ApiStructField('callbackId', 'int', 4), - ] -CallbackId = synthesize('CallbackId', CALLBACKID_FIELDS) - - - -# TODO: getIPConfig diff --git a/Gateway/FormatUtils.py b/Gateway/FormatUtils.py deleted file mode 100644 index 71d6392..0000000 --- a/Gateway/FormatUtils.py +++ /dev/null @@ -1,24 +0,0 @@ -import datetime -import time - -LOG_FORMAT_TIMESTAMP = '%Y/%m/%d %H:%M:%S' - -def formatMacString(mac): - return '-'.join(["%.2x"%i for i in mac]) - -def formatShortMac(mac): - return '-'.join(["%.2x"%i for i in mac[6:]]) - -def formatConnectionParams(connectionParams): - if isinstance(connectionParams,str): - return connectionParams.replace('/','-') - elif isinstance(connectionParams,tuple): - return '{0}-{1}'.format(connectionParams[0].replace('.','_'),connectionParams[1]) - -def formatTimestamp(timestamp=None): - if timestamp==None: - timestamp = time.time() - return '{0}.{1}'.format( - time.strftime(LOG_FORMAT_TIMESTAMP,time.localtime(timestamp)), - int((timestamp*1000)%1000) - ) diff --git a/Gateway/Gateway.py b/Gateway/Gateway.py deleted file mode 100644 index b733ae8..0000000 --- a/Gateway/Gateway.py +++ /dev/null @@ -1,301 +0,0 @@ -#!/usr/bin/python - -import logging -class NullHandler(logging.Handler): - def emit(self, record): - pass -log = logging.getLogger('Gateway') -log.setLevel(logging.ERROR) -log.addHandler(NullHandler()) - -import time -import threading -import traceback - -from pydispatch import dispatcher - -from DustLinkData import DustLinkData - -from EventBus import EventBusClient - -from IpMgrConnectorSerial import IpMgrConnectorSerial -from IpMgrConnectorMux import IpMgrConnectorMux -import ApiException - -import GatewayListener -import NetworkStateAnalyzer - -from NetworkTestPublisher import PublishDustLinkData -from NetworkTestPublisher import PublishLogFile - -import FormatUtils - -class Gateway(threading.Thread): - - REFRESH_PERIOD = 10.0 # in seconds - - SNAPSHOT_START = 'SNAPSHOT_START' - CMD = 'CMD' - SNAPSHOT_END = 'SNAPSHOT_END' - NOTIF = 'NOTIF' - - def __init__(self,refresh_period=REFRESH_PERIOD): - - # log - log.info('creating instance') - - # store params - self.refresh_period = refresh_period - - # initialize parent class - threading.Thread.__init__(self) - - # give this thread a name - self.name = 'Gateway' - - # local variables - self.goOn = True - self.dataLock = threading.Lock() - self.apiconnectors = {} - self.listeners = {} - self.analyzers = {} - - # connect to dispatcher - dispatcher.connect( - self.tearDown, - signal = 'tearDown', - weak = False, - ) - dispatcher.connect( - self.deviceCommunicationError, - signal = 'deviceCommunicationError', - weak = False, - ) - - # start itself - self.start() - - def run(self): - - try: - - # log - log.info('thread started') - - while self.goOn: - - # update modules - with self.dataLock: - self._updateModules() - - # sleep a bit - time.sleep(self.refresh_period) - - #===== kill associated threads - - with self.dataLock: - for connectParam in self.apiconnectors.keys(): - self._deleteConnection(connectParam) - - # disconnect from dispatcher - dispatcher.disconnect( - self.tearDown, - signal = 'tearDown', - weak = False, - ) - dispatcher.disconnect( - self.deviceCommunicationError, - signal = 'deviceCommunicationError', - weak = False, - ) - - # log - log.info('thread ended') - - except Exception as err: - output = [] - output += ['===== crash in thread {0} ====='.format(self.name)] - output += ['\nerror:\n'] - output += [str(err)] - output += ['\ncall stack:\n'] - output += [traceback.format_exc()] - output = '\n'.join(output) - print output - log.critical(output) - raise - - #======================== public ========================================== - - def tearDown(self): - - # log - log.info('tearDown() called') - - # kill main thread (will kill associated threads) - self.goOn = False - - def deviceCommunicationError(self,sender,signal,data): - assert isinstance(data,dict) - dictKeys = data.keys() - dictKeys.sort() - assert dictKeys==['connectionParam','reason'] - - log.warning('received device communication error for connection {0}'.format( - data['connectionParam'] - ) - ) - - with self.dataLock: - # update state - try: - DustLinkData.DustLinkData().updateManagerConnectionState( - data['connectionParam'], - DustLinkData.DustLinkData.MANAGERCONNECTION_STATE_FAIL, - reason = data['reason'], - ) - except ValueError: - pass # happens when connection has already been deleted - - self._deleteConnection(data['connectionParam']) - - #======================== private ========================================= - - def _updateModules(self): - - # get the connections - storedConnections = DustLinkData.DustLinkData().getManagerConnections() - - connectionKeys = self.apiconnectors.keys() - - # stop some - for activeConnection in connectionKeys: - if (not storedConnections) or (activeConnection not in storedConnections): - self._deleteConnection(activeConnection) - - # start some - if storedConnections: - for storedConnection in storedConnections: - if storedConnection not in connectionKeys: - self._addConnection(storedConnection) - elif storedConnections[storedConnection]['state'] == DustLinkData.DustLinkData.MANAGERCONNECTION_STATE_INACTIVE: - self._deleteConnection(storedConnection) - self._addConnection(storedConnection) - - def _addConnection(self,connectParam): - - #===== apiconnectors - - assert(connectParam not in self.apiconnectors) - - if isinstance(connectParam,str): - newConnector = IpMgrConnectorSerial.IpMgrConnectorSerial() - try: - newConnector.connect({ - 'port': connectParam, - }) - except ApiException.ConnectionError as err: - - # log - log.warning('could not add apiconnectors {0}: {1}'.format(connectParam, err)) - - # update state - DustLinkData.DustLinkData().updateManagerConnectionState( - connectParam, - DustLinkData.DustLinkData.MANAGERCONNECTION_STATE_FAIL, - reason = str(err), - ) - - # give the aborted connector a chance to disconnect - try: - newConnector.disconnect() - except: - pass - - return - else: - try: - newConnector = IpMgrConnectorMux.IpMgrConnectorMux() - newConnector.connect({ - 'host': connectParam[0], - 'port': connectParam[1], - }) - except ApiException.ConnectionError as err: - - # log - log.warning('could not add apiconnectors {0}: {1}'.format(connectParam, err)) - - # update state - DustLinkData.DustLinkData().updateManagerConnectionState( - connectParam, - DustLinkData.DustLinkData.MANAGERCONNECTION_STATE_FAIL, - reason = str(err), - ) - - # give the aborted connector a chance to disconnect - try: - newConnector.disconnect() - except: - pass - - return - - # log - log.info('added apiconnectors {0}'.format(connectParam)) - - self.apiconnectors[connectParam] = newConnector - - DustLinkData.DustLinkData().updateManagerConnectionState( - connectParam, - DustLinkData.DustLinkData.MANAGERCONNECTION_STATE_ACTIVE - ) - - #===== add network - try: - DustLinkData.DustLinkData().deleteNetwork(FormatUtils.formatConnectionParams(connectParam)) - except ValueError: - pass # happens if network doesn't exist - - #===== add network - try: - DustLinkData.DustLinkData().addNetwork(FormatUtils.formatConnectionParams(connectParam)) - except ValueError: - pass # happens if network already exists from previous connection - - #===== add listener - assert(connectParam not in self.listeners) - self.listeners[connectParam] = GatewayListener.GatewayListener( - self.apiconnectors[connectParam], - connectParam, - ) - log.info('added listener {0}'.format(connectParam)) - - #===== add analyzer - assert(connectParam not in self.analyzers) - self.analyzers[connectParam] = NetworkStateAnalyzer.NetworkStateAnalyzer(connectParam) - log.info('added analyzer {0}'.format(connectParam)) - - def _deleteConnection(self, connectParam): - - #===== analyzers - if connectParam in self.analyzers: - self.analyzers[connectParam].tearDown() - del self.analyzers[connectParam] - log.info('deleted analyzer {0}'.format(connectParam)) - - #===== listener - if connectParam in self.listeners: - self.listeners[connectParam].tearDown() - del self.listeners[connectParam] - log.info('deleted listener {0}'.format(connectParam)) - - #===== apiconnectors - if connectParam in self.apiconnectors: - self.apiconnectors[connectParam].disconnect() - del self.apiconnectors[connectParam] - log.info('deleted apiconnector {0}'.format(connectParam)) - - #===== delete network - try: - DustLinkData.DustLinkData().deleteNetwork(FormatUtils.formatConnectionParams(connectParam)) - except ValueError: - pass # happens if network was already deleted, e.g. by an earlier call to this function diff --git a/Gateway/GatewayCli.py b/Gateway/GatewayCli.py deleted file mode 100644 index 9353032..0000000 --- a/Gateway/GatewayCli.py +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/python - -import sys -import threading -import binascii -import time -import logging - -from pydispatch import dispatcher - -from dustCli import DustCli - -from SmartMeshSDK import ApiException - -class GatewayCli(DustCli.DustCli): - ''' - \brief Thread which handles CLI commands entered by the user. - ''' - - DEFAULT_SERIALMUX_HOST = '127.0.0.1' - DEFAULT_SERIALMUX_PORT = 9900 - - def __init__(self,appName,nsnap): - - # record parameters - self.appName = appName - self.nsnap = nsnap - - # instanciate parent class - DustCli.DustCli.__init__(self,self.appName) - self.name = 'GatewayCli' - - # register commands - self.registerCommand('connect', - 'c', - 'connect to the manager', - ['serial \nserialmux '], - self._handleConnect, - dontCheckParamsLength=True) - self.registerCommand('disconnect', - 'd', - 'disconnect from the manager', - [' or \'*\''], - self._handleDisconnect) - self.registerCommand('snap', - 'sn', - 'gets a snapshot from a subset of connected managers', - [' or \'*\''], - self._handleSnap) - self.registerCommand('stats', - 'st', - 'display statistics from a subset of connected managers', - [' or \'*\''], - self._handleStats) - - #======================== private ========================================= - - #=== command handlers - - def _handleConnect(self,params): - - if len(params)==0: - self.nsnap.connectMux(self.DEFAULT_SERIALMUX_HOST, - self.DEFAULT_SERIALMUX_PORT) - - else: - connectType = params[0] - - if connectType in ['serial','s']: - - if len(params)!=2: - self._printUsageFromName('connect') - return - - serialport = params[1] - - try: - self.nsnap.connectSerial(serialport) - except ApiException.ConnectionError as err: - print err - - elif connectType in ['serialmux','mux','m']: - - if len(params)!=3: - self._printUsageFromName('connect') - return - - hostip = params[1] - try: - tcpport = int(params[2]) - except TypeError: - print "tcpport should be an int" - - try: - self.nsnap.connectMux(hostip,tcpport) - except ApiException.ConnectionError as err: - print err - - else: - self._printUsageFromName('connect') - return - - def _handleDisconnect(self,params): - - # usage - if len(params)!=1: - self._printUsageFromName('disconnect') - return - - macToMatch = params[0] - - macsDisconnected = self.nsnap.disconnect(macToMatch) - - output = [] - output += ["disconnected {0} manager(s)".format(len(macsDisconnected))] - for mac in macsDisconnected: - output += [' - {0}'.format(mac)] - print '\n'.join(output) - - def _handleSnap(self,params): - - # usage - if len(params)!=1: - self._printUsageFromName('stats') - return - - macToMatch = params[0] - - # perform snapshots on all matching MACs - macsSnapped = self.nsnap.snap(macToMatch) - - output = [] - output += ["\nsnasphot performed on {0} manager(s)".format(len(macsSnapped))] - for mac in macsSnapped: - output += [' - {0}'.format(mac)] - print '\n'.join(output) - - def _handleStats(self,params): - - # usage - if len(params)!=1: - self._printUsageFromName('stats') - return - - allStats = dispatcher.send( - signal = 'getStats', - data = None, - ) - - output = [] - for moduleStats in allStats: - module = moduleStats[0].im_self.name - stats = moduleStats[1] - - output += [" - {0}:".format(module)] - keys = stats.keys() - keys.sort() - for k in keys: - output += [" - {0:>20}: {1}".format(k,stats[k])] - - print '\n'.join(output) - - #======================== helpers ========================================= - \ No newline at end of file diff --git a/Gateway/GatewayListener.py b/Gateway/GatewayListener.py deleted file mode 100644 index ce486ff..0000000 --- a/Gateway/GatewayListener.py +++ /dev/null @@ -1,390 +0,0 @@ -#!/usr/bin/python - -import logging -class NullHandler(logging.Handler): - def emit(self, record): - pass -log = logging.getLogger('GatewayListener') -log.setLevel(logging.ERROR) -log.addHandler(NullHandler()) - -import time -import threading -import traceback - -from EventBus import EventBusClient -from pydispatch import dispatcher - -import DynNotifs -import FormatUtils -import ApiConnector -import Gateway -import ApiException -from IpMgrConnectorMux import IpMgrSubscribe - -class SnapShot(threading.Thread): - - SNAPSHOT_PERIOD_FIRST = 5 ##< in seconds - SNAPSHOT_PERIOD_SEC = 5*60 ##< in seconds - - def __init__(self,gatewaylistener,connector,connectParams): - - assert isinstance(gatewaylistener,GatewayListener) - assert isinstance(connector,ApiConnector.ApiConnector) - assert isinstance(connectParams,(str,tuple)) - - # store params - self.gatewaylistener = gatewaylistener - self.connector = connector - self.connectParams = connectParams - - # local variables - self.periodicSnapshotRunning = False - self.snapShotPeriod = self.SNAPSHOT_PERIOD_FIRST - self.busySnapshotting = threading.Lock() - - # initialize parent - threading.Thread.__init__(self) - self.name = '{0}_SnapShot'.format(FormatUtils.formatConnectionParams(self.connectParams)) - - # start itself - self.start() - - def run(self): - - # log - log.info('thread {0} started'.format(self.name)) - - try: - - self.periodicSnapshotRunning = True - self.delayCounter = 0 - while self.periodicSnapshotRunning: - time.sleep(1) - self.delayCounter +=1 - if self.delayCounter==self.snapShotPeriod: - self.doSnapshot() - self.delayCounter = 0 - self.snapShotPeriod = self.SNAPSHOT_PERIOD_SEC - - except (ApiException.ConnectionError,ApiException.CommandTimeoutError) as err: - - # log - log.warning('connection error={0}'.format(err)) - - # dispatch - dispatcher.send( - signal = 'deviceCommunicationError', - data = { - 'connectionParam': self.connectParams, - 'reason': str(err), - }, - ) - - except Exception as err: - output = [] - output += ['===== crash in thread {0} ====='.format(self.name)] - output += ['\nerror:\n'] - output += [str(err)] - output += ['\ncall stack:\n'] - output += [traceback.format_exc()] - output = '\n'.join(output) - print output # critical error - log.critical(output) - raise - - # log - log.info('thread {0} ended'.format(self.name)) - - #======================== public ========================================== - - def doSnapshot(self): - - self.busySnapshotting.acquire() - - motes = [] - - # dispatch - dispatcher.send( - signal = 'networkEvent_{0}'.format(FormatUtils.formatConnectionParams(self.connectParams)), - data = { - 'type': Gateway.Gateway.SNAPSHOT_START, - }, - ) - - starttime = time.time() - - try: - currentMac = (0,0,0,0,0,0,0,0) # start getMoteConfig() iteration with the 0 MAC address - continueAsking = True - while continueAsking: - try: - res = self._execCommand(self.connector.dn_getMoteConfig,(currentMac,True)) - except ApiException.APIError: - continueAsking = False - else: - motes.append(res.macAddress) - currentMac = res.macAddress - - for mac in motes: - self._execCommand(self.connector.dn_getMoteInfo,(mac,)) - - for mac in motes: - currentPathId = 0 - continueAsking = True - while continueAsking: - try: - res = self._execCommand(self.connector.dn_getNextPathInfo,(mac,0,currentPathId)) - except ApiException.APIError: - continueAsking = False - else: - currentPathId = res.pathId - - # Note: below are commands we don't execute since not parsed - ''' - currentMac = (0,0,0,0,0,0,0,0) # start getNextACLEntry() iteration with the 0 MAC address - continueAsking = True - while continueAsking: - try: - res = self._execCommand(self.connector.dn_getNextAclEntry,(currentMac,)) - except ApiException.APIError: - continueAsking = False - else: - currentMac = res.mac - - self._execCommand(self.connector.dn_getIPConfig,()) - - self._execCommand(self.connector.dn_getLicense,()) - - self._execCommand(self.connector.dn_getManagerInfo,()) - - self._execCommand(self.connector.dn_getNetworkConfig,()) - - self._execCommand(self.connector.dn_getNetworkInfo,()) - - self._execCommand(self.connector.dn_getSysInfo,()) - - self._execCommand(self.connector.dn_getTime,()) - ''' - - except ApiException.APIError as err: - log.critical("FAILED: {0}".format(str(err))) - - # dispatch - dispatcher.send( - signal = 'networkEvent_{0}'.format(FormatUtils.formatConnectionParams(self.connectParams)), - data = { - 'type': Gateway.Gateway.SNAPSHOT_END, - }, - ) - - self.busySnapshotting.release() - - def tearDown(self): - self.periodicSnapshotRunning = False - - #======================== private ========================================= - - def _execCommand(self,func,params): - try: - res = func(*params) - except TypeError as err: - # TODO: Avoid TypeError from communication error - log.error(str(err)) - raise ApiException.ConnectionError(str(err)) - - - # dispatch - dispatcher.send( - signal = 'networkEvent_{0}'.format(FormatUtils.formatConnectionParams(self.connectParams)), - data = { - 'type': Gateway.Gateway.CMD, - 'name': func.__name__, - 'params': params, - 'res': res, - }, - ) - - return res - - def _formatParams(self, params): - output = [] - for p in params: - if isinstance(p, (list,tuple)): - if len(p)==8: - value = FormatUtils.formatMacString(p) - else: - value = '0x'+(''.join(['%.2x'%i for i in p])) - else: - value = str(p) - output += [value] - return ','.join(output) - - #======================== helpers ========================================= - -class GatewayListener(EventBusClient.EventBusClient): - - def __init__(self,connector,connectParams): - assert isinstance(connector,ApiConnector.ApiConnector) - assert isinstance(connectParams,(str,tuple)) - - # record parameters - self.connector = connector - self.connectParams = connectParams - - # log - log.info("creating instance") - - # local variables - self.statsLock = threading.Lock() - self.starttime = time.time() - self.logLineCounter = 0 - self._clearStats() - - self.snapShotThread = SnapShot(self,connector,connectParams) - - # subscriber - try: - self.subscriber = IpMgrSubscribe.IpMgrSubscribe(self.connector) - self.subscriber.start() - - self.subscriber.subscribe( - notifTypes = [ - IpMgrSubscribe.IpMgrSubscribe.NOTIFDATA, - IpMgrSubscribe.IpMgrSubscribe.NOTIFIPDATA, - ], - fun = self._notifCallback, - isRlbl = False, - ) - self.subscriber.subscribe( - notifTypes = [ - IpMgrSubscribe.IpMgrSubscribe.NOTIFEVENT, - IpMgrSubscribe.IpMgrSubscribe.NOTIFLOG, - IpMgrSubscribe.IpMgrSubscribe.NOTIFHEALTHREPORT, - ], - fun = self._notifCallback, - isRlbl = True, - ) - self.subscriber.subscribe( - notifTypes = [ - IpMgrSubscribe.IpMgrSubscribe.ERROR, - IpMgrSubscribe.IpMgrSubscribe.FINISH, - ], - fun = self._disconnectedCallback, - isRlbl = True, - ) - except TypeError as err: - # TODO: Avoid TypeError from communication error - log.error(str(err)) - raise ApiException.ConnectionError(str(err)) - - self.subscriber._thread.name = '{0}_IpMgrSubscribe'.format(FormatUtils.formatConnectionParams(self.connectParams)) - - # initialize parent class - EventBusClient.EventBusClient.__init__(self, - 'dataToMesh_{0}'.format(FormatUtils.formatConnectionParams(self.connectParams)), - self._sendDataToMesh, - ) - self.name = '{0}_GatewayListener'.format( - FormatUtils.formatConnectionParams(self.connectParams) - ) - - #======================== public ========================================== - - def tearDown(self): - - # tear down internal threads - self.snapShotThread.tearDown() - - # call parent class - super(GatewayListener,self).tearDown() - - #======================== private ========================================= - - def _sendDataToMesh(self,sender,signal,data): - - try: - self.connector.dn_sendData( - macAddress = data['mac'], - priority = data['priority'], - srcPort = data['srcPort'], - dstPort = data['dstPort'], - options = data['options'], - data = data['data'], - ) - except TypeError as err: - # TODO: Avoid TypeError from communication error - log.error(str(err)) - raise ApiException.ConnectionError(str(err)) - - def _clearStats(self): - self.statsLock.acquire() - self.statsStarttime = time.time() - self.stats = {} - self.statsLock.release() - - def _notifCallback(self, notifName, notifParams): - - if notifName in [IpMgrSubscribe.IpMgrSubscribe.NOTIFDATA]: - - packet = { - 'timestamp': float(notifParams.utcSecs)+float(notifParams.utcUsecs/1000000.0), - 'mac': tuple(notifParams.macAddress), - 'srcPort': notifParams.srcPort, - 'destPort': notifParams.dstPort, - 'payload': [b for b in notifParams.data], - } - - # log - if log.isEnabledFor(logging.DEBUG): - log.debug('dispatching rawDataToLocal from {0}:{1} to port {2} ({3} bytes)'.format( - packet['mac'], - packet['srcPort'], - packet['destPort'], - len(packet['payload']) - ) - ) - - # dispatch - self._dispatch ( - signal = 'rawDataToLocal', - data = packet, - ) - - elif notifName in [IpMgrSubscribe.IpMgrSubscribe.NOTIFIPDATA]: - - print 'TODO dispatch IPdata {0}'.format(notifParams) - pass - - else: - - # dispatch - self._dispatch ( - signal = 'networkEvent_{0}'.format(FormatUtils.formatConnectionParams(self.connectParams)), - data = { - 'type': Gateway.Gateway.NOTIF, - 'notifName': notifName, - 'notifParams': notifParams, - }, - ) - - # increment stats - self.statsLock.acquire() - if notifName not in self.stats: - self.stats[notifName] = 0 - self.stats[notifName] += 1 - self.statsLock.release() - - def _disconnectedCallback(self,notifName=None,notifParams=None): - - # log - log.warning('disconnection indication received') - - # dispatch - dispatcher.send( - signal = 'deviceCommunicationError', - data = { - 'connectionParam': self.connectParams, - 'reason': 'disconnection indication received', - }, - ) diff --git a/Gateway/MuxMessage.py b/Gateway/MuxMessage.py deleted file mode 100644 index 708d3b2..0000000 --- a/Gateway/MuxMessage.py +++ /dev/null @@ -1,206 +0,0 @@ -''' -Serial Mux Message classes -''' -import string -import struct -import time - -# Magic token for the Serial Mux -MAGIC = struct.pack('BBBB', 0xa7, 0x40, 0xa0, 0xf5) -# Magic token for the Location Engine API -LOCATION_MAGIC = struct.pack('BBBB', 0x73, 0x03, 0xc6, 0x5d) - -# Global event id -event_id = 1 - - -class API: - AUTH = [ 48, 49, 50, 51, 52, 53, 54, 55 ] # TODO: randomize me! - VERSIONS = (4, 3) - - # Notification constants - NOTIF_EVENT = 1 - NOTIF_DATA = 4 - NOTIF_IP = 5 - NOTIF_HEALTH = 6 - - # Command types - HELLO_CMD = 1 - HELLO_RESP = 2 - - NOTIFICATION = 20 - RESET_CMD = 21 - SUBSCRIBE_CMD = 22 - GET_TIME_CMD = 23 - SET_NETWORK_CFG_CMD = 26 - CLEAR_STATS_CMD = 31 - EXCH_JOINKEY_CMD = 33 - EXCH_NETWORKID_CMD = 34 - SET_ACL_CMD = 39 - GET_NEXT_ACL_CMD = 40 - DELETE_ACL_CMD = 41 - PING_MOTE_CMD = 42 - GET_LOG_CMD = 43 - - SEND_DATA_CMD = 44 - START_NETWORK_CMD = 45 - GET_SYS_INFO_CMD = 46 - GET_MOTE_CFG_CMD = 47 - GET_PATH_INFO_CMD = 48 - GET_NEXT_PATH_CMD = 49 - SET_ADVERTISING_CMD = 50 - SET_DFRAME_MULT_CMD = 51 - MEASURE_RSSI_CMD = 52 - GET_MANAGER_INFO_CMD = 53 - SET_TIME_CMD = 54 - GET_LICENSE_CMD = 55 - SET_LICENSE_CMD = 56 - ENABLE_TRACE_CMD = 57 - SET_CLI_USER_CMD = 58 - SEND_IP_CMD = 59 - START_LOCATION_CMD = 60 - RESTORE_FACTORY_DEFAULTS_CMD = 61 - GET_MOTE_INFO_CMD = 62 - GET_NETWORK_CFG_CMD = 63 - GET_NETWORK_INFO_CMD = 64 - GET_MOTE_CFG_BY_ID_CMD = 65 - SET_COMMONJOINKEY_CMD = 66 - GET_IP_CFG_CMD = 67 - SET_IP_CFG_CMD = 68 - DEL_MOTE = 69 - - # Path direction - PATH_DIR_NOTUSE = 1 - PATH_DIR_UP = 2 - PATH_DIR_DOWN = 3 - - # Notification ID - NOTIF_EVENT = 1 - NOTIF_LOG = 2 - NOTIF_DATA = 4 - NOTIF_IP_DATA = 5 - NOTIF_HR = 6 - - # Event type - EV_MOTE_RESET = 0 - EV_NET_RESET = 1 - EV_CMD_FINISH = 2 - EV_MOTE_JOIN = 3 - EV_MOTE_OPER = 4 - EV_MOTE_LOST = 5 - EV_NET_TIME = 6 - EV_PING_RESP = 7 - EV_RSV1 = 8 - EV_MOTE_BANDWIDTH= 9 - EV_PATH_CREATE = 10 - EV_PATH_DELETE = 11 - EV_PACKET_SENT = 12 - EV_MOTE_CREATE = 13 - EV_MOTE_DELETE = 14 - - # Mote state - MOTE_STATE_LOST = 0 - MOTE_STATE_NEGOT = 1 - MOTE_STATE_OPER = 4 - - -# Mux messages - -def build_message(cmd_type, data, cmd_id=0): - '''Build a Serial Mux message''' - cmd = MAGIC - cmd += struct.pack('!HHB', len(data)+3, cmd_id, cmd_type) - cmd += data - return cmd - -def build_response(cmd_type, response_code, data, cmd_id=0): - '''Build a Serial Mux response''' - resp = struct.pack('B', response_code) + data - return build_message(cmd_type, resp, cmd_id) - -def build_data_notif(src_mac, src_port, dest_port, data): - ''' - Build a data notification - src, dest: mac address, port of source and destination - ''' - t = time.time() - secs = int(t) - msecs = int((t - secs) * 1000000) - notif = struct.pack('!BBLL8B', API.NOTIF_DATA, 0, secs, msecs, *src_mac) - notif += struct.pack('HH', src_port, dest_port) + data - return build_message(API.NOTIFICATION, notif) - -def build_event(event_type, data): - '''Build an event from the provided data and insert the current event id''' - global event_id - notif = struct.pack('!BLB', API.NOTIF_EVENT, event_id, event_type) + data - event_id += 1 - return build_message(API.NOTIFICATION, notif) - -def build_mote_event(event_type, mac): - ''' - Build a mote event notification (Lost, Joining, Operational, Reset) - ''' - return build_event(event_type, struct.pack('8B', *mac)) - - -# Message Parser - -class Parser(object): - ''' - Simple parser class for Serial Mux messages - ''' - def __init__(self, magic = MAGIC): - self.magic = magic - self.input_buffer = '' - self.callback = None - - def setCallback(self, cb): - ''' - Set the callback for a complete message - ''' - self.callback = cb - - def parse(self, data): - ''' - Parse input data - Calls the registered callback when complete message is received - ''' - if not data: - return - self.input_buffer += data - while self.parse_one(): - pass - - def parse_one(self): - '''Parse a single command from input_data - Returns: whether a command was found - ''' - msg_start = self.input_buffer.find(self.magic) - if msg_start >= 0: - # strip the ignored input - self.input_buffer = self.input_buffer[msg_start:] - # verify input is long enough - if len(self.input_buffer) < 6: - return False - # parse message header - bin_len = self.input_buffer[4:6] - msg_len = struct.unpack('!H', bin_len)[0] - # TODO: limit the length of valid messages - index_end = 6 + msg_len - # verify the message is complete - if len(self.input_buffer) < index_end: - return False - - msg = self.input_buffer[6:index_end] - (cmd_id, cmd_type) = struct.unpack('!HB', msg[0:3]) - data = msg[3:] - if self.callback: - self.callback(cmd_id, cmd_type, data) - self.input_buffer = self.input_buffer[index_end:] - return True - else: - # if the token doesn't appear, ignore all but the last 3 characters - self.input_buffer = self.input_buffer[-3:] - return False - diff --git a/Gateway/NetworkStateAnalyzer.py b/Gateway/NetworkStateAnalyzer.py deleted file mode 100644 index d667f6b..0000000 --- a/Gateway/NetworkStateAnalyzer.py +++ /dev/null @@ -1,586 +0,0 @@ -#!/usr/bin/python - -import logging -class NullHandler(logging.Handler): - def emit(self, record): - pass -log = logging.getLogger('NetworkStateAnalyzer') -log.setLevel(logging.ERROR) -log.addHandler(NullHandler()) - -import threading - -from DustLinkData import DustLinkData -import FormatUtils -import Gateway - -import DynNotifs -from EventBus import EventBusClient -from IpMgrConnectorSerial.IpMgrConnectorSerial import IpMgrConnectorSerial -from IpMgrConnectorMux.IpMgrConnectorMux import IpMgrConnectorMux - -class NetworkStateAnalyzer(EventBusClient.EventBusClient): - - QUEUESIZE = 100 - - def __init__(self,connectParams): - - # log - log.info("creating instance") - - # store params - self.connectParams = connectParams - - # initialize parent class - EventBusClient.EventBusClient.__init__(self, - 'networkEvent_{0}'.format(FormatUtils.formatConnectionParams(self.connectParams)), - self._logIndication, - queuesize=self.QUEUESIZE, - ) - self.name = '{0}_NetworkStateAnalyzer'.format( - FormatUtils.formatConnectionParams(self.connectParams) - ) - - # local variables - self.netname = FormatUtils.formatConnectionParams(self.connectParams) - self.dataLock = threading.Lock() - self.snapPaths = [] - self.snapshotOngoing = False - - #======================== public ========================================== - - def _logIndication(self,sender,signal,data): - - # log - if log.isEnabledFor(logging.DEBUG): - log.debug("got data {0}".format(data)) - - #===== store event data in DustLinkData - found = True - - if data['type']==Gateway.Gateway.SNAPSHOT_START: - self._handle_SNAPSHOT_START() - - elif data['type']==Gateway.Gateway.CMD: - if (isinstance(data['res'],IpMgrConnectorSerial.Tuple_dn_getMoteConfig) or isinstance(data['res'],IpMgrConnectorMux.Tuple_dn_getMoteConfig)): - self._handle_CMD_getMoteConfig(data['res']) - elif (isinstance(data['res'],IpMgrConnectorSerial.Tuple_dn_getMoteInfo) or isinstance(data['res'],IpMgrConnectorMux.Tuple_dn_getMoteInfo)): - self._handle_CMD_getMoteInfo(data['res']) - elif (isinstance(data['res'],IpMgrConnectorSerial.Tuple_dn_getNextPathInfo) or isinstance(data['res'],IpMgrConnectorMux.Tuple_dn_getNextPathInfo)): - self._handle_CMD_getNextPathInfo(data['res']) - else: - found = False - - elif data['type']==Gateway.Gateway.SNAPSHOT_END: - self._handle_SNAPSHOT_END() - - elif data['type']==Gateway.Gateway.NOTIF: - if (isinstance(data['notifParams'],IpMgrConnectorSerial.Tuple_notifHealthReport) or isinstance(data['notifParams'],IpMgrConnectorMux.Tuple_notifHealthReport)): - self._handle_NOTIF_notifHealthReport(data['notifParams']) - elif (isinstance(data['notifParams'],IpMgrConnectorSerial.Tuple_eventMoteCreate) or isinstance(data['notifParams'],IpMgrConnectorMux.Tuple_eventMoteCreate)): - self._handle_NOTIF_eventMoteCreate(data['notifParams']) - elif (isinstance(data['notifParams'],IpMgrConnectorSerial.Tuple_eventMoteJoin) or isinstance(data['notifParams'],IpMgrConnectorMux.Tuple_eventMoteJoin)): - self._handle_NOTIF_eventMoteJoin(data['notifParams']) - elif (isinstance(data['notifParams'],IpMgrConnectorSerial.Tuple_eventMoteOperational) or isinstance(data['notifParams'],IpMgrConnectorMux.Tuple_eventMoteOperational)): - self._handle_NOTIF_eventMoteOperational(data['notifParams']) - elif (isinstance(data['notifParams'],IpMgrConnectorSerial.Tuple_eventPathCreate) or isinstance(data['notifParams'],IpMgrConnectorMux.Tuple_eventPathCreate)): - self._handle_NOTIF_eventPathCreate(data['notifParams']) - elif (isinstance(data['notifParams'],IpMgrConnectorSerial.Tuple_eventPathDelete) or isinstance(data['notifParams'],IpMgrConnectorMux.Tuple_eventPathDelete)): - self._handle_NOTIF_eventPathDelete(data['notifParams']) - elif (isinstance(data['notifParams'],IpMgrConnectorSerial.Tuple_eventMoteLost) or isinstance(data['notifParams'],IpMgrConnectorMux.Tuple_eventMoteLost)): - self._handle_NOTIF_eventMoteLost(data['notifParams']) - elif (isinstance(data['notifParams'],IpMgrConnectorSerial.Tuple_eventPacketSent) or isinstance(data['notifParams'],IpMgrConnectorMux.Tuple_eventPacketSent)): - pass # nothing to do - else: - found = False - - else: - raise SystemError('unexpected data type={0}'.format(data['type'])) - - if not found: - log.warning('unhandled indication {0}'.format(data)) - - #===== execute tests - pass # TODO - - #======================== handlers ======================================== - - #===== SNAPSHOT_START - - def _handle_SNAPSHOT_START(self): - try: - self.dataLock.acquire() - self.snapshotOngoing = True - self.snapPaths = [] - finally: - self.dataLock.release() - - #===== CMD - - def _handle_CMD_getMoteConfig(self,res): - - mac = tuple(res.macAddress) - - self._addNewMoteIfNeeded(mac) - - for k,v in {'moteId': res.moteId, - 'isAP': res.isAP, - 'state': res.state, - 'isRouting': res.isRouting,}.items(): - DustLinkData.DustLinkData().setMoteInfo(mac,k,v) - - def _handle_CMD_getMoteInfo(self,res): - - mac = tuple(res.macAddress) - - self._addNewMoteIfNeeded(mac) - - for k,v in {'state': res.state, - 'numNbrs': res.numNbrs, - 'numGoodNbrs': res.numGoodNbrs, - 'requestedBw': res.requestedBw, - 'totalNeededBw': res.totalNeededBw, - 'assignedBw': res.assignedBw, - 'packetsReceived': res.packetsReceived, - 'packetsLost': res.packetsLost, - 'avgLatency': res.avgLatency,}.items(): - DustLinkData.DustLinkData().setMoteInfo(mac,k,v) - - def _handle_CMD_getNextPathInfo(self,res): - try: - self.dataLock.acquire() - self.snapPaths.append(res) - finally: - self.dataLock.release() - - #===== SNAPSHOT_END - - def _handle_SNAPSHOT_END(self): - try: - self.dataLock.acquire() - - currentPaths = DustLinkData.DustLinkData().getNetworkPaths(self.netname) - receivedPaths = [(tuple(p.source),tuple(p.dest)) for p in self.snapPaths] - - # delete paths that have disappeared - for path in currentPaths: - if path not in receivedPaths: - DustLinkData.DustLinkData().deletePath(self.netname,path[0],path[1]) - - # add new paths - for path in receivedPaths: - if path not in currentPaths: - DustLinkData.DustLinkData().addPath(self.netname,path[0],path[1]) - - # update paths - for path in self.snapPaths: - mac = tuple(path.source) - neighbor = tuple(path.dest) - - self._addNewMoteIfNeeded(mac) - self._addNewMoteIfNeeded(neighbor) - - for k,v in {'pathId': path.pathId, - 'direction': path.direction, - 'numLinks': path.numLinks, - 'quality': path.quality, - 'rssiSrcDest': path.rssiSrcDest, - 'rssiDestSrc': path.rssiDestSrc,}.items(): - DustLinkData.DustLinkData().setPathInfo( - FormatUtils.formatConnectionParams(self.connectParams), - mac,neighbor, - k, - v - ) - - self.snapshotOngoing = False - - finally: - self.dataLock.release() - - #===== NOTIF - - def _handle_NOTIF_notifHealthReport(self,notifParams): - - mac = tuple(notifParams.macAddress) - self._addNewMoteIfNeeded(mac) - - # create the HR object - data_as_string = '' - data_as_string += ''.join([chr(b) for b in notifParams.macAddress]) - data_as_string += ''.join([chr(b) for b in notifParams.payload]) - hr = DynNotifs.HealthReport(data_as_string) - - if hr.nbrDscv: - self._handle_NOTIF_notifHealthReport_nbrDscv(mac,hr.nbrDscv) - if hr.nbrHR: - self._handle_NOTIF_notifHealthReport_nbrHR(mac,hr.nbrHR) - if hr.devHR: - self._handle_NOTIF_notifHealthReport_devHR(mac,hr.devHR) - if hr.QOcc: - self._handle_NOTIF_notifHealthReport_QOcc(mac,hr.QOcc) - - # helper for _handle_NOTIF_notifHealthReport - def _handle_NOTIF_notifHealthReport_nbrDscv(self,mac,nbrDscv): - - return # poipoi - - ''' - def updateFromTuple(self,tuple): - assert type(tuple)==DynNotifs.MoteCmdDscvr - self.nbrId = tuple.nbrId - self.rsl = tuple.rsl - self.numRx = tuple.numRx - - self.motes[mac].discoveredNeighbors = [] - for nbr in nbrDscv: - newDiscoveredNeighbor = DiscoveredNeigbor() - newDiscoveredNeighbor.updateFromTuple(nbr) - self.motes[mac].discoveredNeighbors.append(newDiscoveredNeighbor) - ''' - - # helper for _handle_NOTIF_notifHealthReport - def _handle_NOTIF_notifHealthReport_nbrHR(self,mac,nbrHR): - - return # poipoi - - ''' - class Neighbor(StateAttribute): - - def updateFromTuple(self,tuple): - assert type(tuple)==DynNotifs.MoteCmdNbrHR - - self.nbrId = tuple.nbrId - self.nbrFlag = tuple.nbrFlag - self.rsl = tuple.rsl - self.numTxPk = tuple.numTxPk - self.numTxFail = tuple.numTxFail - self.numRxPk = tuple.numRxPk - - # update motes - self.motes[mac].neighbors = [] - for nbr in nbrHR: - newNeighbor = Neighbor() - newNeighbor.updateFromTuple(nbr) - self.motes[mac].neighbors.append(newNeighbor) - - # update waterfall - for nbr in nbrHR: - if nbr.numTxPk>0: - self.waterfall.append({ - "from" : self._formatShortMac(mac), - "to" : nbr.nbrId, - "rssi" : nbr.rsl, - "pdr" : float(nbr.numTxPk-nbr.numTxFail)/float(nbr.numTxPk), - }) - while len(self.waterfall)>self.NUM_WATERFALL_POINTS: - # log - log.debug("{0} waterfalls entries (max is {1}): popping".format( - len(self.waterfall), - self.NUM_WATERFALL_POINTS, - ) - ) - self.waterfall.pop(0) - ''' - - # helper for _handle_NOTIF_notifHealthReport - def _handle_NOTIF_notifHealthReport_devHR(self,mac,devHR): - - for k,v in {'charge': devHR.charge, - 'temperature': devHR.temperature, - 'batteryVolt': devHR.batteryVolt, - 'numTxOk': devHR.numTxOk, - 'numTxFail': devHR.numTxFail, - 'numRxOk': devHR.numRxOk, - 'numRxLost': devHR.numRxLost, - 'numMacDrop': devHR.numMacDrop, - 'numTxBad': devHR.numTxBad, - 'badLink_frameId': devHR.badLink_frameId, - 'badLink_slot': devHR.badLink_slot, - 'badLink_offset': devHR.badLink_offset,}.items(): - DustLinkData.DustLinkData().setMoteInfo(mac,k,v) - - # helper for _handle_NOTIF_notifHealthReport - def _handle_NOTIF_notifHealthReport_QOcc(self,mac,QOcc_param): - - for k,v in {'QOccAvrg': QOcc_param.avrg, - 'QOccMax': QOcc_param.max,}.items(): - DustLinkData.DustLinkData().setMoteInfo(mac,k,v) - - def _handle_NOTIF_eventMoteCreate(self,notifParams): - - mac = tuple(notifParams.macAddress) - - # add mote if needed - self._addNewMoteIfNeeded(mac) - - # configure the mote's id - DustLinkData.DustLinkData().setMoteInfo(mac,'moteId',notifParams.moteId) - - def _handle_NOTIF_eventMoteJoin(self,notifParams): - - mac = tuple(notifParams.macAddress) - - # add mote if needed - self._addNewMoteIfNeeded(mac) - - # configure the mote's state - DustLinkData.DustLinkData().setMoteInfo(mac,'moteState',1) # state 1=negotiating - - def _handle_NOTIF_eventMoteOperational(self,notifParams): - - mac = tuple(notifParams.macAddress) - - # add mote if needed - self._addNewMoteIfNeeded(mac) - - # configure the mote's state - DustLinkData.DustLinkData().setMoteInfo(mac,'moteState',4) # state 1=operational - - def _handle_NOTIF_eventPathCreate(self,notifParams): - try: - self.dataLock.acquire() - - if not self.snapshotOngoing: - - source = tuple(notifParams.source) - destination = tuple(notifParams.dest) - - # add motes if needed - self._addNewMoteIfNeeded(source) - self._addNewMoteIfNeeded(destination) - - thisPath = (source,destination) - currentPaths = DustLinkData.DustLinkData().getNetworkPaths(self.netname) - - # add path - if thisPath not in currentPaths: - DustLinkData.DustLinkData().addPath(self.netname,thisPath[0],thisPath[1]) - - # update path info - DustLinkData.DustLinkData().setPathInfo( - self.netname, - thisPath[0],thisPath[1], - 'direction', - notifParams.direction - ) - - finally: - self.dataLock.release() - - def _handle_NOTIF_eventPathDelete(self,notifParams): - try: - self.dataLock.acquire() - - if not self.snapshotOngoing: - - source = tuple(notifParams.source) - destination = tuple(notifParams.dest) - - # add motes if needed - self._addNewMoteIfNeeded(source) - self._addNewMoteIfNeeded(destination) - - thisPath = (source,destination) - reversePath = (destination, source) - currentPaths = DustLinkData.DustLinkData().getNetworkPaths(self.netname) - - # delete path - if thisPath in currentPaths: - DustLinkData.DustLinkData().deletePath(self.netname,thisPath[0],thisPath[1]) - if reversePath in currentPaths: - DustLinkData.DustLinkData().deletePath(self.netname,reversePath[0],reversePath[1]) - - finally: - self.dataLock.release() - - def _handle_NOTIF_eventMoteLost(self,notifParams): - try: - self.dataLock.acquire() - - if not self.snapshotOngoing: - - source = tuple(notifParams.macAddress) - - # add motes if needed - self._addNewMoteIfNeeded(source) - - currentPaths = DustLinkData.DustLinkData().getNetworkPaths(self.netname) - - # delete path - for path in currentPaths: - if source == path[0]: - DustLinkData.DustLinkData().deletePath(self.netname,*path) - - finally: - self.dataLock.release() - - #======================== helpers ========================================= - - def _addNewMoteIfNeeded(self,mac): - dld = DustLinkData.DustLinkData() - - # add mote - try: - dld.addMote(mac) - except ValueError: - pass # happens when mote already exists - - # in demo mode, add OAP apps to mote - if dld.getDemoMode(): - moteInfo = dld.getMoteInfo(mac) - if moteInfo and ('isAP' in moteInfo) and moteInfo['isAP']==0: - for appname in dld.DEMO_MODE_APPS.keys(): - try: - dld.attachAppToMote(mac,appname) - except ValueError: - pass # happens when app does not exist, or already attached - - # add mote to network - try: - dld.addNetworkMote( - self.netname, - mac - ) - except ValueError: - pass # happens when mote already in network - - #======================== unused ========================================== - - # running tests - ''' - from NetworkTest import NetworkTest - from NetworkTest import TestNumGoodNeighbors - from NetworkTest import TestNetworkReliability - from NetworkTest import TestMultipleJoins - from NetworkTest import TestStabilityVsRssi - from NetworkTest import TestPerMoteAvailability - from NetworkTest import TestNetworkAvailability - from NetworkTest import TestNumberLinks - from NetworkTest import TestSingleParent - ''' - - ''' - # add stats - self.stats['testCounters'] = {} - self.stats['numRun'] = 0 - self.stats['numNOTRUN'] = 0 - self.stats['numPASS'] = 0 - self.stats['numFAIL'] = 0 - - # local variable - self.dataLock = threading.Lock() - self.networkState = NetworkState.NetworkState(connectParams) - - self.tests = { - GatewayLogParser.AnalysisEventCmd: { - 'param': 'returnTuple', - 'tests': { - IpMgrConnectorSerial.Tuple_dn_getMoteInfo: [ - TestNumGoodNeighbors.TestNumGoodNeighbors, # check for number of good neighbors - TestNetworkReliability.TestNetworkReliability, # check network reliability - ] - }, - }, - GatewayLogParser.AnalysisEventStats: [ - ], - GatewayLogParser.AnalysisEventNotif: { - 'param': 'notifTuple', - 'tests': { - IpMgrConnectorSerial.Tuple_eventMoteJoin: [ - TestMultipleJoins.TestMultipleJoins, # check for multiple joins - ], - GatewayListener.SnapHr: [ - TestPerMoteAvailability.TestPerMoteAvailability, # check per-mote availability - TestNetworkAvailability.TestNetworkAvailability, # check network availability - ] - }, - }, - GatewayLogParser.AnalysisEventFyi: { - 'param': 'type', - 'tests': { - GatewayLogParser.AnalysisEventFyi.SNAPSHOT_END: [ - TestStabilityVsRssi.TestStabilityVsRssi, # check stability vs. RSSI - TestNumberLinks.TestNumberLinks, # analyze number of links - TestSingleParent.TestSingleParent, # count single-parent motes - ] - }, - }, - ''' - - ''' - def _findTestsToRun(self,data): - testDesc = self.tests[type(data)] - if isinstance(testDesc,(list)): - return testDesc - else: - key = getattr(data,testDesc['param']) - if not isinstance(key,str): - key = type(key) - try: - return testDesc['tests'][key] - except KeyError: - pass # happend when no test exists for this case - return None - ''' - - ''' - # run tests associated with data - testToRun = self._findTestsToRun(data) - if testToRun: - - # lock the networkState - self.networkState.dataLock.acquire() - - # build test suite - testSuite = unittest.TestSuite() - for testToRun in testToRun: - - # update stats - self.dataLock.acquire() - testName = testToRun.__name__ - if testName not in self.stats['testCounters']: - self.stats['testCounters'][testName] = 0 - self.stats['testCounters'][testName] += 1 - self.dataLock.release() - - # parametrize test - if testToRun.getType()==NetworkTest.NetworkTest.NETWORK_WIDE: - testSuite.addTest(NetworkTest.NetworkTest.parametrize(data.timestamp,testToRun,self.networkState)) - elif testToRun.getType()==NetworkTest.NetworkTest.LAST_MOTE: - assert(lastmac) - testSuite.addTest(NetworkTest.NetworkTest.parametrize(data.timestamp,testToRun,self.networkState,tuple(lastmac))) - elif testToRun.getType()==NetworkTest.NetworkTest.PER_MOTE: - for mac in self.networkState.motes.keys(): - testSuite.addTest(NetworkTest.NetworkTest.parametrize(data.timestamp,testToRun,self.networkState,mac)) - else: - raise SystemError("unsupported test type {0}".format(testToRun.getType())) - - # log - if log.isEnabledFor(logging.DEBUG): - log.debug("Testing {0} by calling {1}".format(type(data),testSuite)) - - # run tests - testResult = NetworkTestResult.NetworkTestResult() - testSuite.run(testResult) - if testResult.errors: - print testResult.errors - assert(not testResult.errors) - assert(testResult.testsRun==(len(testResult.notRunDesc)+len(testResult.successDesc)+len(testResult.failureDesc))) - assert(len(testResult.failures)==len(testResult.failureDesc)) - - # unlock the networkState - self.networkState.dataLock.release() - - # update stats - self.dataLock.acquire() - self.stats['numRun'] += testResult.testsRun - self.stats['numNOTRUN'] += len(testResult.notRunDesc) - self.stats['numPASS'] += len(testResult.successDesc) - self.stats['numFAIL'] += len(testResult.failureDesc) - self.dataLock.release() - - # publish testResult - for pub in self.networkTestPublishers: - pub.publish(testResult) - ''' \ No newline at end of file diff --git a/Gateway/NetworkTest/NetworkTest.py b/Gateway/NetworkTest/NetworkTest.py deleted file mode 100644 index 689f6be..0000000 --- a/Gateway/NetworkTest/NetworkTest.py +++ /dev/null @@ -1,81 +0,0 @@ -import sys -import unittest - -from Gateway import FormatUtils - -class NetworkTest(unittest.TestCase): - - NETWORK_WIDE = 'network wide' - LAST_MOTE = 'last mote' - PER_MOTE = 'per mote' - - def __init__(self, timestamp, methodName='runTest', networkState=None, mac=None): - - # initialize parent class - unittest.TestCase.__init__(self,methodName) - - # record params - self.timestamp = timestamp - self.networkState = networkState - self.mac = mac - - def setUp(self): - self.notRunDesc = {} - self.successDesc = {} - self.failureDesc = {} - - #======================== setup =========================================== - - @staticmethod - def getType(): - raise NotImplementedError() - - @staticmethod - def parametrize(timestamp, testcase_class,networkState,mac=None): - testloader = unittest.TestLoader() - testnames = testloader.getTestCaseNames(testcase_class) - suite = unittest.TestSuite() - for name in testnames: - suite.addTest(testcase_class(timestamp,name,networkState=networkState,mac=mac)) - return suite - - #======================== public ========================================== - - #==== set desc - - def setNotRunDesc(self,desc): - self.notRunDesc[self._callerName()] = desc - - def setSuccessDesc(self,desc): - self.successDesc[self._callerName()] = desc - - def setFailureDesc(self,desc): - self.failureDesc[self._callerName()] = desc - - #==== get desc - - # raises KeyError when no notRunDesc - def getNotRunDesc(self,methodName): - return self.notRunDesc[methodName] - - def getSuccessDesc(self,methodName): - try: - return self.successDesc[methodName] - except KeyError: - return "No decription provided." - - def getFailureDesc(self,methodName): - try: - return self.failureDesc[methodName] - except KeyError: - return "No decription provided." - - #======================== private ========================================= - - def _callerName(self): - return sys._getframe(2).f_code.co_name - - def _formatMac(self,mac): - assert(len(mac)==8) - - return FormatUtils.formatMacString(mac) \ No newline at end of file diff --git a/Gateway/NetworkTest/TestMultipleJoins.py b/Gateway/NetworkTest/TestMultipleJoins.py deleted file mode 100644 index 62991b4..0000000 --- a/Gateway/NetworkTest/TestMultipleJoins.py +++ /dev/null @@ -1,37 +0,0 @@ -import NetworkTest - -class TestMultipleJoins(NetworkTest.NetworkTest): - - @staticmethod - def getType(): - return NetworkTest.NetworkTest.LAST_MOTE - - #======================== tests =========================================== - - def test_multipleJoins(self): - - mote = self.networkState.motes[self.mac] - try: - self.assertEqual(len(mote.timestamps[Mote.TS_MOTEJOIN]),1) - except AssertionError as err: - - # log an error message - self.setFailureDesc( - 'mote {0} joined {1} times.'.format( - self._formatMac(self.mac), - len(mote.timestamps[Mote.TS_MOTEJOIN]) - ) - ) - - # let the unittest framework know this test failed - raise - else: - # log an success message - self.setSuccessDesc( - 'mote {0} joined {1} time.'.format( - self._formatMac(self.mac), - len(mote.timestamps[Mote.TS_MOTEJOIN]) - ) - ) - - #======================== helpers ========================================= diff --git a/Gateway/NetworkTest/TestNetworkAvailability.py b/Gateway/NetworkTest/TestNetworkAvailability.py deleted file mode 100644 index 1fa263d..0000000 --- a/Gateway/NetworkTest/TestNetworkAvailability.py +++ /dev/null @@ -1,59 +0,0 @@ -import NetworkTest - -class TestNetworkAvailability(NetworkTest.NetworkTest): - - MIN_NETWORKAVAILABILITY = 0.99 - - @staticmethod - def getType(): - return NetworkTest.NetworkTest.NETWORK_WIDE - - #======================== tests =========================================== - - def test_networkAvailability(self): - - # counter network-wide number of packets generated/failed - numTxOk = 0 - numTxFail = 0 - for mac,mote in self.networkState.motes.items(): - if ('numTxOk' in mote.info): - numTxOk += mote.info['numTxOk'] - if ('numTxFail' in mote.info): - numTxFail += mote.info['numTxFail'] - - # stop here if both counters are 0 - if not numTxOk: - # log a notRun message - self.setNotRunDesc( - 'This test could not run because no packets were sent in the network (yet?) (numTxOk=={0} for the network) and so its\'s impossible to calculate a ratio.'.format( - numTxOk - ) - ) - return - - # calculate resulting network availbility - networkAvailability = (1-float(numTxFail)/float(numTxOk)) - - # make sure about threshold - try: - self.assertGreaterEqual(networkAvailability,self.MIN_NETWORKAVAILABILITY) - except AssertionError as err: - # log an error message - self.setFailureDesc( - 'networkAvailability={0} is below the expected {1}'.format( - networkAvailability, - self.MIN_NETWORKAVAILABILITY, - ) - ) - # let the unittest framework know this test failed - raise - else: - # log an success message - self.setSuccessDesc( - 'networkAvailability={0} is better than the expected {1}'.format( - networkAvailability, - self.MIN_NETWORKAVAILABILITY, - ) - ) - - #======================== helpers ========================================= diff --git a/Gateway/NetworkTest/TestNetworkReliability.py b/Gateway/NetworkTest/TestNetworkReliability.py deleted file mode 100644 index 4bde088..0000000 --- a/Gateway/NetworkTest/TestNetworkReliability.py +++ /dev/null @@ -1,59 +0,0 @@ -import NetworkTest - -class TestNetworkReliability(NetworkTest.NetworkTest): - - MIN_NETWORKRELIABILITY = 0.999 - - @staticmethod - def getType(): - return NetworkTest.NetworkTest.NETWORK_WIDE - - #======================== tests =========================================== - - def test_networkReliability(self): - - # counter network-wide number of packets generated/lost - numPktsGenerated = 0 - numPktsLost = 0 - for mac,mote in self.networkState.motes.items(): - if ('packetsReceived' in mote.info): - numPktsGenerated += mote.info['packetsReceived'] - if ('packetsLost' in mote.info): - numPktsGenerated += mote.info['packetsLost'] - - # stop here if both counters are 0 - if (not numPktsGenerated) and (not numPktsLost): - # log a notRun message - self.setNotRunDesc( - 'This test could not run because numPktsGenerated=={0} and numPktsLost=={1} and so its\'s impossible to calculate a ratio.'.format( - numPktsGenerated,numPktsLost - ) - ) - return - - # calculate resulting network reliability - networkReliability = (1-float(numPktsLost)/float(numPktsLost + numPktsGenerated)) - - # make sure about threshold - try: - self.assertGreaterEqual(networkReliability,self.MIN_NETWORKRELIABILITY) - except AssertionError as err: - # log an error message - self.setFailureDesc( - 'networkReliability={0} is below the expected {1}'.format( - networkReliability, - self.MIN_NETWORKRELIABILITY, - ) - ) - # let the unittest framework know this test failed - raise - else: - # log an success message - self.setSuccessDesc( - 'networkReliability={0} is better than the expected {1}'.format( - networkReliability, - self.MIN_NETWORKRELIABILITY, - ) - ) - - #======================== helpers ========================================= diff --git a/Gateway/NetworkTest/TestNumGoodNeighbors.py b/Gateway/NetworkTest/TestNumGoodNeighbors.py deleted file mode 100644 index bb9c5e2..0000000 --- a/Gateway/NetworkTest/TestNumGoodNeighbors.py +++ /dev/null @@ -1,51 +0,0 @@ -import NetworkTest - -class TestNumGoodNeighbors(NetworkTest.NetworkTest): - - MIN_NUMGOODNEIGHBORS = 3 - - @staticmethod - def getType(): - return NetworkTest.NetworkTest.LAST_MOTE - - #======================== tests =========================================== - - def test_numGoodNeighbors(self): - - mote = self.networkState.motes[self.mac] - if ('numGoodNbrs' not in mote.info): - # log a notRun message - self.setNotRunDesc( - 'This test could not run because mote {0} did not report any numGoodNbrs counter (the counters it did report are {1}).'.format( - self._formatMac(self.mac), - mote.info.keys() - ) - ) - return - - try: - self.assertGreaterEqual(mote.info['numGoodNbrs'],self.MIN_NUMGOODNEIGHBORS) - except AssertionError as err: - - # log an error message - self.setFailureDesc( - 'mote {0} has {1} good neighbors, expected at least {2}.'.format( - self._formatMac(self.mac), - mote.info['numGoodNbrs'], - self.MIN_NUMGOODNEIGHBORS - ) - ) - - # let the unittest framework know this test failed - raise - else: - # log an success message - self.setSuccessDesc( - 'mote {0} has {1} good neighbors, which is more than {2}.'.format( - self._formatMac(self.mac), - mote.info['numGoodNbrs'], - self.MIN_NUMGOODNEIGHBORS - ) - ) - - #======================== helpers ========================================= diff --git a/Gateway/NetworkTest/TestNumberLinks.py b/Gateway/NetworkTest/TestNumberLinks.py deleted file mode 100644 index 9fae5ed..0000000 --- a/Gateway/NetworkTest/TestNumberLinks.py +++ /dev/null @@ -1,145 +0,0 @@ -import NetworkTest -from Gateway import NetworkState - -class TestNumberLinks(NetworkTest.NetworkTest): - - MAX_LINKS_MOTE = 180 - MAX_TX_LINKS_AP = 140 - - TXLINK = 2 # TODO replace by attribute of Path object when defined - RXLINK = 3 # TODO replace by attribute of Path object when defined - - @staticmethod - def getType(): - return NetworkTest.NetworkTest.PER_MOTE - - #======================== tests =========================================== - - def test_txRxBalance(self): - - mote = self.networkState.motes[self.mac] - - if mote.info['isAP']: - # log a notRun message - self.setNotRunDesc( - 'This test could not run because mote {0} is an AP (isAP=={1}).'.format( - self._formatMac(self.mac), - mote.info['isAP'] - ) - ) - return - - (numTx,numRx) = self._countLinks(mote) - try: - self.assertGreater(numTx,numRx) - except AssertionError as err: - # log an error message - self.setFailureDesc( - 'mote {0} has {1} TX and {2} RX links'.format( - self._formatMac(self.mac), - numTx, - numRx, - ) - ) - # let the unittest framework know this test failed - raise - else: - # log an success message - self.setSuccessDesc( - 'mote {0} has {1} TX and {2} RX links'.format( - self._formatMac(self.mac), - numTx, - numRx, - ) - ) - - def test_linksMote(self): - - mote = self.networkState.motes[self.mac] - - if mote.info['isAP']: - # log a notRun message - self.setNotRunDesc( - 'This test could not run because mote {0} is an AP (isAP=={1}).'.format( - self._formatMac(self.mac), - mote.info['isAP'] - ) - ) - return - - (numTx,numRx) = self._countLinks(mote) - try: - self.assertLessEqual(numTx+numRx,self.MAX_LINKS_MOTE) - except AssertionError as err: - # log an error message - self.setFailureDesc( - 'mote {0} has {1} links (both TX and RX), which is more than the recommended max {2}'.format( - self._formatMac(self.mac), - numTx+numRx, - self.MAX_LINKS_MOTE, - ) - ) - # let the unittest framework know this test failed - raise - else: - # log an success message - self.setSuccessDesc( - 'mote {0} has {1} links (both TX and RX), which is less than the recommended max {2}'.format( - self._formatMac(self.mac), - numTx+numRx, - self.MAX_LINKS_MOTE, - ) - ) - - def test_rxLinksAp(self): - - mote = self.networkState.motes[self.mac] - - if mote.info['isAP']: - # log a notRun message - self.setNotRunDesc( - 'This test could not run because mote {0} is an AP (isAP=={1}).'.format( - self._formatMac(self.mac), - mote.info['isAP'] - ) - ) - return - - (numTx,numRx) = self._countLinks(mote) - try: - self.assertLessEqual(numRx,self.MAX_TX_LINKS_AP) - except AssertionError as err: - # log an error message - self.setFailureDesc( - 'AP mote {0} has {1} RX links, which is more than the recommended max {2}'.format( - self._formatMac(self.mac), - numRx, - self.MAX_TX_LINKS_AP, - ) - ) - # let the unittest framework know this test failed - raise - else: - # log an success message - self.setSuccessDesc( - 'AP mote {0} has {1} RX links, which is less than the recommended max {2}'.format( - self._formatMac(self.mac), - numRx, - self.MAX_TX_LINKS_AP, - ) - ) - - #======================== helpers ========================================= - - def _countLinks(self,mote): - numTx = 0 - numRx = 0 - - for neighbor,path in mote.paths.items(): - assert(type(path)==NetworkState.Path) - if path.direction==self.TXLINK: - numTx += path.numLinks - elif path.direction==self.RXLINK: - numRx += path.numLinks - - return (numTx,numRx) diff --git a/Gateway/NetworkTest/TestPerMoteAvailability.py b/Gateway/NetworkTest/TestPerMoteAvailability.py deleted file mode 100644 index aba61e3..0000000 --- a/Gateway/NetworkTest/TestPerMoteAvailability.py +++ /dev/null @@ -1,77 +0,0 @@ -import NetworkTest - -class TestPerMoteAvailability(NetworkTest.NetworkTest): - - MIN_MOTEAVAILABILITY = 0.99 - - @staticmethod - def getType(): - return NetworkTest.NetworkTest.LAST_MOTE - - #======================== tests =========================================== - - def test_perMoteAvailability(self): - - mote = self.networkState.motes[self.mac] - - #==== filter edge cases where the test can not be run - - if 'numTxOk' not in mote.info: - # log a notRun message - self.setNotRunDesc( - 'This test could not run because mote {0} did not report any numTxOk counter (the counters it did report are {1}).'.format( - self._formatMac(self.mac), - mote.info.keys() - ) - ) - return - - if 'numTxFail' not in mote.info: - # log a notRun message - self.setNotRunDesc( - 'This test could not run because mote {0} did not report any numTxFail counter (the counters it did report are {1}).'.format( - self._formatMac(self.mac), - mote.info.keys() - ) - ) - return - - if not mote.info['numTxOk']: - # log a notRun message - self.setNotRunDesc( - 'This test could not run because mote {0} did not send any packets succesfully (yet?) (numTxOk=={1}) and so its\'s impossible to calculate a ratio.'.format( - self._formatMac(self.mac), - mote.info['numTxOk'] - ) - ) - return - - #==== run the test - - availability = (1-float(mote.info['numTxFail'])/float(mote.info['numTxOk'])) - try: - self.assertGreaterEqual(availability,self.MIN_MOTEAVAILABILITY) - except AssertionError as err: - - # log an error message - self.setFailureDesc( - 'availability for mote {0} is {1}, expected at least {2}.'.format( - self._formatMac(self.mac), - availability, - self.MIN_MOTEAVAILABILITY - ) - ) - - # let the unittest framework know this test failed - raise - else: - # log an success message - self.setSuccessDesc( - 'availability for mote {0} is {1}, which is better than {2}.'.format( - self._formatMac(self.mac), - availability, - self.MIN_MOTEAVAILABILITY - ) - ) - - #======================== helpers ========================================= diff --git a/Gateway/NetworkTest/TestSingleParent.py b/Gateway/NetworkTest/TestSingleParent.py deleted file mode 100644 index 407a1f9..0000000 --- a/Gateway/NetworkTest/TestSingleParent.py +++ /dev/null @@ -1,56 +0,0 @@ -import NetworkTest -from Gateway import NetworkState - -class TestSingleParent(NetworkTest.NetworkTest): - - TXLINK = 2 # TODO replace by attribute of Path object when defined - RXLINK = 3 # TODO replace by attribute of Path object when defined - - @staticmethod - def getType(): - return NetworkTest.NetworkTest.NETWORK_WIDE - - #======================== tests =========================================== - - def test_SingleSingleParentMote(self): - - numSingleParentMotes = 0 - for mac,mote in self.networkState.motes.items(): - (numTx,numRx) = self._countLinks(mote) - if numTx==1: - numSingleParentMotes += 1 - try: - self.assertEqual(numSingleParentMotes,1) - except AssertionError as err: - - # log an error message - self.setFailureDesc( - 'there are {0} single parent motes in the network; we expected only 1.'.format( - numSingleParentMotes, - ) - ) - - # let the unittest framework know this test failed - raise - else: - # log an success message - self.setSuccessDesc( - 'there are {0} single parent motes in the network; which is expected and normal.'.format( - numSingleParentMotes, - ) - ) - - #======================== helpers ========================================= - - def _countLinks(self,mote): - numTx = 0 - numRx = 0 - - for neighbor,path in mote.paths.items(): - assert(type(path)==NetworkState.Path) - if path.direction==self.TXLINK: - numTx += 1 - elif path.direction==self.RXLINK: - numRx += 1 - - return (numTx,numRx) diff --git a/Gateway/NetworkTest/TestStabilityVsRssi.py b/Gateway/NetworkTest/TestStabilityVsRssi.py deleted file mode 100644 index fe78207..0000000 --- a/Gateway/NetworkTest/TestStabilityVsRssi.py +++ /dev/null @@ -1,88 +0,0 @@ -import NetworkTest - -class TestStabilityVsRssi(NetworkTest.NetworkTest): - - MIN_NUM_PACKETS = 30 - THRES_HIGH_RSSI = -70 - THRES_HIGH_STAB = 0.7 - THRES_LOW_RSSI = -80 - THRES_LOW_STAB = 0.5 - - @staticmethod - def getType(): - return NetworkTest.NetworkTest.PER_MOTE - - #======================== tests =========================================== - - def test_stabilityVsRssi(self): - - mote = self.networkState.motes[self.mac] - - goOn = False - for neighbor in mote.neighbors: - if neighbor.numTxPk>self.MIN_NUM_PACKETS and neighbor.rsl<0: - goOn = True - break - - if not goOn: - # log a notRun message - self.setNotRunDesc( - 'This test could not run because mote {0} does not have any neighbor to which it has sent more than {1} packet, and which reports a negative RSSI.'.format( - self._formatMac(self.mac), - self.MIN_NUM_PACKETS - ) - ) - return - - for neighbor in mote.neighbors: - if neighbor.numTxPk>self.MIN_NUM_PACKETS and neighbor.rsl<0: - linkStability = 1-float(neighbor.numTxFail)/float(neighbor.numTxPk) - linkRssi = neighbor.rsl - - try: - self.assertFalse(linkRssi>self.THRES_HIGH_RSSI and linkStabilityself.THRES_LOW_RSSI and linkStability30}: NOTRUN: {2}".format(FormatUtils.formatTimestamp(timestamp), - testName, - desc)) - for (timestamp,testName,desc) in testResult.successDesc: - self.testLogger.log(self.PASS,"{0} {1:>30}: {2}".format(FormatUtils.formatTimestamp(timestamp), - testName, - desc)) - for (timestamp,testName,desc) in testResult.failureDesc: - self.testLogger.log(self.FAIL,"{0} {1:>30}: {2}".format(FormatUtils.formatTimestamp(timestamp), - testName, - desc)) - - #======================== private ========================================= \ No newline at end of file diff --git a/Gateway/NetworkTestPublisher/PublishScreen.py b/Gateway/NetworkTestPublisher/PublishScreen.py deleted file mode 100644 index 3b59232..0000000 --- a/Gateway/NetworkTestPublisher/PublishScreen.py +++ /dev/null @@ -1,26 +0,0 @@ -import logging -class NullHandler(logging.Handler): - def emit(self, record): - pass -log = logging.getLogger('PublishScreen') -log.setLevel(logging.ERROR) -log.addHandler(NullHandler()) - -import NetworkTestPublisher - -class PublishScreen(NetworkTestPublisher.NetworkTestPublisher): - - #======================== public ========================================== - - def publish(self, testResult): - - # log - log.debug("publishing {0}".format(testResult)) - - output = [] - output += ["NOTRUN {0} {1}: {2}".format(timestamp,testName,desc) for (timestamp,testName,desc) in testResult.notRunDesc] - output += ["PASS {0} {1}: {2}".format(timestamp,testName,desc) for (timestamp,testName,desc) in testResult.successDesc] - output += ["FAIL {0} {1}: {2}".format(timestamp,testName,desc) for (timestamp,testName,desc) in testResult.failureDesc] - print '\n'.join(output) - - #======================== private ========================================= \ No newline at end of file diff --git a/Gateway/NetworkTestResult.py b/Gateway/NetworkTestResult.py deleted file mode 100644 index 972ad9d..0000000 --- a/Gateway/NetworkTestResult.py +++ /dev/null @@ -1,47 +0,0 @@ -import unittest - -class NetworkTestResult(unittest.TestResult): - - def __init__(self): - - # initialize parent class - unittest.TestResult.__init__(self) - - # additional variables - self.notRunDesc = [] - self.successDesc = [] - self.failureDesc = [] - - def addSuccess(self, test): - - # call function from parent class - unittest.TestResult.addSuccess(self,test) - - # add in descriptions of the successes (or notRun) - try: - self.notRunDesc.append( (test.timestamp, # timestamp - test._testMethodName, # testName - test.getNotRunDesc(test._testMethodName))) # desc - except KeyError: - self.successDesc.append( (test.timestamp, # timestamp - test._testMethodName, # testName - test.getSuccessDesc(test._testMethodName))) # desc - - def addFailure(self, test, err): - - # call function from parent class - unittest.TestResult.addFailure(self,test,err) - - # add in descriptions of the failures - self.failureDesc.append( (test.timestamp, # timestamp - test._testMethodName, # testName - test.getFailureDesc(test._testMethodName))) # desc - - def __str__(self): - output = [] - output += [unittest.TestResult.__str__(self)] - if self.failures: - output += ["failures:"] - for fd in self.failureDesc: - output += [" - {0}".format(fd)] - return '\n'.join(output) diff --git a/Gateway/StructUtils.py b/Gateway/StructUtils.py deleted file mode 100644 index 2e5bc77..0000000 --- a/Gateway/StructUtils.py +++ /dev/null @@ -1,148 +0,0 @@ -''' -Structure utilities - -Build classes from a description of their fields -''' - -from collections import namedtuple -import struct - -# An object description is a (ordered) list of field descriptions -# TODO: describe valid types -# TODO: add enums -# TODO: add printing hook, default to str() - -ApiStructField = namedtuple('ApiStructField', 'name type len') - -# obj = [ { 'name': 'field_name', 'type': [int, array, boolean], 'len': l }, ] - - -def parse_field(field, data): - index = 0 - if field.type is 'array': - desc = '{0}B'.format(field.len) - val = list(struct.unpack(desc, data[0:field.len])) - elif field.type is 'int' : - if field.len is 1: - val = struct.unpack('B', data[0])[0] - elif field.len is 2: - val = struct.unpack('!H', data[0:2])[0] - elif field.len is 4: - val = struct.unpack('!I', data[0:4])[0] - elif field.type is 'sint' : - if field.len is 1: - val = struct.unpack('b', data[0])[0] - elif field.len is 2: - val = struct.unpack('!h', data[0:2])[0] - elif field.len is 4: - val = struct.unpack('!i', data[0:4])[0] - elif field.type is 'asn' : - val = 0 - for i in list(struct.unpack('!5B', data[0:5])) : - val = val * 256 + i - elif field.type is 'boolean': - val = ord(data[0]) == 1 - pass - else: - raise Exception('unknown field type:' + field.type) - index = field.len - return (index, val) - -def parse(obj_type, data): - obj_vals = {} - index = 0 - # TODO: check length - for f in obj_type.fields: - if (len(data) > index) : - (index_incr, val) = parse_field(f, data[index:]) - else : - (index_incr, val) = (0, None) - obj_vals[f.name] = val - index += index_incr - return obj_type(**obj_vals) - -def parse_self(self, data): - obj_vals = {} - index = 0 - # TODO: check length - for f in self.fields: - if (len(data) > index) : - (index_incr, val) = parse_field(f, data[index:]) - else : - (index_incr, val) = (0, None) - obj_vals[f.name] = val - index += index_incr - return self.__class__(**obj_vals) - -def parse_array(typeArrayItem, data): - res = [] - offset = 0 - while (len(data) - offset >= typeArrayItem.size): - item = parse(typeArrayItem, data[offset : offset+typeArrayItem.size]) - if item: - res.append(item) - offset += typeArrayItem.size - return res - -def pair_to_string(name, val): - if type(val) == list: - return '%16s: %s\n' % (name, '-'.join(['%02X' % c for c in val])) - else: - return '%16s: %s\n' % (name, str(val)) - -def to_string(self): - 'Generate a pretty printed string' - return ''.join([pair_to_string(f.name, self.__getattribute__(f.name)) - for f in self.fields]) - -def serialize(self): - 'Serialize the structure in its binary API format' - resp = '' - for f in self.fields: - val = self.__getattribute__(f.name) - if f.type is 'array': - desc = '{0}B'.format(f.len) - resp += struct.pack(desc, *val) - elif f.type is 'int' : - if f.len is 1: - resp += struct.pack('B', val) - elif f.len is 2: - resp += struct.pack('!H', val) - elif f.len is 4: - resp += struct.pack('!I', val) - elif f.type is 'sint' : - if f.len is 1: - resp += struct.pack('b', val) - elif f.len is 2: - resp += struct.pack('!h', val) - elif f.len is 4: - resp += struct.pack('!i', val) - elif f.type is 'boolean': - resp += struct.pack('B', val) - elif f.type is 'asn' : - v = val - ar = [0, 0, 0, 0, 0] - for i in range(0, 5) : - l[4-i] = v % 256 - v = v / 256 - resp += struct.pack('5B', *v) - else: - raise Exception('unknown field type:' + field.type) - return resp - - -# TODO: instead of passing in an array of field structs, -# build the array from a dict -def synthesize(obj_name, fields): - 'Create an API object class from its list of fields' - field_names = ' '.join([f.name for f in fields]) - obj_type = namedtuple(obj_name, field_names) - # add some useful stuff - obj_type.fields = fields - # TODO: somehow parse needs to be a class method (really a factory) - #obj_type.parse = parse - #obj_type.parse_s = parse_self - obj_type.__str__ = to_string - obj_type.serialize = serialize - obj_type.size = sum(f.len for f in fields) - return obj_type diff --git a/Gateway/snap_version.py b/Gateway/snap_version.py deleted file mode 100644 index 08bc4da..0000000 --- a/Gateway/snap_version.py +++ /dev/null @@ -1,8 +0,0 @@ -# -# The version in this file is automatically updated by the release script. -# -# PLEASE DO NOT CHANGE THE SYNTAX OF THE VERSION VALUE -# -VERSION = (1, 0, 1, 0) - -# END OF FILE diff --git a/PKG-INFO b/PKG-INFO index b20f96c..d56eeee 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: SmartMeshSDK -Version: 1.0.3.73 +Version: 1.0.4.110 Summary: UNKNOWN Home-page: UNKNOWN Author: Linear Technology diff --git a/SmartMeshSDK/ApiConnector.py b/SmartMeshSDK/ApiConnector.py index 472f879..c21aa3a 100644 --- a/SmartMeshSDK/ApiConnector.py +++ b/SmartMeshSDK/ApiConnector.py @@ -39,10 +39,10 @@ def logDump(buf, msg = None, level=logging.DEBUG): class ApiConnector(object): ''' \ingroup ApiConnector + \brief Base class for all connector objects. ''' - def __init__(self, maxQSize = DEFAULT_Q_SIZE): self.maxQSize = maxQSize self.queue = NotifQueue(self.maxQSize) diff --git a/SmartMeshSDK/ApiDefinition/ApiDefinition.py b/SmartMeshSDK/ApiDefinition/ApiDefinition.py index 94fbb46..bec28f1 100644 --- a/SmartMeshSDK/ApiDefinition/ApiDefinition.py +++ b/SmartMeshSDK/ApiDefinition/ApiDefinition.py @@ -1,7 +1,7 @@ #!/usr/bin/python import types -from ApiException import CommandError +from SmartMeshSDK.ApiException import CommandError import logging class NullHandler(logging.Handler): @@ -20,6 +20,7 @@ class FieldFormats(object): INT = 'int' INTS = 'ints' HEXDATA = 'hex' + FLOAT = 'float' class FieldOptions(object): ''' @@ -74,7 +75,7 @@ def __init__(self,fieldDef,fieldOptions): def isValidValue(self,val): # check format and length if self.format==FieldFormats.STRING: - if ( type(val)!=types.StringType or + if ( (type(val) not in [types.StringType,types.UnicodeType]) or len(val)>self.length ): return False @@ -85,14 +86,12 @@ def isValidValue(self,val): if ( (type(val)!=types.IntType and type(val)!=types.LongType) or val>pow(2,8*self.length) ): - print "****", type(val), types.IntType, val, pow(2,8*self.length), val>pow(2,8*self.length) return False elif self.format==FieldFormats.INTS: if ( (type(val)!=types.IntType and type(val)!=types.LongType) or val>(pow(2,8*self.length)/2) or val<(-pow(2,8*self.length)/2) ): - print "****", type(val), types.IntType, val, pow(2,8*self.length), val>pow(2,8*self.length) return False elif self.format==FieldFormats.HEXDATA: if type(val)!=types.ListType and type(val)!=types.TupleType: @@ -104,6 +103,9 @@ def isValidValue(self,val): return False if i>=pow(2,8): return False + elif self.format==FieldFormats.FLOAT: + if ( (type(val)!=types.IntType and type(val)!=types.FloatType) ): + return False else: raise SystemError('unknown field format='+self.format) @@ -116,6 +118,7 @@ def isValidValue(self,val): class ApiDefinition(object): ''' \ingroup ApiDefinition + \brief Base class for all API definitions objects. ''' @@ -540,10 +543,10 @@ def isValidFieldFormatting(self,commandArray, # check whether this value is valid if thisField.isValidValue(fieldValue)==False: - raise CommandError(CommandError.MALFORMED_FIELD, - ' commandArray='+str(commandArray)+\ - ' fieldName='+fieldName+\ - ' value='+str(fieldValue)) + raise CommandError( + CommandError.MALFORMED_FIELD, + 'commandArray={0} fieldName={1} fieldValue={2}'.format(commandArray,fieldName,fieldValue) + ) def validateRequest(self,commandArray,fields): ''' diff --git a/SmartMeshSDK/ApiDefinition/ByteArraySerializer.py b/SmartMeshSDK/ApiDefinition/ByteArraySerializer.py index 0a90096..e012b41 100644 --- a/SmartMeshSDK/ApiDefinition/ByteArraySerializer.py +++ b/SmartMeshSDK/ApiDefinition/ByteArraySerializer.py @@ -2,10 +2,12 @@ import struct import operator -import ApiDefinition -from ApiException import CommandError import types +import ApiDefinition +from SmartMeshSDK.ApiException import CommandError +from SmartMeshSDK import FormatUtils + import logging class NullHandler(logging.Handler): def emit(self, record): @@ -17,6 +19,7 @@ def emit(self, record): class ByteArraySerializer(object): ''' \ingroup ApiDefinition + \brief Serializer/deserializer for byte arrays. ''' @@ -25,23 +28,33 @@ def __init__(self,ApiDef): def serialize(self,commandArray,fieldsToFill): - log.debug( "serialize ...\n" + \ - " - commandArray="+str(commandArray)+"\n" \ - " - fieldsToFill="+str(fieldsToFill)) + # log + if log.isEnabledFor(logging.DEBUG): + output = [] + output += ["serialize ..."] + output += ["- commandArray: {0}".format(commandArray)] + output += ["- fieldsToFill: {0}".format(fieldsToFill)] + output = '\n'.join(output) + log.debug(output) + # validate input if type(commandArray)!=types.ListType and type(commandArray)!=types.TupleType: raise TypeError("First parameter should be a list or tuple, not "+str(type(commandArray))) + # initialize the output byteArray = [] for cmdCounter in range(len(commandArray)): # packet payload - definition = self.ApiDef.getDefinition(ApiDefinition.ApiDefinition.COMMAND, - commandArray[:cmdCounter+1]) + definition = self.ApiDef.getDefinition( + ApiDefinition.ApiDefinition.COMMAND, + commandArray[:cmdCounter+1] + ) fields = [ApiDefinition.Field(fieldRaw,self.ApiDef.fieldOptions) for fieldRaw in definition['request']] + for field in fields: thisFieldByteArray = [] if field.name in ApiDefinition.ApiDefinition.RESERVED: @@ -53,17 +66,16 @@ def serialize(self,commandArray,fieldsToFill): ) ) else: - val = fieldsToFill[field.name] + val = fieldsToFill[field.name] if field.format==ApiDefinition.FieldFormats.STRING: - for car in val: - thisFieldByteArray += [ord(car) for car in val] + thisFieldByteArray += [ord(car) for car in val] elif field.format==ApiDefinition.FieldFormats.BOOL: thisFieldByteArray.append(val) elif field.format==ApiDefinition.FieldFormats.INT: - thisFieldByteArray += [operator.mod(int(val>>(8*i)), 0x100) for i in xrange(field.length-1, -1, -1)] + thisFieldByteArray += [operator.mod(int(val>>(8*i)), 0x100) for i in xrange(field.length-1, -1, -1)] elif field.format==ApiDefinition.FieldFormats.INTS: if field.length==1: @@ -82,7 +94,8 @@ def serialize(self,commandArray,fieldsToFill): else: raise SystemError('unknown field format='+field.format) - + + # padding while len(thisFieldByteArray)=len(byteArray) ): continueParsing = False - - if ( - (not notRcOk) and - (index!=len(byteArray)) - ): - raise CommandError(CommandError.TOO_MANY_BYTES,index) - - log.debug( "... deserialized into\n"+ \ - " - nameArray=" +str(nameArray)+"\n" \ - " - returnFields="+str(returnFields) ) + if log.isEnabledFor(logging.DEBUG): + output = [] + output += ["... deserialized into"] + output += ["- nameArray: {0}".format(nameArray)] + output += ["- returnFields: {0}".format(returnFields)] + output = '\n'.join(output) + log.debug(output) return nameArray,returnFields - diff --git a/SmartMeshSDK/ApiDefinition/HartMgrDefinition.py b/SmartMeshSDK/ApiDefinition/HartMgrDefinition.py index 3af6f4e..7d464fd 100644 --- a/SmartMeshSDK/ApiDefinition/HartMgrDefinition.py +++ b/SmartMeshSDK/ApiDefinition/HartMgrDefinition.py @@ -19,6 +19,7 @@ def emit(self, record): ## # \ingroup ApiDefinition # + class HartMgrDefinition(ApiDefinition.ApiDefinition): ''' \brief API definition for the HART manager. @@ -33,10 +34,11 @@ class HartMgrDefinition(ApiDefinition.ApiDefinition): BOOL = ApiDefinition.FieldFormats.BOOL INT = ApiDefinition.FieldFormats.INT INTS = ApiDefinition.FieldFormats.INTS - FLOAT = 'float' + FLOAT = ApiDefinition.FieldFormats.FLOAT HEXDATA = ApiDefinition.FieldFormats.HEXDATA RC = ApiDefinition.ApiDefinition.RC SUBID1 = ApiDefinition.ApiDefinition.SUBID1 + LIST = 'list' # Enumerations # fieldOptions is a list of [ value, description, ?? ] @@ -320,6 +322,32 @@ class HartMgrDefinition(ApiDefinition.ApiDefinition): ], }, }, + { + 'id' : 'netMoteQuarantine', + 'name' : 'MoteQuarantine', + 'description': '', + 'response' : { + 'FIELDS': [ + ['moteId', INT, 4, None], + ['macAddr', STRING, 32, None], + ['reason', STRING, 64, None], # TODO: length + ], + }, + }, + { + 'id' : 'netMoteJoinQuarantine', + 'name' : 'MoteJoinQuarantine', + 'description': '', + 'response' : { + 'FIELDS': [ + ['moteId', INT, 4, None], + ['macAddr', STRING, 32, None], + ['reason', STRING, 64, None], # TODO: length + ['userData', STRING, 64, None], # TODO: length + ], + }, + }, + { 'id' : 'netMoteUnknown', 'name' : 'MoteUnknown', @@ -478,6 +506,19 @@ class HartMgrDefinition(ApiDefinition.ApiDefinition): ], }, }, + { + 'id' : 'netTransportTimeout', + 'name' : 'TransportTimeout', + 'description': '', + 'response' : { + 'FIELDS': [ + ['srcMacAddr', STRING, 32, None], + ['destMacAddr', STRING, 32, None], + ['timeoutType', STRING, 32, None], # TODO: timeout type + ['callbackId', INT, 4, None], + ], + }, + }, # TODO: redundancy events: sysRdntModeChange, sysRdntPeerStatusChange # TODO: alarm open and close have sub-events @@ -851,6 +892,19 @@ def deserialize_getStats(self, cmd_metadata, xmlrpc_resp): net_stats[field.name] = '' # default value return net_stats + def deserialize_getSourceRoute(self, cmd_metadata, xmlrpc_resp): + net_stats = {} + fields = self.getResponseFields(self.COMMAND, [cmd_metadata['name']]) + # parse the Statistics element + resp_dict = self._parse_xmlobj(xmlrpc_resp, 'SourceRoute', None) + # Ug. deserialization for this case is heavily dependant on response structure + for path in ['primaryPath', 'secondaryPath']: + if path in resp_dict and 'macAddr' in resp_dict[path]: + resp_dict[path] = resp_dict[path]['macAddr'] + else: + resp_dict[path] = [] + return resp_dict + # Commands commands = [ # Get Config commands @@ -993,12 +1047,31 @@ def deserialize_getStats(self, cmd_metadata, xmlrpc_resp): ['temperature', FLOAT, 4, None], # deg C # added in Manager 4.1.0.2 ['numLostPackets', INT, 4, None], + # added in Manager 4.1.0.11 + ['latencyToMote', INT, 4, None], ], }, 'serializer' : 'serialize_getMoteStats', 'deserializer' : 'deserialize_getStats', }, - + { + 'id' : 'getConfig', + 'name' : 'getSourceRoute', + 'description': 'Get the Source Route for a specific Mote', + 'request' : [ + ['destMacAddr', STRING, 25, None], + ], + 'response' : { + 'SourceRoute': [ + ['destMacAddr', STRING, 25, None], + ['primaryPath', LIST, 16, None], + ['secondaryPath', LIST, 16, None], + ], + }, + 'serializer' : 'serialize_getConfig', + 'serializerParam': ['config', 'SourceRoutes', 'SourceRoute'], + 'deserializer' : 'deserialize_getSourceRoute', + }, # getMotes -- return LIST of motess { 'id' : 'getConfig', @@ -1094,7 +1167,7 @@ def deserialize_getStats(self, cmd_metadata, xmlrpc_resp): ], 'response' : { 'Security': [ - ['securityMode', STRING, 16, 'securityMode'], + ['securityMode', STRING, 20, 'securityMode'], ['acceptHARTDevicesOnly', BOOL, 1, None], ], }, @@ -1303,7 +1376,6 @@ def deserialize_getStats(self, cmd_metadata, xmlrpc_resp): 'request' : [ ['macAddr', STRING, 25, None], ['name', STRING, 16, None], - ['powerSource', STRING, 16, None], # TODO: enum ['enableRouting', BOOL, 1, None], ], 'response' : { @@ -1369,13 +1441,13 @@ def deserialize_getStats(self, cmd_metadata, xmlrpc_resp): 'name' : 'setSecurity', 'description': 'Set security configuration', 'request' : [ - ['securityMode', STRING, 16, 'securityMode'], + ['securityMode', STRING, 20, 'securityMode'], ['commonJoinKey', HEXDATA, 16, None], ['acceptHARTDevicesOnly', BOOL, 1, None], ], 'response' : { 'Security': [ - ['securityMode', STRING, 16, 'securityMode'], + ['securityMode', STRING, 20, 'securityMode'], ['acceptHARTDevicesOnly', BOOL, 1, None], ], }, @@ -1485,7 +1557,7 @@ def deserialize_getStats(self, cmd_metadata, xmlrpc_resp): 'serializer' : 'serialize_setConfig', 'serializerParam' : ['config', 'Motes', 'Mote'], }, - + # Send packet commands { 'id' : 'sendRequest', @@ -1496,7 +1568,7 @@ def deserialize_getStats(self, cmd_metadata, xmlrpc_resp): ['domain', STRING, 16, 'appDomain'], ['priority', STRING, 16, 'packetPriority'], ['reliable', BOOL, 0, None], - ['data', HEXDATA, None, None], + ['data', HEXDATA, None,None], ], 'response' : { FIELDS : [ @@ -1672,7 +1744,7 @@ def deserialize_getStats(self, cmd_metadata, xmlrpc_resp): ], }, }, - + # activateAdvertising { 'id' : 'activateAdvertising', @@ -1688,11 +1760,54 @@ def deserialize_getStats(self, cmd_metadata, xmlrpc_resp): ], }, }, - + + # startOtap + { + 'id' : 'startOtap', + 'name' : 'startOtap', + 'description': 'This command initiates the OTAP (Over-The-Air-Programming) process to upgrade software on motes and the Access Point. By default, the process will retry the OTAP file transmission 100 times.', + 'request' : [ + ], + 'response' : { + FIELDS : [ + ['result', STRING, 32, None], + ], + }, + }, + + # startOtapWithRetries + { + 'id' : 'startOtapWithRetries', + 'name' : 'startOtapWithRetries', + 'description': 'This command initiates the OTAP (Over-The-Air-Programming) process to upgrade software for motes and the Access Point, using the specified number of retries.', + 'request' : [ + ['retries', INT, 1, None] + ], + 'response' : { + FIELDS : [ + ['result', STRING, 32, None], + ], + }, + }, + + # cancelOtap + { + 'id' : 'cancelOtap', + 'name' : 'cancelOtap', + 'description': 'This command cancels the OTAP (Over-The-Air-Programming) process to upgrade software on motes and the access point.', + 'request' : [ + ], + 'response' : { + FIELDS : [ + ['result', STRING, 32, None], + ], + }, + }, + # decommissionDevice { 'id' : 'decommissionDevice', - 'name' : 'decommission', + 'name' : 'decommissionDevice', 'description': 'Decommission a device in the network', 'request' : [ ['macAddr', STRING, 25, None], @@ -1704,10 +1819,25 @@ def deserialize_getStats(self, cmd_metadata, xmlrpc_resp): }, }, + # promoteToOperational + { + 'id' : 'promoteToOperational', + 'name' : 'promoteToOperational', + 'description': 'Promote a quarantined device to operational', + 'request' : [ + ['macAddr', STRING, 25, None], + ], + 'response' : { + FIELDS : [ + ['result', STRING, 32, None], + ], + }, + }, + # pingMote { 'id' : 'pingMote', - 'name' : 'ping', + 'name' : 'pingMote', 'description': '''Ping the specified mote. A Net Ping Reply event notification will contain the mote's response.''', 'request' : [ ['macAddr', STRING, 25, None], @@ -1745,9 +1875,7 @@ def deserialize_getStats(self, cmd_metadata, xmlrpc_resp): ], }, }, - - # TODO startLocation stopLocation - + # reset { 'id' : 'reset', @@ -1829,4 +1957,18 @@ def deserialize_getStats(self, cmd_metadata, xmlrpc_resp): }, # cli -- the CLI command is deprecated + + { + 'id' : 'cli', + 'name' : 'cli', + 'description': 'This command tunnels a given command through to the manager\'s Command Line Interface (CLI). The CLI command can be called by only one XML API client at a time. The response to the given CLI command is tunneled back to the client via the notifications channel. To receive the CLI notification, the client must be subscribed to CLI notifications (see Notification Channel)', + 'request' : [ + ['command', STRING, 128, None], + ], + 'response' : { + FIELDS : [ + ['result', STRING, 32, None], + ], + }, + }, ] diff --git a/SmartMeshSDK/ApiDefinition/HartMoteDefinition.py b/SmartMeshSDK/ApiDefinition/HartMoteDefinition.py index 44d98f1..683b29e 100644 --- a/SmartMeshSDK/ApiDefinition/HartMoteDefinition.py +++ b/SmartMeshSDK/ApiDefinition/HartMoteDefinition.py @@ -6,6 +6,7 @@ class HartMoteDefinition(ApiDefinition.ApiDefinition): ''' \ingroup ApiDefinition + \brief API definition for the IP mote. \note This class inherits from ApiDefinition. It redefines the attributes of @@ -88,17 +89,10 @@ def serializeSetNv(self,commandArray,fieldsToFill): # byteArray now contains the following bytes: # [0] is the paramId - # [1] is the targetMemory value - # [2:] are the parameters - - # remove the targetFlag from the packet - targetMemory = byteArray.pop(1) + # [1:] are the parameters # prepend the flagList, the list of flags to be set in the header - if targetMemory: - flaglist = [self.FLAG_SETNV_RAM,] - else: - flaglist = [] + flaglist = [] # add the 4B reserved field byteArray = [0,0,0,0]+byteArray @@ -127,7 +121,7 @@ def serializeGetNv(self,commandArray,fieldsToFill): # ApiDefinition for a full description of the structure of this field. fieldOptions = { RC: [ - [0, 'RC_OK', 'Operation was successfully completed.'], + [0, 'RC_OK', 'Operation was successfullycompleted.'], [3, 'RC_BUSY', 'Resource unavailable'], [4, 'RC_INVALID_LEN', 'Invalid length'], [5, 'RC_INVALID_STATE', 'Invalid state'], @@ -141,6 +135,7 @@ def serializeGetNv(self,commandArray,fieldsToFill): [13, 'RC_INCOMPLETE_JOIN_INFO', 'Incomplete join info'], [14, 'RC_NOT_FOUND', 'Resource not found'], [15, 'RC_INVALID_VALUE', 'Invalid value'], + [19, 'RC_ERASE_FAIL', 'Erase operation failed'], ], 'ApplicationDomain' : [ [0x00, 'Publish', ''], @@ -163,12 +158,13 @@ def serializeGetNv(self,commandArray,fieldsToFill): [0x00, 'Init', 'The mote is in the process of booting'], [0x01, 'Idle', 'The mote is accepting configuration commands. Upon receiving a join command, the mote moves into the Searching state. The Idle state is equivalent to the HART "Low Power" state.'], [0x02, 'Searching', 'The mote\'s receiver is on with a configurable duty cycle while the mote is actively searching for a network. The Searching state is equivalent to the HART "Active Search" state or "Passive Search," depending on the duty cycle setting.'], - [0x03, 'Negotiating', 'The mote has detected a network and has received a join request from the manager'], + [0x03, 'Negotiating', 'The mote has detected a network and has sent a join request to the manager'], [0x04, 'Connected', 'The mote has joined the network and established communication with the network manager. The Connected state is equivalent to the HART "Quarantine" state.'], [0x05, 'Operational', 'The mote has links to both the network manager and gateway, and is ready to send data'], [0x06, 'Disconnected', 'The mote has disconnected from the network'], [0x07, 'Radio test', 'The mote is in radio test mode'], - [0x08, 'Promiscuous Listen', 'The mote received search command and is in promiscuous listen mode'], + [0x08, 'Promiscuous Listen', 'The mote received a search command and is in promiscuous listen mode'], + [0x09, 'Suspended', 'The mote entered suspended state after having processed command 972'], ], 'ServiceState' : [ [0x00, 'Inactive', ''], @@ -181,7 +177,7 @@ def serializeGetNv(self,commandArray,fieldsToFill): [0x02, 'Rechargeable/Scavenging', ''], ], 'OTAPLockout' : [ - [0x00, 'OTAP allowed', ''], + [0x00, 'OTAP allowed (default)', ''], [0x01, 'OTAP disabled', ''], ], 'Priority' : [ @@ -195,16 +191,16 @@ def serializeGetNv(self,commandArray,fieldsToFill): [0x02, 'Continuous wave', ''], ], 'hrCounterMode' : [ - [0, 'rollover', ''], - [1, 'saturating', ''], + [0, 'Rollover', ''], + [1, 'Saturating', ''], + ], + 'ComplianceMode' : [ + [0, 'Some timers and counters deviate from HART specification', ''], + [1, 'Strict HART compliance',''], ], #---------------------------------------------------------------------- - 'targetMemory' : [ - [False,'NV_only', ''], - [True, 'NV_and_RAM', ''], - ], 'tranType' : [ [False,'bestEffort', ''], [True, 'Acked', ''], @@ -254,7 +250,7 @@ def serializeGetNv(self,commandArray,fieldsToFill): { 'id' : 0x07, 'name' : 'batteryLife', - 'description': 'The setParameter command allows the microprocessor to update the remaining battery life information that the mote reports to WirelessHART Gateway in Command 778. This parameter must be set during theIdle state prior to joining, and should be updated periodically throughout operation.This parameter is only used in WirelessHART-compliant devices.\n\nCommand 778 is deprecated in version 7.4 of the HART specification as most existing gateways do not use battery life information.\n\n', + 'description': 'The setParameter command allows the microprocessor to update the remaining battery life information that the mote reports to WirelessHART Gateway in Command 778. This parameter must be set during theIdle state prior to joining, and should be updated periodically throughout operation.This parameter is only used in WirelessHART-compliant devices.\n\nCommand 778 is deprecated in version 7.4 of the HART specification as most existing gateways do not use battery life information.', 'request' : [ ['batteryLife', INT, 2, None], ['powerStatus', INT, 1, 'PowerStatus'], @@ -272,7 +268,7 @@ def serializeGetNv(self,commandArray,fieldsToFill): { 'id' : 0x08, 'name' : 'service', - 'description': '\n\nThe setParameter command is used to request new device-originated bandwidth services and modify existing device-initiated services (now called "Timetables" in WirelessHART 7.4).Calling thiscommand updates the motes internal service table, which later initiates a request to the network manager for bandwidth allocation. A subsequent serviceIndication notification will be sent indicating the response from the network manager. The getParameter command may be used to read the service table, including the state of the service request.\n\nThe setParameter command may be sent at any time. If the network manager rejects a service request, the microprocessor can try again by re-issuing the setParameter command.\n\nTo delete a service, set the time field of the desired service to zero. Service request flags, application domain, and destination address are ignored by the mote when time equals zero.', + 'description': 'The setParameter command is used to request new device-originated bandwidth services and modify existing device-initiated services (now called "Timetables" in WirelessHART 7.4).Calling thiscommand updates the motes internal service table, which later initiates a request to the network manager for bandwidth allocation. A subsequent serviceIndication notification will be sent indicating the response from the network manager. The getParameter command may be used to read the service table, including the state of the service request.\n\nThe setParameter command may be sent at any time. If the network manager rejects a service request, the microprocessor can try again by re-issuing the setParameter command.\n\nTo delete a service, set the time field of the desired service to zero. Service request flags, application domain, and destination address are ignored by the mote when time equals zero.', 'request' : [ ['serviceId', INT, 1, None], ['serviceReqFlags', INT, 1, None], @@ -330,7 +326,7 @@ def serializeGetNv(self,commandArray,fieldsToFill): { 'id' : 0x0B, 'name' : 'eventMask', - 'description': 'The setParameter command allows the microprocessor to subscribe to the types of events that may be sent in the motes events notification message. This command may be called at any time and takes effect at the next event notification. The mote includes an event in the notification message if the corresponding bit in is set to 1, and excludes the event if the bit is set to 0. At mote reset, the default value of is 1 for all events.', + 'description': 'The setParameter command allows the microprocessor to subscribe to the types of events that may be sent in the motes events notification message. This command may be called at any time and takes effect at the next event notification. The mote includes an event in the notification message if the corresponding bit in is set to 1, and excludes the event if the bit is set to 0. At mote reset, the default value of is 1 for all events.\n\nNew event type may be added in future revisions of mote software. It is recommended that the client code only subscribe to known events and gracefully ignore all unknown events.', 'request' : [ ['eventMask', INT, 4, None], ], @@ -348,7 +344,7 @@ def serializeGetNv(self,commandArray,fieldsToFill): { 'id' : 0x12, 'name' : 'writeProtect', - 'description': 'The setParameter command allows the microprocessor to enable or disable access to selected WirelessHART commands via wireless or the hartPayload command. Refer to the WirelessHART Mote User Guide for the list of affected commands. If writeProtect is enabled and the mote receives any of these commands (either via wireless connection or via the hartPayload command), the command will have no effect, and the mote will return RC_7 (In Write Protect Mode). At mote boot, writeProtect is set to 0 (writes allowed). The current status of writeProtect may be read via the getParameter command. This command is for WirelessHART-compliant devices only.', + 'description': 'The setParameter command allows the microprocessor to enable or disable access to selected WirelessHART commands via wireless or the hartPayload command. Refer to theSmartMesh WirelessHART User\'s Guide for the list of affected commands. If writeProtect is enabled and the mote receives any of these commands (either via wireless connection or via the hartPayload command), the command will have no effect, and the mote will return RC_7 (In Write Protect Mode). At mote boot, writeProtect is set to 0 (writes allowed). The current status of writeProtect may be read via the getParameter command. This command is for WirelessHART-compliant devices only.', 'request' : [ ['writeProtect', INT, 1, 'WriteProtectMode'], ], @@ -366,10 +362,25 @@ def serializeGetNv(self,commandArray,fieldsToFill): ] subCommandsGet = [ + { + 'id' : 0x06, + 'name' : 'joinDutyCycle', + 'description': 'The getParameter command return mote\'s join duty cycle, which determines the percentage of time the mote spends in radio receive mode while searching for network. The value of join duty cycle is expressed in increments of 1/255th of 100%, where 0 corresponds to 0% and 255 corresponds to 100%.', + 'request' : [ + ], + 'response' : { + 'FIELDS': [ + ['joinDutyCycle', INT, 1, None], + ], + }, + 'responseCodes': { + 'RC_OK' : 'Operation was successfully completed', + }, + }, { 'id' : 0x08, 'name' : 'service', - 'description': '\n\nThe getParameter command retrieves information about the service allocation that is currently available to the field device. Services (now called "Timetables" in WirelessHART 7.4) in the range 0x00-7F are those requested by the device, and those in the range 0x80-FF are assigned independently by the network manager.', + 'description': 'The getParameter command retrieves information about the service allocation that is currently available to the field device. Services (now called "Timetables" in WirelessHART 7.4) in the range 0x00-7F are those requested by the device, and those in the range 0x80-FF are assigned independently by the network manager.', 'request' : [ ['serviceId', INT, 1, None], ], @@ -463,8 +474,7 @@ def serializeGetNv(self,commandArray,fieldsToFill): ], 'response' : { 'FIELDS': [ - ['utcTimeSec', INT, 8, None], - ['utcTimeMsec', INT, 8, None], + ['utcTime', HEXDATA, 8, None], ['asn', HEXDATA, 5, None], ['asnOffset', INT, 2, None], ], @@ -521,7 +531,6 @@ def serializeGetNv(self,commandArray,fieldsToFill): 'name' : 'macAddress', 'description': 'The setNVParameter command may be used to supersede the factory-configured MAC address of the mote.', 'request' : [ - ['targetMemory', BOOL, 1, True], ['macAddr', HEXDATA, 8, None], ], 'response' : { @@ -542,7 +551,6 @@ def serializeGetNv(self,commandArray,fieldsToFill): 'name' : 'joinKey', 'description': 'The setParameter command may be used to set the join key. Upon receiving this request, the mote stores the new join key in its persistent storage. Using the write RAM option will only have an effect if the command is called while the mote is in Idle state. Otherwise, the new value will be used after the next mote boot.', 'request' : [ - ['targetMemory', BOOL, 1, True], ['joinKey', HEXDATA, 16, None], ], 'response' : { @@ -563,7 +571,6 @@ def serializeGetNv(self,commandArray,fieldsToFill): 'name' : 'networkId', 'description': 'The setNVParameter command may be used to set the persistent Network ID of the mote. The mote reads this value from persistent storage at boot time. Note: while the mote is in Idle state, it is possible to update the value of mote\'s in-RAM Network ID by using the RAM flag in the header of this command. This avoids the extra reset that is needed to start using the Network ID. Network ID can also be set over the air using HART command 773 in a WirelessHART-compliant network.', 'request' : [ - ['targetMemory', BOOL, 1, True], ['networkId', INT, 2, None], ], 'response' : { @@ -584,7 +591,6 @@ def serializeGetNv(self,commandArray,fieldsToFill): 'name' : 'txPower', 'description': 'The setParameter command sets the mote output power. Refer to product datasheets for supported RF output power values. For example, if the mote has a typical RF output power of +8 dBm when the Power Amplifier (PA) is enabled, then set the txPower parameter to 8 to enable the PA. Similarly, if the mote has a typical RF output power of -2 dBm when the PA is disabled, then set the txPower parameter to -2 to turn off the PA. This command may be issued at any time and takes effect at the next mote boot. To change the transmit power immediately, use the write RAM option of this command, which can also be used at any time.', 'request' : [ - ['targetMemory', BOOL, 1, True], ['txPower', INTS, 1, None], ], 'response' : { @@ -603,9 +609,8 @@ def serializeGetNv(self,commandArray,fieldsToFill): { 'id' : 0x05, 'name' : 'powerInfo', - 'description': '\n\nThe setNVParameter command specifies the average current that is available to the mote. Using the write RAM option will only have an effect if the command is called while the mote is in Idle state. Otherwise, the new value will be used after the next mote boot.', + 'description': 'The setNVParameter command specifies the average current that is available to the mote. Using the write RAM option will only have an effect if the command is called while the mote is in Idle state. Otherwise, the new value will be used after the next mote boot.', 'request' : [ - ['targetMemory', BOOL, 1, True], ['powerSource', INT, 1, 'PowerSource'], ['dischargeCur', INT, 2, None], ['dischargeTime', INT, 4, None], @@ -628,9 +633,8 @@ def serializeGetNv(self,commandArray,fieldsToFill): { 'id' : 0x13, 'name' : 'ttl', - 'description': '\n\nThe setNVParameter command sets the mote\'spersistentpacket Time To Live (TTL) value.TTL specifies the maximum number of hops a packet may traverse before it is discarded from the network. A mote sets the initial value of the TTL field in the packets it generates to this value.The mote reads the value from persistent storage at boot time.To change the TTL used currently, this command may be issued with the RAM option.\n\nThe mote defaults TTL to 127. For compliant devices, the HART specification currently defaults to 32, but this will change to 249 in spec version 7.4, as will the mote default. We suggest not changing the mote default unless HART specifically raises it as a compliance issue when you submit your device for testing.\n\n', + 'description': 'The setNVParameter command sets the mote\'spersistentpacket Time To Live (TTL) value.TTL specifies the maximum number of hops a packet may traverse before it is discarded from the network. A mote sets the initial value of the TTL field in the packets it generates to this value.The mote reads the value from persistent storage at boot time.To change the TTL used currently, this command may be issued with the RAM option.\n\nThe mote defaults TTL to 127. For compliant devices, the HART specification currently defaults to 32, but this will change to 249 in spec version 7.4, as will the mote default. We suggest not changing the mote default unless HART specifically raises it as a compliance issue when you submit your device for testing.', 'request' : [ - ['targetMemory', BOOL, 1, True], ['timeToLive', INT, 1, None], ], 'response' : { @@ -650,9 +654,8 @@ def serializeGetNv(self,commandArray,fieldsToFill): { 'id' : 0x14, 'name' : 'HARTantennaGain', - 'description': '\n\nThe setNVParameter command stores value of the antenna gain in the mote\'s persistent storage.This value is added to the conducted output power of the mote when replying to HART command 797 (Write Radio Power Output) and to HART command 798 (Read Radio Output Power). The antenna gain should take into account both the gain of the antenna and any loss (for example, attenuation from a long coax cable) between the mote and the antenna. By default, this value is 2, assuming a +2 dBi antenna gain.To change the transmit power immediately, use the write RAM option of this command.', + 'description': 'The setNVParameter command stores value of the antenna gain in the mote\'s persistent storage.This value is added to the conducted output power of the mote when replying to HART command 797 (Write Radio Power Output) and to HART command 798 (Read Radio Output Power). The antenna gain should take into account both the gain of the antenna and any loss (for example, attenuation from a long coax cable) between the mote and the antenna. By default, this value is 2, assuming a +2 dBi antenna gain.To change the transmit power immediately, use the write RAM option of this command.', 'request' : [ - ['targetMemory', BOOL, 1, True], ['antennaGain', INTS, 1, None], ], 'response' : { @@ -673,7 +676,6 @@ def serializeGetNv(self,commandArray,fieldsToFill): 'name' : 'OTAPlockout', 'description': 'The setNVParameter command specifies whether the mote\'s firmware can be updated over the air. Over-The-Air-Programming (OTAP) is allowed by default. The mote reads the OTAPlockout value from persistent storage at boot time. To change the value used currently, this command may be issued with RAM option.\n\nDust Networks recommends that OEMs allow their devices to receive firmware updates, either by leaving the OTAPlockout parameter at its default value, or by making OTAPlockout settable using a WirelessHART command that is available both over the air and through its wired maintenance port. OEMs have the option of making such a command password protected.', 'request' : [ - ['targetMemory', BOOL, 1, True], ['otapLockout', INT, 1, 'OTAPLockout'], ], 'response' : { @@ -710,13 +712,33 @@ def serializeGetNv(self,commandArray,fieldsToFill): }, 'serializer' : 'serializeSetNv', }, + { + 'id' : 0x19, + 'name' : 'compliantMode', + 'description': 'ThesetNVParametercommand may be used to force strict compliance to HART specification requirements, specifically:\n\n-join timeouts (faster in non-compliant mode)\n-Keepalive interval (adapts to synch quality in non-compliant mode)\n-Health report format (uses saturating counters in non-compliant mode)\n\nNote: This parameter is available in devices running mote software >= 1.1.0', + 'request' : [ + ['compliantMode', INT, 1, None], + ], + 'response' : { + 'FIELDS': [ + ], + }, + 'responseCodes': { + 'RC_OK' : 'Operation was successfully completed', + 'RC_INVALID_LEN' : 'Invalid packet length', + 'RC_WRITE_FAIL' : 'Write did not complete', + 'RC_LOW_VOLTAGE' : 'Voltage check failed', + 'RC_UNKNOWN_PARAM' : 'Unknown parameter value', + }, + 'serializer' : 'serializeSetNv', + }, ] subCommandsGetNv = [ { 'id' : 0x01, 'name' : 'macAddress', - 'description': 'The getNVParameter command returns the MAC address stored in mote\'s persistent storage (i.e. set with setNVParameter).\n\nThis command returns 0000000000000000 if the macAddress has not been set previously - the mote will use its hardware MAC in this case, but it is not displayed with this command.\n\n', + 'description': 'The getNVParameter command returns the MAC address stored in mote\'s persistent storage (i.e. set with setNVParameter).\n\nThis command returns 0000000000000000 if the macAddress has not been set previously - the mote will use its hardware MAC in this case, but it is not displayed with this command.', 'request' : [ ], 'response' : { @@ -867,6 +889,24 @@ def serializeGetNv(self,commandArray,fieldsToFill): }, 'serializer' : 'serializeGetNv', }, + { + 'id' : 0x19, + 'name' : 'compliantMode', + 'description': 'ThegetNVParametercommand may be used to retrieve the compliance mode that is used by devices.This mode controls strict compliance to HART specification requirements, specifically:\n\n-join timeouts (faster in non-compliant mode)\n-Keepalive interval (adapts to synch quality in non-compliant mode)\n-Health report format (uses saturating counters in non-compliant mode)\n\nNote: This parameter is available in devices running mote software >= 1.1.0', + 'request' : [ + ], + 'response' : { + 'FIELDS': [ + ['compliantMode', INT, 1, 'ComplianceMode'], + ], + }, + 'responseCodes': { + 'RC_OK' : 'Operation was successfully completed', + 'RC_UNKNOWN_PARAM' : 'Unknown parameter value', + 'RC_READ_FAIL' : 'Read did not complete', + }, + 'serializer' : 'serializeGetNv', + }, ] # We redefine this attribute inherited from ApiDefinition. See @@ -937,7 +977,7 @@ def serializeGetNv(self,commandArray,fieldsToFill): { 'id' : 0x05, 'name' : 'send', - 'description': "\n\nThe send command allows a serial device to send a packet into the network through the mote's serial port. The mote forwards the packet to the network upon receiving it. The microprocessor must not attempt to send data at a rate that exceeds its allocated bandwidth. For a WirelessHART device, the payload of the packet must include the status byte and the extended status byte, followed by one or more sets of HART commands up to the maximum send payload size, as follows:\n\nStatus|Extended Status|Cmd1|Length1|Data1|Cmd2|Length2|Data2\n\nPrior to sending the payload into the network, the mote caches the value of Status and Extended Status to use in packets it originates locally. The send command is only valid when the mote is in theOperational state. If the mote receives this command when it is not in the Operational state, it returns the error RC_INV_STATE. Note: The serial device can receive a request while the mote is in the process of transition from the Connected state to theOperational state.", + 'description': "The send command allows a serial device to send a packet into the network through the mote's serial port. The mote forwards the packet to the network upon receiving it. The microprocessor must not attempt to send data at a rate that exceeds its allocated bandwidth. For a WirelessHART device, the payload of the packet must include the status byte and the extended status byte, followed by one or more sets of HART commands up to the maximum send payload size, as follows:\n\nRequest: Status|Extended Status|Cmd1|Length1|Data1|Cmd2|Length2|Data2...\nResponse: Status|Extended Status|Cmd1|Length1(includes response code)|RC1|Data1|Cmd2|Length2|RC2|Data2...\n\nPrior to sending the payload into the network, the mote caches the value of Status and Extended Status to use in packets it originates locally. The send command is only valid when the mote is in theOperational state. If the mote receives this command when it is not in the Operational state, it returns the error RC_INV_STATE. Note: The serial device can receive a request while the mote is in the process of transition from the Connected state to theOperational state.", 'request' : [ ['tranType', BOOL, 1, True], ['tranDir', BOOL, 1, True], @@ -945,7 +985,7 @@ def serializeGetNv(self,commandArray,fieldsToFill): ['serviceId', INT, 1, None], ['appDomain', INT, 1, 'ApplicationDomain'], ['priority', INT, 1, 'Priority'], - ['reserved', HEXDATA, 2, None], + ['reserved', INT, 2, None], ['seqNum', INT, 1, None], ['payloadLen', INT, 1, None], ['payload', HEXDATA, None,None], @@ -986,7 +1026,7 @@ def serializeGetNv(self,commandArray,fieldsToFill): { 'id' : 0x07, 'name' : 'disconnect', - 'description': 'The disconnect command requests that the mote disconnect from the network. The mote will send an indication to its network neighbors that it is about to become unavailable. Just after the mote disconnects, it sends the microprocessor an events packet with the disconnected bit set, indicating it will reset. This command is only valid in when the mote is in the Connected or Operational state (see Mote State).\n\nThe OEM microprocessor should disconnect from the network if the device is going to power down, reset, or otherwise be unavailable for a long period.\n\nA mote will reset itself after having sent the disconnect notification to the OEM microprocessor. The microprocessor should wait to acknowledge the boot event before shutting down.\n\n', + 'description': 'The disconnect command requests that the mote disconnect from the network. The mote will send an indication to its network neighbors that it is about to become unavailable. Just after the mote disconnects, it sends the microprocessor an events packet with the disconnected bit set, indicating it will reset. This command is only valid in when the mote is in the Connected or Operational state (see Mote State).\n\nThe OEM microprocessor should disconnect from the network if the device is going to power down, reset, or otherwise be unavailable for a long period.\n\nA mote will reset itself after having sent the disconnect notification to the OEM microprocessor. The microprocessor should wait to acknowledge the boot event before shutting down.', 'request' : [ ], 'response' : { @@ -1052,7 +1092,7 @@ def serializeGetNv(self,commandArray,fieldsToFill): { 'id' : 0x0B, 'name' : 'testRadioTx', - 'description': 'The testRadioTx command initiates transmission over the radio. This command may only be issued prior to the mote joining the network. While executing this command the mote sendsnumPackets packets. Each packet consists of a payload of up to 125 bytes, and a 2-byte 802.15.4 CRC at the end. Bytes 0 and 1 contain the packet number (in big-endian format) that increments with every packet transmitted. Bytes 2..N contain a counter (from 0..N-2) that increments with every byte inside payload. Transmissions occur on the specified channel.\n\nIf number of packets parameter is set to 0x00, the mote will generate an unmodulatedtest tone on the selected channel. The test tone can only be stopped by resetting themote.\n\n\n\nChannel numbering is 0-14, corresponding to IEEE 2.4 GHz channels 11-25.\n\nNote: this command is deprecated and should not be used in new designs. The replacement command is testRadioTxExt.\n\n', + 'description': 'The testRadioTx command initiates transmission over the radio. This command may only be issued prior to the mote joining the network. While executing this command the mote sendsnumPackets packets. Each packet consists of a payload of up to 125 bytes, and a 2-byte 802.15.4 CRC at the end. Bytes 0 and 1 contain the packet number (in big-endian format) that increments with every packet transmitted. Bytes 2..N contain a counter (from 0..N-2) that increments with every byte inside payload. Transmissions occur on the specified channel.\n\nIf number of packets parameter is set to 0x00, the mote will generate an unmodulatedtest tone on the selected channel. The test tone can only be stopped by resetting themote.\n\n\n\nChannel numbering is 0-15, corresponding to IEEE 2.4 GHz channels 11-26.\n\nNote: this command is deprecated and should not be used in new designs. The replacement command is testRadioTxExt.', 'request' : [ ['channel', INT, 1, None], ['numPackets', INT, 2, None], @@ -1073,7 +1113,7 @@ def serializeGetNv(self,commandArray,fieldsToFill): { 'id' : 0x0C, 'name' : 'testRadioRx', - 'description': 'The testRadioRx command clears all previously collected statistics and initiates a test of radio reception for the specified channel and duration. During the test, the mote keeps statistics about the number of packets received (with and without error). The test results may be retrieved using the getParameter command.The mote must be reset (either hardware or software reset) after radio tests are complete and prior to joining.\n\n\n\nChannel numbering is 0-14, corresponding to IEEE 2.4 GHz channels 11-25.\n\nNote: this command is deprecated and should not be used in new designs. The replacement command is testRadioRxExt.\n\n', + 'description': 'The testRadioRx command clears all previously collected statistics and initiates a test of radio reception for the specified channel and duration. During the test, the mote keeps statistics about the number of packets received (with and without error). The test results may be retrieved using the getParameter command.The mote must be reset (either hardware or software reset) after radio tests are complete and prior to joining.\n\n\n\nChannel numbering is 0-15, corresponding to IEEE 2.4 GHz channels 11-26.\n\nNote: this command is deprecated and should not be used in new designs. The replacement command is testRadioRxExt.', 'request' : [ ['channel', INT, 1, None], ['time', INT, 2, None], @@ -1127,7 +1167,7 @@ def serializeGetNv(self,commandArray,fieldsToFill): { 'id' : 0x13, 'name' : 'testRadioTxExt', - 'description': 'ThetestRadioTxExtcommand allows the microprocessor to initiate a radio transmission test. This command may only be issued prior to the mote joining the network. Three types of transmission tests are supported:\n\n-Packet transmission\n-Continuous modulation\n-Continuous wave (unmodulated signal)\n\nIn a packet transmission test, the mote generates arepeatCntnumber of packet sequences. Each sequence consists of up to 10 packets with configurable size and delays. Each packet starts with a PHY preamble (5 bytes), followed by a PHY length field (1 byte), followed by data payload of up to 125 bytes, and finally a 2-byte 802.15.4 CRC at the end. Bytes 0 and 1 contain the packet number (in big-endian format) that increments with every packet transmitted. Bytes 2..N contain a counter (from 0..N-2) that increments with every byte inside payload. Transmissions occur on the set of channels defined by chanMask, selected inpseudo-randomorder.\n\nIn a continuous modulation test, the mote generates continuous pseudo-random modulated signal, centered at the specified channel. The test is stopped by resetting the mote.\n\nIn a continuous wave test, the mote generates an unmodulated tone, centered at the specified channel. The test tone is stopped by resetting the mote.\n\nThetestRadioTxExtcommand may only be issued when the mote is in Idle mode, prior to its joining the network. The mote must be reset (either hardware or software reset) after radio tests are complete and prior to joining.\n\n\n\nChannel numbering is 0-14, corresponding to IEEE 2.4 GHz channels 11-25.', + 'description': 'ThetestRadioTxExtcommand allows the microprocessor to initiate a radio transmission test. This command may only be issued prior to the mote joining the network. Three types of transmission tests are supported:\n\n-Packet transmission\n-Continuous modulation\n-Continuous wave (unmodulated signal)\n\nIn a packet transmission test, the mote generates arepeatCntnumber of packet sequences. Each sequence consists of up to 10 packets with configurable size and delays. Each packet starts with a PHY preamble (5 bytes), followed by a PHY length field (1 byte), followed by data payload of up to 125 bytes, and finally a 2-byte 802.15.4 CRC at the end. Byte 0 of the payload contains stationId of the sender. Bytes 1 and 2 contain the packet number (in big-endian format) that increments with every packet transmitted. Bytes 3..N contain a counter (from 0..N-3) that increments with every byte inside payload. Transmissions occur on the set of channels defined by chanMask, selected inpseudo-randomorder.\n\nIn a continuous modulation test, the mote generates continuous pseudo-random modulated signal, centered at the specified channel. The test is stopped by resetting the mote.\n\nIn a continuous wave test, the mote generates an unmodulated tone, centered at the specified channel. The test tone is stopped by resetting the mote.\n\nThetestRadioTxExtcommand may only be issued when the mote is in Idle mode, prior to its joining the network. The mote must be reset (either hardware or software reset) after radio tests are complete and prior to joining.\n\n\n\nChannel numbering is 0-15, corresponding to IEEE 2.4 GHz channels 11-26.', 'request' : [ ['testType', INT, 1, 'TestType'], ['chanMask', INT, 2, None], @@ -1171,10 +1211,11 @@ def serializeGetNv(self,commandArray,fieldsToFill): { 'id' : 0x14, 'name' : 'testRadioRxExt', - 'description': 'ThetestRadioRxcommand clears all previously collected statistics and initiates a test of radio reception for the specified channel and duration. During the test, the mote keeps statistics about the number of packets received (with and without error). The test results may be retrieved using thegetParameter command.The mote must be reset (either hardware or software reset) after radio tests are complete and prior to joining.\n\n\n\nChannel numbering is 0-14, corresponding to IEEE 2.4 GHz channels 11-25.', + 'description': "ThetestRadioRxcommand clears all previously collected statistics and initiates a test of radio reception for the specified channel and duration. During the test, the mote keeps statistics about the number of packets received (with and without error). Note that the non-zero station id specified in this command must match the transmitter's station id. This helps to isolate traffic if multiple tests are running in the same radio space.The test results may be retrieved using thegetParameter command.The mote must be reset (either hardware or software reset) after radio tests are complete and prior to joining.\n\n\n\nChannel numbering is 0-15, corresponding to IEEE 2.4 GHz channels 11-26.", 'request' : [ ['channelMask', INT, 2, None], ['time', INT, 2, None], + ['stationId', INT, 1, None], ], 'response' : { 'FIELDS': [ @@ -1189,6 +1230,22 @@ def serializeGetNv(self,commandArray,fieldsToFill): 'RC_INVALID_LEN' : 'Invalid packet size', }, }, + { + 'id' : 0x15, + 'name' : 'zeroize', + 'description': 'Zeroize (zeroise) command erases flash area that is used to store configuration parameters, such as join keys. This command is intended to satisfy zeroization requirement of FIPS-140 standard. After the command executes, the mote should be reset.', + 'request' : [ + ], + 'response' : { + 'FIELDS': [ + [RC, INT, 1, True], + ], + }, + 'responseCodes': { + 'RC_OK' : 'Command completed successfully', + 'RC_ERASE_FAIL' : 'Flash could not be erased due to unexpected internal error', + }, + }, ] # We redefine this attribute inherited from ApiDefinition. See @@ -1197,7 +1254,7 @@ def serializeGetNv(self,commandArray,fieldsToFill): { 'id' : 0xd, 'name' : 'timeIndication', - 'description': '', + 'description': 'The timeIndication notification applies to mote products that support a time interrupt into the mote. The time packet includes the network time and the current UTC time relative to the manager.\n\nFor LTC5800-WHMbased products, driving the TIMEn pin low (assert) wakes the processor. The pin must asserted for a minimum of tstrobe s. De-asserting the pin latches the time, and a timeIndication will be generated within tresponse ms.Refer to theLTC5800-WHM Datasheetfor additional information about TIME pin usage.\n\nThe processor will remain awake and drawing current while the TIMEn pin is asserted. To avoid drawing excess current, take care to minimize the duration of the TIMEn pin being asserted past tstrobe minimum.', 'response' : { 'FIELDS': [ ['utcSec', INT, 4, None], @@ -1210,7 +1267,7 @@ def serializeGetNv(self,commandArray,fieldsToFill): { 'id' : 0xe, 'name' : 'serviceIndication', - 'description': '', + 'description': 'The serviceIndication notification describes new manager-originated services (ID = 0x80-FF), or changes in existing services (ID = 0x00-7F). For more info on when the serviceIndication notification is sent and details about the individual parameters, see Bandwidth Services. If the time field contains the value 0x07FFFFFF, the manager is unable to sustain the service due to network conditions and has effectively disabled the service. The service is not removed, however, and the microprocessor can elect to either delete the service or submit a request to update the service at a future time.', 'response' : { 'FIELDS': [ ['eventCode', INT, 1, None], @@ -1227,7 +1284,7 @@ def serializeGetNv(self,commandArray,fieldsToFill): { 'id' : 0xf, 'name' : 'events', - 'description': '', + 'description': 'The events notification sends an event notification packet to the microprocessor informing it of new events that have occurred. The reported event is cleared from the mote when the mote receives an acknowledgement in the form of a response packet from the microprocessor.', 'response' : { 'FIELDS': [ ['events', INT, 4, None], @@ -1239,7 +1296,7 @@ def serializeGetNv(self,commandArray,fieldsToFill): { 'id' : 0x81, 'name' : 'dataReceived', - 'description': '', + 'description': 'The dataReceived notification notifies the microprocessor that a packet was received.When the microprocessor receives a reliable dataReceived request, in addition to acknowledging the request with a dataReceived response it must also respond using the send command.', 'response' : { 'FIELDS': [ ['srcAddr', HEXDATA, 2, None], @@ -1252,14 +1309,24 @@ def serializeGetNv(self,commandArray,fieldsToFill): { 'id' : 0x12, 'name' : 'advReceived', - 'description': '', + 'description': 'The advReceived notification notifies the microprocessor each time the mote receives an advertisement packet while in promiscuous listen mode. The command contains information about the advertisement, including the Network ID, Mote ID, RSSI, and join priority (hop depth). Note that advReceived notifications are sent only if the mote has been placed in listen mode using the search command (see search).', 'response' : { 'FIELDS': [ ['netId', INT, 2, None], ['moteid', HEXDATA, 2, None], - ['rssi', INT, 1, None], + ['rssi', INTS, 1, None], ['joinPri', INT, 1, None], ], }, }, + { + 'id' : 0x16, + 'name' : 'suspendStarted', + 'description': 'The mote generates suspendStarted notification when it enters suspended state as a result of processing Wireless HART command 972. When the suspend interval begins, the mote discontinues its radio operation and generates this notification. After the interval specified in command 972 ends, mote proceeds to reset. It is the responsibility of the attached microprocessor to re-join the network.', + 'response' : { + 'FIELDS': [ + ['duration', INT, 4, None], + ], + }, + }, ] diff --git a/SmartMeshSDK/ApiDefinition/IpMgrDefinition.py b/SmartMeshSDK/ApiDefinition/IpMgrDefinition.py index fa1318d..f90e43b 100644 --- a/SmartMeshSDK/ApiDefinition/IpMgrDefinition.py +++ b/SmartMeshSDK/ApiDefinition/IpMgrDefinition.py @@ -6,6 +6,7 @@ class IpMgrDefinition(ApiDefinition.ApiDefinition): ''' \ingroup ApiDefinition + \brief API definition for the IP manager. \note This class inherits from ApiDefinition. It redefines the attributes of @@ -54,7 +55,7 @@ def deserialize(self,type,cmdId,byteArray): # [ 1, 'event', 'Event notification'], # [ 2, 'log', 'Log notification'], # [ 4, 'data', 'Data payload notification'], - # [ 5, 'ipData', '6lowpan packet notification'], + # [ 5, 'ipData', '6LoWPAN packet notification'], # [ 6, 'healthReport', 'Health report notification'], # ], # 'eventTypes' : [ @@ -78,13 +79,15 @@ def deserialize(self,type,cmdId,byteArray): [ 2, 'RC_INVALID_ARGUMENT', 'Invalid argument'], [ 3, 'INVALID_AUTHENTICATION','Invalid Authentication (MUX)'], [ 4, 'INVALID_API_VERSION', 'Invalid API version (MUX)'], - [ 5, 'comd_timeout', 'command timeout (MUX)'], - [11, 'RC_END_OF_LIST', 'End of list is returned when an iteration reaches the end of the list of objects'], + [ 5, 'COMMAND_TIMEOUT', 'Command timeout (MUX)'], + [11, 'RC_NOT_FOUND', 'Object is not found'], [12, 'RC_NO_RESOURCES', 'Reached maximum number of items'], [13, 'RC_IN_PROGRESS', 'Operation is in progress'], [14, 'RC_NACK', 'Negative acknowledgment'], [15, 'RC_WRITE_FAIL', 'Flash write failed'], [16, 'RC_VALIDATION_ERROR', 'Parameter validation error'], + [17, 'RC_INV_STATE', 'Object has inappropriate state'], + [18, 'RC_END_OF_LIST', 'End of list is returned when an iteration reaches the end of the list of objects'], ], 'frameProfile' : [ [ 1, 'Profile_01', 'Fast network build, medium speed network operation'], @@ -97,10 +100,6 @@ def deserialize(self,type,cmdId,byteArray): [ 0, 'normal', 'Normal downstream bandwidth'], [ 1, 'fast', 'Fast downstream bandwidth'], ], - 'locationMode' : [ - [ 0, 'off', 'No Location capability'], - [ 1, 'onDemand', 'On Demand Location Awareness'], - ], 'networkState' : [ [ 0, 'operational', 'Network is operating normally'], [ 1, 'radiotest', 'Manager is in radiotest mode'], @@ -116,7 +115,6 @@ def deserialize(self,type,cmdId,byteArray): ], 'resetType' : [ [ 0, 'resetSystem', 'Reset the system'], - [ 1, 'resetNetwork', 'Reset the network'], [ 2, 'resetMote', 'Reset the mote'], ], 'backboneFrameMode' : [ @@ -227,7 +225,8 @@ def deserialize(self,type,cmdId,byteArray): }, 'responseCodes': { 'RC_OK' : 'Command successfully completed', - 'RC_END_OF_LIST' : 'Mote with specified MAC address is not found or is not in operational state (applies to mote reset)', + 'RC_NOT_FOUND' : 'Mote with specified MAC address is not found', + 'RC_INV_STATE' : 'Mote is not in operational state', 'RC_NACK' : 'User commands queue is full (applies to mote reset)', 'RC_INVALID_ARGUMENT' : 'Invalid reset type value', }, @@ -235,7 +234,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 22, 'name' : 'subscribe', - 'description': '\n\nThe subscribe command indicates that the manager should send the external application the specified notifications. It contains two filter fields:\n\n-filter is a bitmask of flags indicating the types of notifications that the client wants to receive\n-unackFilter allows the client to select which of the notifications selected in filter should be sent acknowledged. If a notification is sent as \'acknowledged\', thesubsequent notification packets will be queued while waiting for response.\n\nEach subscription request overwrites the previous one. If an application is subscribed to data and then decides he also wants events he should send a subscribe command with both the data and event flags set. To clear all subscriptions, the client should send a subscribe command with the filter set to zero. When a session is initiated between the manager and a client, the subscription filter is initialized to zero.\n\nThe subscribe bitmap uses the values of the notification type enumeration. Some values are unused to provide backwards compatibility with earlier APIs.', + 'description': 'The subscribe command indicates that the manager should send the external application the specified notifications. It contains two filter fields:\n\n-filter is a bitmask of flags indicating the types of notifications that the client wants to receive\n-unackFilter allows the client to select which of the notifications selected in filter should be sent acknowledged. If a notification is sent as \'acknowledged\', thesubsequent notification packets will be queued while waiting for response.\n\nEach subscription request overwrites the previous one. If an application is subscribed to data and then decides he also wants events he should send a subscribe command with both the data and event flags set. To clear all subscriptions, the client should send a subscribe command with the filter set to zero. When a session is initiated between the manager and a client, the subscription filter is initialized to zero.\n\nThe subscribe bitmap uses the values of the notification type enumeration. Some values are unused to provide backwards compatibility with earlier APIs.', 'request' : [ ['filter', HEXDATA, 4, None], ['unackFilter', HEXDATA, 4, None], @@ -253,7 +252,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 23, 'name' : 'getTime', - 'description': 'The getTime command returns the current manager UTC time and current absolute slot number (ASN). The time values returned by this command are delayed by queuing and transfer time over the serial connection. For additional precision, an external application should trigger the networkTime notification using the Time Pin. ', + 'description': 'The getTime command returns the current manager UTC time and current absolute slot number (ASN). The time values returned by this command are delayed by queuing and transfer time over the serial connection. For additional precision, an external application should trigger the networkTime notification using the Time Pin.', 'request' : [ ], 'response' : { @@ -273,7 +272,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 26, 'name' : 'setNetworkConfig', - 'description': '\n\nThe setNetworkConfig command changes network configuration parameters. The response code indicates whether the changes were successfully applied. Generally, changes to network configuration will take effect when the manager reboots. Exceptions are detailed below:\n\n\n-Max Motes: The new maxMotes value is used as soon as new motes try to join the network, but motes are not removed from the network if the value is set to a number lower than numMotes.\n-Base Bandwidth: Changing baseBandwidth while the network is running does not reallocate bandwidth to Operational motes.', + 'description': 'The setNetworkConfig command changes network configuration parameters. The response code indicates whether the changes were successfully applied.This change is persistent.\n\nGenerally, changes to network configuration will take effect when the manager reboots. Exceptions are detailed below:\n\n\n-Max Motes: The new maxMotes value is used as soon as new motes try to join the network, but motes are not removed from the network if the value is set to a number lower than numMotes.\n-Base Bandwidth: Changing baseBandwidth while the network is running does not reallocate bandwidth to Operational motes.', 'request' : [ ['networkId', INT, 2, None], ['apTxPower', INTS, 1, None], @@ -285,7 +284,7 @@ def deserialize(self,type,cmdId,byteArray): ['ccaMode', INT, 1, True], ['channelList', INT, 2, None], ['autoStartNetwork', BOOL, 1, None], - ['locMode', INT, 1, 'locationMode'], + ['locMode', INT, 1, None], ['bbMode', INT, 1, 'backboneFrameMode'], ['bbSize', INT, 1, None], ['isRadioTest', INT, 1, None], @@ -321,7 +320,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 33, 'name' : 'exchangeMoteJoinKey', - 'description': 'The exchangeMoteJoinKey command triggers the manager to send a new join key to the specified mote and update the manager\'s ACL entry for the mote. The response contains a callbackId. A commandFinished notification with this callbackId will be sent when the operation is complete. ', + 'description': 'The exchangeMoteJoinKey command triggers the manager to send a new join key to the specified mote and update the manager\'s ACL entry for the mote. The response contains a callbackId. A commandFinished event notification with this callbackId will be sent when the operation is complete. This change is persistent.', 'request' : [ ['macAddress', HEXDATA, 8, None], ['key', HEXDATA, 16, None], @@ -334,14 +333,16 @@ def deserialize(self,type,cmdId,byteArray): }, 'responseCodes': { 'RC_OK' : 'Command successfully completed', - 'RC_END_OF_LIST' : 'Mote with specified MAC address is not found or is not in operational state', + 'RC_NOT_FOUND' : 'Mote with specified MAC address is not found', + 'RC_INV_STATE' : 'Mote is not in operational state', 'RC_NACK' : 'User commands queue is full', + 'RC_WRITE_FAIL' : 'Flash write error, can\'t save new settings', }, }, { 'id' : 34, 'name' : 'exchangeNetworkId', - 'description': 'The exchangeNetworkId command triggers the manager to distribute a new network ID to all the motes in the network. AcallbackId is returned in the response. A commandFinished notification with this callbackId will be sent when the operation is complete.', + 'description': 'The exchangeNetworkId command triggers the manager to distribute a new network ID to all the motes in the network. AcallbackId is returned in the response. A commandFinished notification with this callbackId will be sent when the operation is complete.This change is persistent.', 'request' : [ ['id', INT, 2, None], ], @@ -361,7 +362,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 35, 'name' : 'radiotestTx', - 'description': 'The radiotestTx command allows the user to initiate a radio transmission test. It may only be executed if the manager has been booted up in radiotest mode (see setNetworkConfig command). Three types of transmission tests are supported:\n\n-Packet transmission\n-Continuous modulation\n-Continuous wave (unmodulated signal)\n\nIn a packet transmission test, the device generates a repeatCnt number of packet sequences. Each sequence consists of up to 10 packets with configurable size and delays. Each packet consists of payload of up to 125 bytes, and a 2-byte 802.15.4 CRC at the end. Bytes 0 and 1 contain the packet number (in big-endian format) that increments with every packet transmitted. Bytes 2..N contain a counter (from 0..N-2) that increments with every byte inside payload. Transmissions occur on the set of channels defined by chanMask, selected in pseudo-random order.\n\nIn a continuous modulation test, the device generates continuous pseudo-random modulated signal, centered at the specified channel. The test is stopped by resetting the device.\n\nIn a continuous wave test, the device generates an unmodulated tone, centered at the specified channel. The test tone is stopped by resetting the device.\n\n\n\nChannel numbering is 0-14, corresponding to IEEE 2.4 GHz channels 11-25.', + 'description': 'The radiotestTx command allows the user to initiate a radio transmission test. It may only be executed if the manager has been booted up in radiotest mode (see setNetworkConfig command). Three types of transmission tests are supported:\n\n-Packet transmission\n-Continuous modulation\n-Continuous wave (unmodulated signal)\n\nIn a packet transmission test, the device generates a repeatCnt number of packet sequences. Each sequence consists of up to 10 packets with configurable size and delays.Each packet starts with a PHY preamble (5 bytes), followed by a PHY length field (1 byte), followed by data payload of up to 125 bytes, and finally a 2-byte 802.15.4 CRC at the end. Byte 0 of the payload contains stationId of the sender. Bytes 1 and 2 contain the packet number (in big-endian format) that increments with every packet transmitted. Bytes 3..N contain a counter (from 0..N-2) that increments with every byte inside payload. Transmissions occur on the set of channels defined bychanMask, selected inpseudo-randomorder.\n\nIn a continuous modulation test, the device generates continuous pseudo-random modulated signal, centered at the specified channel. The test is stopped by resetting the device.\n\nIn a continuous wave test, the device generates an unmodulated tone, centered at the specified channel. The test tone is stopped by resetting the device.\n\n\n\nChannel numbering is 0-15, corresponding to IEEE 2.4 GHz channels 11-26.', 'request' : [ ['testType', INT, 1, None], ['chanMask', INT, 2, None], @@ -403,10 +404,11 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 37, 'name' : 'radiotestRx', - 'description': 'The radiotestRx command clears all previously collected statistics and initiates radio reception on the specified channel. It may only be executed if the manager has been booted up in radiotest mode (see setNetworkConfig command).During the test, the device keeps statistics about the number of packets received (with and without error).The test results may be retrieved using the getRadiotestStatistics command.\n\n\n\nChannel numbering is 0-14, corresponding to IEEE 2.4 GHz channels 11-25.', + 'description': 'The radiotestRx command clears all previously collected statistics and initiates radio reception on the specified channel. It may only be executed if the manager has been booted up in radiotest mode (see setNetworkConfig command).During the test, the device keeps statistics about the number of packets received (with and without error). Note that the station id specified in this command must match the transmitter\'s station id. This is necessary to isolate traffic if multiple tests are running in the same radio space.The test results may be retrieved using the getRadiotestStatistics command.\n\n\n\nChannel numbering is 0-15, corresponding to IEEE 2.4 GHz channels 11-26.', 'request' : [ ['mask', INT, 2, None], ['duration', INT, 2, None], + ['stationId', INT, 1, None], ], 'response' : { 'FIELDS': [ @@ -416,7 +418,7 @@ def deserialize(self,type,cmdId,byteArray): 'responseCodes': { 'RC_OK' : 'Command successfully completed', 'RC_IN_PROGRESS' : 'Radiotest is in progress', - 'RC_INVALID_ARGUMENT' : 'Invalid "channel" value', + 'RC_INVALID_ARGUMENT' : 'Invalid mask value', }, }, { @@ -441,7 +443,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 39, 'name' : 'setACLEntry', - 'description': 'The setACLEntry command adds a new entry or updates an existing entry in the Access Control List (ACL).', + 'description': 'The setACLEntry command adds a new entry or updates an existing entry in the Access Control List (ACL).This change is persistent.', 'request' : [ ['macAddress', HEXDATA, 8, None], ['joinKey', HEXDATA, 16, None], @@ -460,7 +462,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 40, 'name' : 'getNextACLEntry', - 'description': '\n\nThe getNextACLEntry command returns information about next mote entry in the access control list (ACL). To begin a search (find the first mote in ACL), a zero MAC address (0000000000000000) should be sent.There is no mechanism for reading the ACL entry of a specific mote. This call is an iterator. If you call getNextACLEntry with mote A as the argument, your response is the ACL entry for mote B, where B is the next mote in the ACL.\n\n', + 'description': 'The getNextACLEntry command returns information about next mote entry in the access control list (ACL). To begin a search (find the first mote in ACL), a zero MAC address (0000000000000000) should be sent.There is no mechanism for reading the ACL entry of a specific mote. This call is an iterator. If you call getNextACLEntry with mote A as the argument, your response is the ACL entry for mote B, where B is the next mote in the ACL.', 'request' : [ ['macAddress', HEXDATA, 8, None], ], @@ -474,13 +476,13 @@ def deserialize(self,type,cmdId,byteArray): 'responseCodes': { 'RC_OK' : 'Command successfully completed', 'RC_END_OF_LIST' : 'End of ACL is reached', - 'RC_INVALID_ARGUMENT' : 'No such mote in the ACL', + 'RC_NOT_FOUND' : 'No such mote in the ACL', }, }, { 'id' : 41, 'name' : 'deleteACLEntry', - 'description': 'The deleteACLEntry command deletes the specified mote from the access control list (ACL). If the macAddress parameter is set to all 0xFFs or all 0x00s, the entire ACL is cleared.', + 'description': 'The deleteACLEntry command deletes the specified mote from the access control list (ACL). If the macAddress parameter is set to all 0xFFs or all 0x00s, the entire ACL is cleared. This change is persistent.', 'request' : [ ['macAddress', HEXDATA, 8, None], ], @@ -491,14 +493,14 @@ def deserialize(self,type,cmdId,byteArray): }, 'responseCodes': { 'RC_OK' : 'Command successfully completed', - 'RC_END_OF_LIST' : 'Specified mote is not found in ACL', + 'RC_NOT_FOUND' : 'Specified mote is not found in ACL', 'RC_WRITE_FAIL' : 'Flash write error, can\'t save new settings', }, }, { 'id' : 42, 'name' : 'pingMote', - 'description': 'The pingMote command sends a ping (echo request) to the mote specified by MAC address. A unique callbackId is generated and returned with the response. When a ping response is received from the mote, the manager generates a ping notification with the measured round trip delay and several other parameters.', + 'description': 'The pingMote command sends a ping (echo request) to the mote specified by MAC address. A unique callbackId is generated and returned with the response. When the response is received from the mote, the manager generates a pingResponse notification with the measured round trip delay and several other parameters. The request is sent using unacknowledged transport, so the mote is not guaranteed to receive the request.', 'request' : [ ['macAddress', HEXDATA, 8, None], ], @@ -510,7 +512,8 @@ def deserialize(self,type,cmdId,byteArray): }, 'responseCodes': { 'RC_OK' : 'Command successfully completed', - 'RC_END_OF_LIST' : 'Specified mote not found or is not in operational state', + 'RC_NOT_FOUND' : 'Specified mote not found', + 'RC_INV_STATE' : 'Mote is not in operational state', 'RC_NO_RESOURCES' : 'User commands queue is full', 'RC_IN_PROGRESS' : 'Previous echo request command is still pending for specified mote', }, @@ -529,13 +532,14 @@ def deserialize(self,type,cmdId,byteArray): }, 'responseCodes': { 'RC_OK' : 'Command successfully completed', - 'RC_END_OF_LIST' : 'Specified mote not found or is not in operational state', + 'RC_NOT_FOUND' : 'Specified mote not found', + 'RC_INV_STATE' : 'Mote is not in operational state', }, }, { 'id' : 44, 'name' : 'sendData', - 'description': 'The sendData command sends a packet to a mote in the network. The response contains a callbackId. When the manager injects the packet into the network, it will generate a packetSent notification. It is the application layers responsibility send a response from the mote, and to timeout if no response is received.\n\nThe sendData command should be used by applications that communicate directly with the manager. If end-to-end (application to mote) IP connectivity is required, the application should use the sendIP command. For a more comprehensive discussion of the distinction, see the SmartMesh IPNetwork User Guide.', + 'description': "The sendData command sends a packet to a mote in the network. The response contains a callbackId. When the manager injects the packet into the network, it will generate a packetSent notification. It is the responsibility of the customer'sapplication layer at the mote to send a response. It is also the responsibility of thecustomer's application layer to timeout if no response is received at the manager if one is expected.\n\nThe sendData command should be used by applications that communicate directly with the manager. If end-to-end (application to mote) IP connectivity is required, the application should use the sendIP command. For a more comprehensive discussion of the distinction, see the SmartMesh IPNetwork User Guide.", 'request' : [ ['macAddress', HEXDATA, 8, None], ['priority', INT, 1, 'packetPriority'], @@ -552,7 +556,8 @@ def deserialize(self,type,cmdId,byteArray): }, 'responseCodes': { 'RC_OK' : 'Command successfully completed', - 'RC_END_OF_LIST' : 'Specified mote is not found or is not in operational state', + 'RC_NOT_FOUND' : 'Specified mote is not found', + 'RC_INV_STATE' : 'Mote is not in operational state', 'RC_NACK' : 'User commands queue is full or couldn\'t allocate memory buffer for payload', 'RC_INVALID_ARGUMENT' : 'Payload size exceeds maximum allowed value', }, @@ -560,7 +565,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 45, 'name' : 'startNetwork', - 'description': 'The startNetwork command tells the manager to allow the network to start forming (begin accepting join requests from devices). The external application must issue the startNetwork command if the autoStartNetwork flag is not set (seesetNetworkConfig). ', + 'description': 'The startNetwork command tells the manager to allow the network to start forming (begin accepting join requests from devices). The external application must issue the startNetwork command if the autoStartNetwork flag is not set (seesetNetworkConfig).', 'request' : [ ], 'response' : { @@ -598,7 +603,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 47, 'name' : 'getMoteConfig', - 'description': '\n\nThe getMoteConfig command returns a single mote description as the response. The command takes two arguments, a MAC Address and a flag indicating whether the MAC Address refers to the requested mote or to the next mote in managers memory. This command may be used to iterate through all motes known by the manager by starting with the macAddress parameter set to 0 and next set to true, and then using the MAC Address of that response as the input to the next call.\n\nThe mote MAC address is used in all query commands, but space constraints require the neighbor health reports to use the Mote ID for identification. Therefore, both identifiers are present in the mote structure.', + 'description': 'The getMoteConfig command returns a single mote description as the response. The command takes two arguments, a MAC Address and a flag indicating whether the MAC Address refers to the requested mote or to the next mote in managers memory. This command may be used to iterate through all motes known by the manager by starting with the macAddress parameter set to 0 and next set to true, and then using the MAC Address of that response as the input to the next call.\n\nThe mote MAC address is used in all query commands, but space constraints require the neighbor health reports to use the Mote ID for identification. Therefore, both identifiers are present in the mote structure.', 'request' : [ ['macAddress', HEXDATA, 8, None], ['next', BOOL, 1, None], @@ -616,7 +621,8 @@ def deserialize(self,type,cmdId,byteArray): }, 'responseCodes': { 'RC_OK' : 'Command successfully completed', - 'RC_END_OF_LIST' : 'The specified mote doesn\'t exist (next = false), or the last mote in the list has been reached (next = true)', + 'RC_NOT_FOUND' : 'The specified mote doesn\'t exist', + 'RC_END_OF_LIST' : 'Last mote in the list has been reached (next = true)', }, }, { @@ -641,13 +647,13 @@ def deserialize(self,type,cmdId,byteArray): }, 'responseCodes': { 'RC_OK' : 'Command successfully completed', - 'RC_INVALID_ARGUMENT' : 'A path between the specified motes doesn\'t exist', + 'RC_NOT_FOUND' : 'A path between the specified motes doesn\'t exist', }, }, { 'id' : 49, 'name' : 'getNextPathInfo', - 'description': '\n\nThe getNextPathInfo command allows iteration across paths connected to a particular mote. The pathId parameter indicates the previous value in the iteration. Setting pathId to 0 returns the first path. A pathId can not be used as a unique identifier for a path. It is only valid when associated with a particular mote. ', + 'description': 'The getNextPathInfo command allows iteration across paths connected to a particular mote. The pathId parameter indicates the previous value in the iteration. Setting pathId to 0 returns the first path. A pathId can not be used as a unique identifier for a path. It is only valid when associated with a particular mote.', 'request' : [ ['macAddress', HEXDATA, 8, None], ['filter', INT, 1, 'pathFilter'], @@ -668,8 +674,8 @@ def deserialize(self,type,cmdId,byteArray): }, 'responseCodes': { 'RC_OK' : 'Command successfully completed', + 'RC_NOT_FOUND' : 'The specified path ID does not exist', 'RC_END_OF_LIST' : 'The specified pathId in the request is the end of the list', - 'RC_INVALID_ARGUMENT' : 'The specified path ID does not exist', }, }, { @@ -693,7 +699,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 51, 'name' : 'setDownstreamFrameMode', - 'description': 'The setDownstreaFrameMode command tells the manager to shorten or extend the downstream frame. Once this command is executed, the manager switches to manual mode and no longer changes frame size automatically. The response is acallbackId. A commandFinished notification with the callbackId is generated when the command propagation is complete. ', + 'description': 'The setDownstreamFrameMode command tells the manager to shorten or extend the downstream frame. The base slotframe length will be multiplied by the downFrameMultValfor "normal" speed. For "fast" speed the downstream slotframe is the base length.Once this command is executed, the manager switches to manual mode and no longer changes slotframesize automatically. The response is acallbackId. A commandFinished notification with the callbackId is generated when the command propagation is complete.', 'request' : [ ['frameMode', INT, 1, 'downstreamFrameMode'], ], @@ -737,7 +743,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 54, 'name' : 'setTime', - 'description': '\n\nThe setTime command sets the UTC time on the manager. This command may only be executed when the network is not running. If the trigger flag is false, the manager sets the specified time as soon as it receives the setTime command. When the manager receives a Time Pin trigger, it temporarily stores the current time. If a setTime request is received within a short period of time following the trigger, the manager calculates the delay since the trigger and adjust the time such that the trigger was received at the specified time value.', + 'description': 'The setTime command sets the UTC time on the manager. This command may only be executed when the network is not running. If the trigger flag is false, the manager sets the specified time as soon as it receives the setTime command. When the manager receives a Time Pin trigger, it temporarily stores the current time. If a setTime request is received within a short period of time following the trigger, the manager calculates the delay since the trigger and adjust the time such that the trigger was received at the specified time value.', 'request' : [ ['trigger', INT, 1, None], ['utcSecs', INT, 8, None], @@ -773,7 +779,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 56, 'name' : 'setLicense', - 'description': 'The setLicense command validates and updates the software license key stored in flash. Features enabled or disabled by the license key change will take effect after the device is restarted.If the license parameter is set to all 0x0s, the manager restores the default license.', + 'description': 'The setLicense command validates and updates the software license key stored in flash. Features enabled or disabled by the license key change will take effect after the device is restarted.If the license parameter is set to all 0x0s, the manager restores the default license. This change is persistent.', 'request' : [ ['license', HEXDATA, 13, None], ], @@ -791,7 +797,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 58, 'name' : 'setCLIUser', - 'description': '\n\nThe setUser command sets the password that must be used to log into the command line for a particular user role. The user roles are:\n\n-Viewer - read-only access to non-sensitive information\n-User - read-write access', + 'description': 'The setUser command sets the password that must be used to log into the command line for a particular user role. The user roles are:\n\n-Viewer - read-only access to non-sensitive information\n-User - read-write accessThis change is persistent.', 'request' : [ ['role', INT, 1, 'userRole'], ['password', HEXDATA, 16, None], @@ -809,7 +815,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 59, 'name' : 'sendIP', - 'description': 'The sendIP command sends an 6LowPAN packet to a mote in the network. The response contains a callbackId. When the manager injects the packet into the network, it will generate a packetSent notification. It is the application layers responsibility send a response from the mote, and to timeout if no response is received. The application is responsible for constructing a valid 6LoWPAN packet.\n\nThe sendIP command should be used by applications that require end-to-end IP connectivity. For applications that do not require end-to-end IP connectivity, the sendData command provides a simpler interface without requiring the application to understand 6LoWPAN encapsulation. For a more comprehensive discussion of the distinction, see theSmartMesh IP Network User Guide.', + 'description': 'The sendIP command sends a6LoWPANpacket to a mote in the network. The response contains a callbackId. When the manager injects the packet into the network, it will generate a packetSent notification with the calllbackId. The application is responsible for constructing a valid 6LoWPANpacket.The packet is sent to the mote best-effort, so the application should deal with responses and timeouts, if any.\n\nThe sendIP command should be used by applications that require end-to-end IP connectivity. For applications that do not require end-to-end IP connectivity, the sendData command provides a simpler interface without requiring the application to understand 6LoWPAN encapsulation. For a more comprehensive discussion of the distinction, see theSmartMesh IP Network User Guide.', 'request' : [ ['macAddress', HEXDATA, 8, None], ['priority', INT, 1, 'packetPriority'], @@ -825,37 +831,16 @@ def deserialize(self,type,cmdId,byteArray): }, 'responseCodes': { 'RC_OK' : 'Command successfully completed', - 'RC_END_OF_LIST' : 'Specified mote is not found or is not in operational state', + 'RC_NOT_FOUND' : 'Specified mote is not found', + 'RC_INV_STATE' : 'Mote is not in operational state', 'RC_NACK' : 'User commands queue is full or could not allocate memory buffer for payload', - 'RC_INVALID_ARGUMENT' : 'Payload size exceeds maximum allowed value or the 6LowPAN packet is invalid', - }, - }, - { - 'id' : 60, - 'name' : 'startLocation', - 'description': '\n\nThe startLocation command sends a request to a set of motes to generate distance measurements to a mobile mote. The manager sends a series of commands to the specified motes active the location links for enough time to perform the requested measurements. More than one startLocation request may be queued up by the manager, but the queue size is limited. The caller must be able to handle a queue full error and retry. The startLocation request contains a variable number of fixed motes. The manager determines the number of fixedMotes provided by the caller by checking the length of the request. \n\n\n\nThe startLocation call returns acallbackId that is used to inform the caller of the progress of the distance measurement (seeEvent Notifications). Location events are similar to commandFinished events. The distance measurements are returned as ipData notificationsfrom the fixed motes. The startLocation command is only allowed if the licenseallows it.', - 'request' : [ - ['numMeasurements', INT, 1, None], - ['mobileMote', HEXDATA, 8, None], - ['fixedMotes', HEXDATA, None,None], - ], - 'response' : { - 'FIELDS': [ - [RC, INT, 1, True], - ['callbackId', INT, 4, None], - ], - }, - 'responseCodes': { - 'RC_OK' : 'Command successfully queued', - 'RC_INVALID_COMMAND' : 'License does not allow location queries', - 'RC_END_OF_LIST' : 'One of the motes is not found or is not in operational state', - 'RC_NACK' : 'User commands queue is full', + 'RC_INVALID_ARGUMENT' : 'Payload size exceeds maximum allowed value or the 6LoWPAN packet is invalid', }, }, { 'id' : 61, 'name' : 'restoreFactoryDefaults', - 'description': 'The restoreFactoryDefaults command restores the default configuration and clears the ACL. This command does not affect the license.', + 'description': 'The restoreFactoryDefaults command restores the default configuration and clears the ACL. This command does not affect the license.This change is persistent.', 'request' : [ ], 'response' : { @@ -871,7 +856,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 62, 'name' : 'getMoteInfo', - 'description': 'The getMoteInfo command returns dynamic information for the specified mote. ', + 'description': 'The getMoteInfo command returns dynamic information for the specified mote.', 'request' : [ ['macAddress', HEXDATA, 8, None], ], @@ -892,7 +877,7 @@ def deserialize(self,type,cmdId,byteArray): }, 'responseCodes': { 'RC_OK' : 'Command successfully completed', - 'RC_INVALID_ARGUMENT' : 'No such mote', + 'RC_NOT_FOUND' : 'No such mote', }, }, { @@ -914,7 +899,7 @@ def deserialize(self,type,cmdId,byteArray): ['ccaMode', INT, 1, True], ['channelList', INT, 2, None], ['autoStartNetwork', BOOL, 1, None], - ['locMode', INT, 1, 'locationMode'], + ['locMode', INT, 1, None], ['bbMode', INT, 1, 'backboneFrameMode'], ['bbSize', INT, 1, None], ['isRadioTest', INT, 1, None], @@ -944,6 +929,9 @@ def deserialize(self,type,cmdId,byteArray): ['netLatency', INT, 4, None], ['netState', INT, 1, 'networkState'], ['ipv6Address', HEXDATA, 16, None], + ['numLostPackets', INT, 4, None], + ['numArrivedPackets', INT, 8, None], + ['maxNumbHops', INT, 1, None], ], }, 'responseCodes': { @@ -970,7 +958,7 @@ def deserialize(self,type,cmdId,byteArray): }, 'responseCodes': { 'RC_OK' : 'Command successfully completed', - 'RC_INVALID_ARGUMENT' : 'No such mote', + 'RC_NOT_FOUND' : 'No such mote', }, }, { @@ -1009,7 +997,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 68, 'name' : 'setIPConfig', - 'description': 'The setIPConfig command sets the manager\'s IP configuration parameters, including the IPv6 address and mask.', + 'description': 'The setIPConfig command sets the IPv6 prefix of the mesh network. Only the upper 8 bytes of the IPv6 address are relevant: the lower 8 bytes of the IPv6 address are ignored, and lower 8 bytes of the mask field are reserved and should be set to 0.This change is persistent.', 'request' : [ ['ipv6Address', HEXDATA, 16, None], ['mask', HEXDATA, 16, None], @@ -1021,12 +1009,13 @@ def deserialize(self,type,cmdId,byteArray): }, 'responseCodes': { 'RC_OK' : 'Command successfully completed', + 'RC_WRITE_FAIL' : 'Flash write error, can\'t save new settings', }, }, { 'id' : 69, 'name' : 'deleteMote', - 'description': 'The deleteMote command deletes a mote from the manager\'s list. A mote can only be deleted if it in the Lost or Unknown states. ', + 'description': 'The deleteMote command deletes a mote from the manager\'s list. A mote can only be deleted if it in the Lost or Unknown states. This change is persistent.', 'request' : [ ['macAddress', HEXDATA, 8, None], ], @@ -1037,8 +1026,9 @@ def deserialize(self,type,cmdId,byteArray): }, 'responseCodes': { 'RC_OK' : 'Command successfully completed', - 'RC_END_OF_LIST' : 'Specified mote is not found', - 'RC_VALIDATION_ERROR' : 'Mote state is not Lost or mote is access point', + 'RC_NOT_FOUND' : 'Specified mote is not found', + 'RC_INV_STATE' : 'Mote state is not Lost or mote is access point', + 'RC_WRITE_FAIL' : 'Flash write error, can\'t save new settings', }, }, { @@ -1108,8 +1098,9 @@ def deserialize(self,type,cmdId,byteArray): ], }, 'responseCodes': { + 'RC_NOT_FOUND' : 'No such mote.', + 'RC_INV_STATE' : 'Mote is not in operational state', 'RC_END_OF_LIST' : 'The index requested is greater than number of links.', - 'RC_INVALID_ARGUMENT' : 'No such mote.', }, }, ] @@ -1136,7 +1127,7 @@ def deserialize(self,type,cmdId,byteArray): }, { 'id' : 2, - 'name' : 'eventCommandFinish', + 'name' : 'eventCommandFinished', 'description': 'The commandFinished notification is used when a reliable command response is received.', 'response' : { 'FIELDS': [ @@ -1168,7 +1159,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 5, 'name' : 'eventMoteLost', - 'description': 'This notification is sent when a mote\'s state changes to "Lost," which may occur when a mote becomes unreachable through the network.', + 'description': 'This notification is sent when a mote\'s state changes to Lost, which indicates that the mote is not responding to downstream messages.', 'response' : { 'FIELDS': [ ['macAddress', HEXDATA, 8, None], @@ -1177,8 +1168,8 @@ def deserialize(self,type,cmdId,byteArray): }, { 'id' : 6, - 'name' : 'eventNetTime', - 'description': 'The time notification is triggered by the client asserting the TIME pin or by calling the getTime command. This notification contains the time when the TIME pin was asserted (or the getTime command was processed) expressed as:\n\n- ASN--The absolute slot number (the number of timeslots since 20:00:00 UTC July 2,2002 if UTC is set on manager, otherwise since Jan 1, 1970)\n- Uptime--The number of seconds since the device was booted\n-- Unix time--The number of seconds and microseconds since Jan 1, 1970 in UTC', + 'name' : 'eventNetworkTime', + 'description': 'The time notification is triggered by the client asserting the TIME pin or by calling the getTime command. This notification contains the time when the TIME pin was asserted (or the getTime command was processed) expressed as:\n\n-ASNThe absolute slot number (the number of timeslots since "7/2/2002 8:00:00 PM PST"if UTC is set on manager, otherwise since Jan 1, 1970)\n\n\n-UptimeThe number of seconds since the device was booted\n-UnixtimeThe number of seconds and microseconds since Jan 1, 1970 in UTC', 'response' : { 'FIELDS': [ ['uptime', INT, 4, None], @@ -1199,7 +1190,7 @@ def deserialize(self,type,cmdId,byteArray): ['macAddress', HEXDATA, 8, None], ['delay', INT, 4, None], ['voltage', INT, 2, None], - ['temperature', INT, 1, None], + ['temperature', INTS, 1, None], ], }, }, @@ -1278,7 +1269,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 2, 'name' : 'notifLog', - 'description': 'Log notification', + 'description': 'A log notifications is generated in response to the getLog command. Each log notification contains a message from the mote\'s log.', 'response' : { 'FIELDS': [ ['macAddress', HEXDATA, 8, None], @@ -1289,7 +1280,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 4, 'name' : 'notifData', - 'description': 'Data payload notification', + 'description': 'The data notification contains a header and a variable length array of binary data. The length of the data is determined based on the length of the notification.\n\nThe manager forwards all packets received on its IP address and non-manager ports as data notifications.', 'response' : { 'FIELDS': [ ['utcSecs', INT, 8, None], @@ -1304,7 +1295,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 5, 'name' : 'notifIpData', - 'description': '6lowpan packet notification', + 'description': 'The ipData notification contains full IP packet sent by the mote, including 6Lowpan header, UDP header, and the UDP payload. Manager generates this notification when it receives packet from a mote with destination other than manager\'s own IP address. The size of thedata field can be calculated by subtracting the fixed header size (up to macAddress)from the size of overall notification packet.', 'response' : { 'System': [ ['utcSecs', INT, 8, None], @@ -1317,7 +1308,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 6, 'name' : 'notifHealthReport', - 'description': 'Health report notification', + 'description': 'The healthReport notifications include the raw payload of health reports received from devices. The payload contains one or more specific health report messages. Each message contains an identifier, length and variable-sized data. The individual healthReport message structures are defined below.', 'response' : { 'System': [ ['macAddress', HEXDATA, 8, None], diff --git a/SmartMeshSDK/ApiDefinition/IpMoteDefinition.py b/SmartMeshSDK/ApiDefinition/IpMoteDefinition.py index 2986391..fc91d0a 100644 --- a/SmartMeshSDK/ApiDefinition/IpMoteDefinition.py +++ b/SmartMeshSDK/ApiDefinition/IpMoteDefinition.py @@ -6,6 +6,7 @@ class IpMoteDefinition(ApiDefinition.ApiDefinition): ''' \ingroup ApiDefinition + \brief API definition for the IP mote. \note This class inherits from ApiDefinition. It redefines the attributes of @@ -57,6 +58,7 @@ def deserialize(self,type,cmdId,byteArray): [0x0E, 'RC_NOT_FOUND', 'Resource not found'], [0x0F, 'RC_INVALID_VALUE', 'Invalid value supplied'], [0x10, 'RC_ACCESS_DENIED', 'Access to resource or command is denied'], + [0x12, 'RC_ERASE_FAIL', 'Erase operation failed'], ], 'serviceType': [ [0, 'bandwidth', 'Bandwidth-type service'], @@ -105,7 +107,7 @@ def deserialize(self,type,cmdId,byteArray): ], 'packetTransmitStatus': [ [0x00, 'ok', 'Packet sent into the network'], - [0x01, 'fail', 'packet was dropped due to timeout or lack of route'], + [0x01, 'fail', 'Packet dropped'], ], 'channel': [ [0, '2.405GHz', ''], @@ -157,7 +159,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 0x02, 'name' : 'joinKey', - 'description': 'The setParameter command may be used to set the join key in mote\'s persistent storage. Join keys are used by motes to establish secure connection with the network. The join key is used at next join. \n\nReading the joinKey parameter is prohibited for security reasons. \n\n', + 'description': 'The setParameter command may be used to set the join key in mote\'s persistent storage. Join keys are used by motes to establish secure connection with the network. The join key is used at next join. \n\nReading the joinKey parameter is prohibited for security reasons.', 'request' : [ ['joinKey', HEXDATA, 16, None], ], @@ -205,7 +207,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 0x06, 'name' : 'joinDutyCycle', - 'description': 'The setParameter command allows the microprocessor to control the ratio of active listen time to doze time (a low-power radio state) during the period when the mote is searching for the network. If you desire a faster join time at the risk of higher power consumption, use the setParameter command to increase the join duty cycle up to 100%. This setting is persistent and takes effect immediately.', + 'description': 'The setParameter command allows the microprocessor to control the ratio of active listen time to doze time (a low-power radio state) during the period when the mote is searching for the network. If you desire a faster join time at the risk of higher power consumption, use the setParameter command to increase the join duty cycle up to 100%. This setting is persistent and requires a reset to take effect.', 'request' : [ ['dutyCycle', INT, 1, None], ], @@ -220,7 +222,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 0x0B, 'name' : 'eventMask', - 'description': 'The setParameter command allows the microprocessor to selectively subscribe to event notifications. The default value of eventMask at mote reset is all 1s - all events are enabled. This setting is not persistent.', + 'description': 'The setParameter command allows the microprocessor to selectively subscribe to event notifications. The default value of eventMask at mote reset is all 1s - all events are enabled. This setting is not persistent.\n\nNew event type may be added in future revisions of mote software. It is recommended that the client code only subscribe to known events and gracefully ignore all unknown events.', 'request' : [ ['eventMask', HEXDATA, 4, None], ], @@ -249,90 +251,6 @@ def deserialize(self,type,cmdId,byteArray): 'RC_WRITE_FAIL' : 'Couldn\'t update persistent storage', }, }, - { - 'id' : 0x16, - 'name' : 'macMicKey', - 'description': 'Set the key used for MAC authentication.', - 'request' : [ - ['macMicKey', HEXDATA, 16, None], - ], - 'response' : { - 'FIELDS': [ - ], - }, - }, - { - 'id' : 0x17, - 'name' : 'moteId', - 'description': 'Set the mote\'s short address.', - 'request' : [ - ['moteId', INT, 2, None], - ], - 'response' : { - 'FIELDS': [ - ], - }, - }, - { - 'id' : 0x18, - 'name' : 'ipv6Address', - 'description': 'Set the mote\'s IPv6 address.', - 'request' : [ - ['ipv6Address', HEXDATA, 16, None], - ], - 'response' : { - 'FIELDS': [ - ], - }, - }, - { - 'id' : 0x19, - 'name' : 'ccaMode', - 'description': '', - 'request' : [ - ['ccaMode', INT, 1, True], - ], - 'response' : { - 'FIELDS': [ - ], - }, - }, - { - 'id' : 0x1A, - 'name' : 'Channels', - 'description': '', - 'request' : [ - ['bitmap', INT, 2, None], - ], - 'response' : { - 'FIELDS': [ - ], - }, - }, - { - 'id' : 0x1B, - 'name' : 'advGraph', - 'description': '', - 'request' : [ - ['graphId', INT, 1, None], - ], - 'response' : { - 'FIELDS': [ - ], - }, - }, - { - 'id' : 0x1C, - 'name' : 'hrTimer', - 'description': 'Set the duration (in seconds) between consecutive health reports.', - 'request' : [ - ['hrTimer', INT, 2, None], - ], - 'response' : { - 'FIELDS': [ - ], - }, - }, { 'id' : 0x1D, 'name' : 'routingMode', @@ -349,23 +267,6 @@ def deserialize(self,type,cmdId,byteArray): 'RC_INVALID_VALUE' : 'Invalid value of mode parameter', }, }, - { - 'id' : 0x1E, - 'name' : 'appInfo', - 'description': '', - 'request' : [ - ['vendorId', INT, 2, None], - ['appId', INT, 1, None], - ['appMajorSwRev', INT, 1, None], - ['appMinorSwRev', INT, 1, None], - ['appPatchNum', INT, 1, None], - ['appBuildNum', INT, 2, None], - ], - 'response' : { - 'FIELDS': [ - ], - }, - }, { 'id' : 0x1F, 'name' : 'powerSrcInfo', @@ -391,30 +292,6 @@ def deserialize(self,type,cmdId,byteArray): 'RC_OK' : 'Command completed successfully', }, }, - { - 'id' : 0x21, - 'name' : 'mobilityType', - 'description': '', - 'request' : [ - ['mobilityType', INT, 1, True], - ], - 'response' : { - 'FIELDS': [ - ], - }, - }, - { - 'id' : 0x22, - 'name' : 'advKey', - 'description': 'Set the key used by the mote for advertisement packets', - 'request' : [ - ['advKey', HEXDATA, 16, None], - ], - 'response' : { - 'FIELDS': [ - ], - }, - }, { 'id' : 0x24, 'name' : 'autoJoin', @@ -503,7 +380,7 @@ def deserialize(self,type,cmdId,byteArray): ], 'response' : { 'FIELDS': [ - ['eventMask', INT, 4, None], + ['eventMask', HEXDATA, 4, None], ], }, 'responseCodes': { @@ -684,23 +561,6 @@ def deserialize(self,type,cmdId,byteArray): 'RC_OK' : 'Command completed successfully', }, }, - { - 'id' : 0x1E, - 'name' : 'appInfo', - 'description': '', - 'request' : [ - ], - 'response' : { - 'FIELDS': [ - ['vendorId', INT, 2, None], - ['appId', INT, 2, None], - ['appMajorSwRev', INT, 1, None], - ['appMinorSwRev', INT, 1, None], - ['appPatchNum', INT, 1, None], - ['appBuildNum', INT, 2, None], - ], - }, - }, { 'id' : 0x1F, 'name' : 'powerSrcInfo', @@ -714,7 +574,7 @@ def deserialize(self,type,cmdId,byteArray): ['currentLimit_0', INT, 2, None], ['dischargePeriod_0', INT, 2, None], ['rechargePeriod_0', INT, 2, None], - ['currrentLimit_1', INT, 2, None], + ['currentLimit_1', INT, 2, None], ['dischargePeriod_1', INT, 2, None], ['rechargePeriod_1', INT, 2, None], ['currentLimit_2', INT, 2, None], @@ -726,62 +586,6 @@ def deserialize(self,type,cmdId,byteArray): 'RC_OK' : 'Command completed successfully', }, }, - { - 'id' : 0x20, - 'name' : 'powerCostInfo', - 'description': '', - 'request' : [ - ], - 'response' : { - 'FIELDS': [ - ['maxTxCost', INT, 1, None], - ['maxRxCost', INT, 1, None], - ['minTxCost', INT, 1, None], - ['minRxCost', INT, 1, None], - ], - }, - }, - { - 'id' : 0x21, - 'name' : 'mobilityType', - 'description': '', - 'request' : [ - ], - 'response' : { - 'FIELDS': [ - ['mobilityType', INT, 1, True], - ], - }, - }, - { - 'id' : 0x22, - 'name' : 'advKey', - 'description': 'Get the key used for advertisement packets.', - 'request' : [ - ], - 'response' : { - 'FIELDS': [ - ['advKey', HEXDATA, 16, None], - ], - }, - }, - { - 'id' : 0x23, - 'name' : 'sizeInfo', - 'description': 'Get information about the mote\'s storage capacity.', - 'request' : [ - ], - 'response' : { - 'FIELDS': [ - ['maxFrames', INT, 1, None], - ['maxLinks', INT, 2, None], - ['maxNeighbors', INT, 1, None], - ['maxRoutes', INT, 1, None], - ['maxGraphs', INT, 1, None], - ['maxMacQueue', INT, 1, None], - ], - }, - }, { 'id' : 0x24, 'name' : 'autoJoin', @@ -835,7 +639,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 0x06, 'name' : 'join', - 'description': 'The join command requests that mote start searching for the network and attempt to join.The mote must be in the IDLE state for this command to be valid. Note that the join time will be affected by the maximum current setting.', + 'description': 'The join command requests that mote start searching for the network and attempt to join.The mote must be in theIdle state for this command to be valid. Note that the join time will be affected by the maximum current setting.', 'request' : [ ], 'response' : { @@ -897,10 +701,11 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 0x0C, 'name' : 'testRadioRx', - 'description': 'The testRadioRx command clears all previously collected statistics and initiates a test of radio reception for the specified channel and duration. During the test, the mote keeps statistics about the number of packets received (with and without error). The test results may be retrieved using the getParameter command. The testRadioRx command may only be issued in Idle mode. The mote must be reset (either hardware or software reset) after radio tests are complete and prior to joining.\n\n\n\nChannel numbering is 0-14, corresponding to IEEE 2.4 GHz channels 11-25.', + 'description': 'The testRadioRx command clears all previously collected statistics and initiates a test of radio reception for the specified channel and duration. During the test, the mote keeps statistics about the number of packets received (with and without error). Note that the non-zero station id specified in this command must match the transmitter\'s station id. This is necessary to isolate traffic if multiple tests are running in the same radio space. The test results may be retrieved using the getParameter command. The testRadioRx command may only be issued in Idle mode. The mote must be reset (either hardware or software reset) after radio tests are complete and prior to joining.\n\n\n\nChannel numbering is 0-15, corresponding to IEEE 2.4 GHz channels 11-26.', 'request' : [ ['channelMask', HEXDATA, 2, None], ['time', INT, 2, None], + ['stationId', INT, 1, None], ], 'response' : { 'FIELDS': [ @@ -909,7 +714,7 @@ def deserialize(self,type,cmdId,byteArray): }, 'responseCodes': { 'RC_OK' : 'Command was accepted', - 'RC_INVALID_VALUE' : 'The mote is not in idle state', + 'RC_INVALID_VALUE' : 'The mote is not in Idle state', 'RC_BUSY' : 'Another test operation in progress', }, }, @@ -1047,7 +852,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 0x24, 'name' : 'search', - 'description': 'The searchcommand requests that mote start listening for advertisements and report those heard from any network withoutattempting to join.The mote must be in the IDLE state for this command to be valid. The search state can be exiting by issuing the join command or the reset command.', + 'description': 'The searchcommand requests that mote start listening for advertisements and report those heard from any network withoutattempting to join.The mote must be in the Idle state for this command to be valid. The search state can be exiting by issuing the join command or the reset command.', 'request' : [ ], 'response' : { @@ -1063,7 +868,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 0x28, 'name' : 'testRadioTxExt', - 'description': 'ThetestRadioTxExtcommand allows the microprocessor to initiate a radio transmission test. This command may only be issued prior to the mote joining the network. Three types of transmission tests are supported:\n\n-Packet transmission\n-Continuous modulation\n-Continuous wave (unmodulated signal)\n\nIn a packet transmission test, the mote generates arepeatCntnumber of packet sequences. Each sequence consists of up to 10 packets with configurable size and delays. Each packet starts with a PHY preamble (5 bytes), followed by a PHY length field (1 byte), followed by data payload of up to 125 bytes, and finally a 2-byte 802.15.4 CRC at the end. Bytes 0 and 1 contain the packet number (in big-endian format) that increments with every packet transmitted. Bytes 2..N contain a counter (from 0..N-2) that increments with every byte inside payload. Transmissions occur on the set of channels defined by chanMask, selected inpseudo-randomorder.\n\nIn a continuous modulation test, the mote generates continuous pseudo-random modulated signal, centered at the specified channel. The test is stopped by resetting the mote.\n\nIn a continuous wave test, the mote generates an unmodulated tone, centered at the specified channel. The test tone is stopped by resetting the mote.\n\nThetestRadioTxExtcommand may only be issued when the mote is in Idle mode, prior to its joining the network. The mote must be reset (either hardware or software reset) after radio tests are complete and prior to joining.\n\n\n\nChannel numbering is 0-14, corresponding to IEEE 2.4 GHz channels 11-25.', + 'description': 'ThetestRadioTxExtcommand allows the microprocessor to initiate a radio transmission test. This command may only be issued prior to the mote joining the network. Three types of transmission tests are supported:\n\n-Packet transmission\n-Continuous modulation\n-Continuous wave (unmodulated signal)\n\nIn a packet transmission test, the mote generates arepeatCntnumber of packet sequences. Each sequence consists of up to 10 packets with configurable size and delays. Each packet starts with a PHY preamble (5 bytes), followed by a PHY length field (1 byte), followed by data payload of up to 125 bytes, and finally a 2-byte 802.15.4 CRC at the end. Byte 0 of the payload contains stationId of the sender. Bytes 1 and 2 contain the packet number (in big-endian format) that increments with every packet transmitted. Bytes 3..N contain a counter (from 0..N-3) that increments with every byte inside payload. Transmissions occur on the set of channels defined by chanMask, selected inpseudo-randomorder.\n\nIn a continuous modulation test, the mote generates continuous pseudo-random modulated signal, centered at the specified channel. The test is stopped by resetting the mote.\n\nIn a continuous wave test, the mote generates an unmodulated tone, centered at the specified channel. The test tone is stopped by resetting the mote.\n\nThetestRadioTxExtcommand may only be issued when the mote is in Idle mode, prior to its joining the network. The mote must be reset (either hardware or software reset) after radio tests are complete and prior to joining.\n\n\n\nChannel numbering is 0-15, corresponding to IEEE 2.4 GHz channels 11-26.', 'request' : [ ['testType', INT, 1, 'radioTestTypes'], ['chanMask', HEXDATA, 2, None], @@ -1090,6 +895,7 @@ def deserialize(self,type,cmdId,byteArray): ['delay_9', INT, 2, None], ['pkLen_10', INT, 1, None], ['delay_10', INT, 2, None], + ['stationId', INT, 1, None], ], 'response' : { 'FIELDS': [ @@ -1102,6 +908,22 @@ def deserialize(self,type,cmdId,byteArray): 'RC_BUSY' : 'Mote is executing another radio test operation', }, }, + { + 'id' : 0x29, + 'name' : 'zeroize', + 'description': 'Zeroize (zeroise) command erases flash area that is used to store configuration parameters, such as join keys. This command is intended to satisfy zeroization requirement ofFIPS-140 standard. After the command executes, the mote should be reset.', + 'request' : [ + ], + 'response' : { + 'FIELDS': [ + [RC, INT, 1, True], + ], + }, + 'responseCodes': { + 'RC_OK' : 'Command completed successfully', + 'RC_ERASE_FAIL' : 'Flash could not be erased due to unexpected internal error', + }, + }, ] # We redefine this attribute inherited from ApiDefinition. See @@ -1110,7 +932,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 0x0D, 'name' : 'timeIndication', - 'description': 'The timeIndication notification applies to mote products that support a time interrupt into the mote. The time packet includes the network time and the current UTC time relative to the manager.\n\nFor LTC5800-IPM based products, driving the TIMEn pin low (assert) wakes the processor. The pin must asserted for a minimum of tstrobe micro-s. De-asserting the pin latches the time, and a timeIndication will be generated within tresponse ms. Refer to the mote datasheet for product-specific details. The LTC5800-IPM datasheet also provides information about TIME pin usage.\n\nThe processor will remain awake and drawing current while the TIMEn pin is asserted. To avoid drawing excess current, take care to minimize the duration of the TIMEn pin being asserted past tstrobe minimums.', + 'description': 'ThetimeIndicationnotification applies to mote products that support a time interrupt into the mote. The time packet includes the network time and the current UTC time relative to the manager.\n\nFor LTC5800-IPMbased products, driving the TIMEn pin low (assert) wakes the processor. The pin must asserted for a minimum of tstrobes. De-asserting the pin latches the time, and a timeIndication will be generated within tresponsems.Refer to theLTC5800-IPM Datasheet for additional information about TIME pin usage.The processor will remain awake and drawing current while the TIMEn pin is asserted. To avoid drawing excess current, take care to minimize the duration of the TIMEn pin being asserted past tstrobe minimum.', 'response' : { 'FIELDS': [ ['uptime', INT, 4, None], @@ -1124,7 +946,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 0x0F, 'name' : 'events', - 'description': 'Informs the application that some new events occurred since the last event notification. If the mote cannot send a packet (due to full queue or lack of buffers), it will retry once a minute until it succeeds.', + 'description': 'The mote sends an events notification to inform the application of new events that occurred since the previous events notification. The notification also contains up-to-date information about current mote state and any pending alarms.', 'response' : { 'FIELDS': [ ['events', INT, 4, 'moteEvents'], @@ -1159,7 +981,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 0x25, 'name' : 'txDone', - 'description': 'The txDone notification informs the application that the mote has finished sending a packet. This notification will only be generated if the user has provided a valid (0x0000-0xFFFE) packet id when calling the sendTo command.', + 'description': 'The txDone notification informs the application that the mote has finished sending a packet. This notification will only be generated if the user has provided a valid (0x0000-0xFFFE) packetID when calling the sendTo command.', 'response' : { 'FIELDS': [ ['packetId', INT, 2, None], @@ -1170,7 +992,7 @@ def deserialize(self,type,cmdId,byteArray): { 'id' : 0x26, 'name' : 'advReceived', - 'description': 'The \'advReceived\' notification is generated by the mote when it is in promiscuous listen state (see the Mote States table) and it receives an advertisement.', + 'description': 'The advReceived notification is generated by the mote when it is in Promiscuous Listen state (see the Mote States table) and it receives an advertisement.', 'response' : { 'FIELDS': [ ['netId', INT, 2, None], diff --git a/SmartMeshSDK/ApiDefinition/xmlutils.py b/SmartMeshSDK/ApiDefinition/xmlutils.py index d33c59e..708db71 100644 --- a/SmartMeshSDK/ApiDefinition/xmlutils.py +++ b/SmartMeshSDK/ApiDefinition/xmlutils.py @@ -28,7 +28,13 @@ def xml_obj_to_dict(xml_obj): ch2_node = ch2_node.nextSibling # populate the attributes dict with the value - attrs[n] = v + # create a list of values if the child name already exists + if n in attrs: + if not type(attrs[n]) is list: + attrs[n] = [attrs[n]] + attrs[n].append(v) + else: + attrs[n] = v if ch_node.hasAttributes(): for name, val in ch_node.attributes.items(): attr_name = n + name.capitalize() diff --git a/SmartMeshSDK/ApiException.py b/SmartMeshSDK/ApiException.py index 59c2e44..ccee554 100644 --- a/SmartMeshSDK/ApiException.py +++ b/SmartMeshSDK/ApiException.py @@ -68,7 +68,6 @@ class CommandError(Exception): UNKNOWN_FIELD = 10 MALFORMED_FIELD = 11 TOO_FEW_BYTES = 12 - TOO_MANY_BYTES = 13 VALUE_NOT_IN_OPTIONS = 14 descriptions = { @@ -84,7 +83,6 @@ class CommandError(Exception): UNKNOWN_FIELD: 'Unknown field', MALFORMED_FIELD: 'Field malformed', TOO_FEW_BYTES: 'Too few bytes in received packet', - TOO_MANY_BYTES: 'Too many bytes in received packet', VALUE_NOT_IN_OPTIONS:'The value of this field is not in the valid options', } diff --git a/SmartMeshSDK/AppUtils.py b/SmartMeshSDK/AppUtils.py new file mode 100644 index 0000000..ab99b29 --- /dev/null +++ b/SmartMeshSDK/AppUtils.py @@ -0,0 +1,32 @@ + +import os +import logging.config +import inspect + +def configureLogging(): + + # compute the log filename + fullpath = inspect.stack()[1][1] + filename = (os.path.split(fullpath)[-1]).split('.')[0] + logfilename = '{0}.log'.format(filename) + + # find the log configuration file, if any + logconf = None + for p in [ + os.path.join('..', 'logging.conf'), + os.path.join('bin','logging.conf'), + ]: + if os.path.exists(p): + logconf = p + break + + # start logging + if logconf: + logging.config.fileConfig( + fname = logconf, + defaults = { + 'logfilename': logfilename, + } + ) + else: + print "WARNING: no log configuration file could be found." \ No newline at end of file diff --git a/SmartMeshSDK/FormatUtils.py b/SmartMeshSDK/FormatUtils.py new file mode 100644 index 0000000..c6bbce8 --- /dev/null +++ b/SmartMeshSDK/FormatUtils.py @@ -0,0 +1,71 @@ +import os +import time +import urllib + +LOG_FORMAT_TIMESTAMP = '%Y/%m/%d %H:%M:%S' + +def formatBuffer(buf): + ''' + example: [0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88] -> "11-22-33-44-55-66-77-88" + ''' + return '-'.join(["%.2x"%i for i in buf]) + +def formatMacString(mac,upper=False): + ''' + example: 0x1122334455667788 -> "11-22-33-44-55-66-77-88" + ''' + + if upper: + res = '-'.join(["%.2X"%i for i in mac]) + else: + res = '-'.join(["%.2x"%i for i in mac]) + + return res + +def formatShortMac(mac): + ''' + example: 0x1122334455667788 -> "77-88" + ''' + return '-'.join(["%.2x"%i for i in mac[6:]]) + +def formatNamedTuple(tup): + output = [] + output += ['{0}:'.format(tup.__class__.__name__)] + for k in tup._fields: + v = getattr(tup, k) + try: + v = formatBuffer(v) + except TypeError: + pass + output += ['{0:>20}: {1}'.format(k,v)] + output = '\n'.join(output) + return output + +def formatDictionnary(dict): + output = [] + for (k,v) in dict.items(): + output += ['{0:>20}: {1}'.format(k,v)] + output = '\n'.join(output) + return output + +def quote(string): + return urllib.quote(string, '') + +def unquote(string): + return urllib.unquote(string) + +def formatConnectionParams(connectionParams): + if isinstance(connectionParams,str): + return connectionParams + elif isinstance(connectionParams,tuple): + return '{0}_{1}'.format(*connectionParams) + else: + raise SystemError("unexpected connectionParams format {0}".format(connectionParams)) + +def formatTimestamp(timestamp=None): + if timestamp==None: + timestamp = time.time() + return '{0}.{1}'.format( + time.strftime(LOG_FORMAT_TIMESTAMP,time.localtime(timestamp)), + int((timestamp*1000)%1000) + ) diff --git a/SmartMeshSDK/GenApiConnectors.py b/SmartMeshSDK/GenApiConnectors.py index c5a4038..116c2fb 100644 --- a/SmartMeshSDK/GenApiConnectors.py +++ b/SmartMeshSDK/GenApiConnectors.py @@ -1,19 +1,18 @@ -''' -Created on Mar 12, 2012 - -@author: alushin -''' +#============================ adjust path ===================================== import sys import os -sys.path.insert(0, os.path.join(sys.path[0], '..')) +if __name__ == "__main__": + here = sys.path[0] + sys.path.insert(0, os.path.join(here, '..')) -import ApiException -from ApiDefinition.IpMgrDefinition import IpMgrDefinition -from ApiDefinition.IpMoteDefinition import IpMoteDefinition -from ApiDefinition.HartMgrDefinition import HartMgrDefinition -from ApiDefinition.HartMoteDefinition import HartMoteDefinition +#============================ imports ========================================= +from SmartMeshSDK import ApiException +from SmartMeshSDK.ApiDefinition.IpMgrDefinition import IpMgrDefinition +from SmartMeshSDK.ApiDefinition.IpMoteDefinition import IpMoteDefinition +from SmartMeshSDK.ApiDefinition.HartMgrDefinition import HartMgrDefinition +from SmartMeshSDK.ApiDefinition.HartMoteDefinition import HartMoteDefinition #============================ templates ======================================= @@ -22,12 +21,16 @@ \'\'\' import collections -import ApiException +from SmartMeshSDK import ApiException from {MODULE_NAME} import {BASE_CLASS_NAME} +## +# \\addtogroup {GEN_CLASS_NAME} +# \\{{ +# + class {GEN_CLASS_NAME}({BASE_CLASS_NAME}): \'\'\' - \ingroup ApiConnector \\brief {BRIEF_DESCRIPTION} \'\'\' ''' @@ -148,6 +151,13 @@ def getNotification(self, timeoutSec=-1) : res = {BASE_CLASS_NAME}.send(self, ['startLocation'], {{"numFrames" : numFrames, "mobileMote" : mobileMote, "fixedMotes" : payload}}) ''' +TMPL_ENDCLASSDEF = ''' +## +# end of {GEN_CLASS_NAME} +# \\}} +# +''' + def printStartLocation(names, respFieldsName, reqFieldsName, CMD_NAME, CMD_PARMS, DESCR, TUPLE_PARAMS, NAMES, PARAMS_DICT, @@ -245,8 +255,9 @@ class GenApiConnectors(object): #======================== public ========================================== - def __init__(self, apiDefName, myClassName, baseClassName, baseModuleName, outputFileName = None, briefDescription = ''): - apiDefClass = globals()[apiDefName] + def __init__(self, apiDefName, myClassName, baseClassName, baseModuleName, outputFileName = None, briefDescription = '', apiDefClass=None): + if apiDefName: + apiDefClass = globals()[apiDefName] self.apiDef = apiDefClass() self.myClassName = myClassName self.baseClassName = baseClassName @@ -267,6 +278,8 @@ def gen(self): self.genCmd() self.outFile.write('\n #======================== notifications ===================================\n') self.genNotif() + s = TMPL_ENDCLASSDEF.format(GEN_CLASS_NAME = self.myClassName) + self.outFile.write(s) self.genFinish() #======================== private ========================================= @@ -463,14 +476,20 @@ def getValidationComment(self, options): return s def genFile(srcFileName, dstFileName, comment): - apiDefName = os.path.splitext(os.path.basename(srcFileName))[0] + if isinstance(srcFileName, str): + apiDefClass = None + apiDefName = os.path.splitext(os.path.basename(srcFileName))[0] + else: + apiDefClass = srcFileName + apiDefName = None baseName = os.path.splitext(os.path.basename(dstFileName))[0] - gen = GenApiConnectors(apiDefName, - baseName, - baseName + "Internal", - baseName + "Internal", - dstFileName, - comment) + gen = GenApiConnectors(apiDefName=apiDefName, + apiDefClass=apiDefClass, + myClassName=baseName, + baseClassName=baseName + "Internal", + baseModuleName=baseName + "Internal", + outputFileName=dstFileName, + briefDescription=comment) gen.gen() def main() : diff --git a/SmartMeshSDK/GenApiDocbook.py b/SmartMeshSDK/GenApiDocbook.py index c541046..51bc139 100644 --- a/SmartMeshSDK/GenApiDocbook.py +++ b/SmartMeshSDK/GenApiDocbook.py @@ -189,7 +189,7 @@ def genOneCommand(self, nameArray, responseFieldsName, requestFieldsName): r = self.apiDef.getResponseFieldNames(self.apiDef.COMMAND, nameArray) responseFieldsName += [n for n in r if n not in self.apiDef.RESERVED] except (ApiException.CommandError) as err: - # means that this function has no reponse fields, which is OK + # means that this function has no response fields, which is OK pass if self.apiDef.hasSubcommands(self.apiDef.COMMAND, nameArray): @@ -240,7 +240,7 @@ def genOneNotification(self, nameArray, responseFieldsName): r = self.apiDef.getResponseFieldNames(self.apiDef.NOTIFICATION,nameArray) responseFieldsName += [n for n in r if n not in self.apiDef.RESERVED] except (ApiException.CommandError) as err: - # means that this function has no reponse fields, which is NOT OK + # means that this function has no response fields, which is NOT OK raise pass diff --git a/SmartMeshSDK/GenIpMgrSubscribe.py b/SmartMeshSDK/GenIpMgrSubscribe.py index c526b89..4984d38 100644 --- a/SmartMeshSDK/GenIpMgrSubscribe.py +++ b/SmartMeshSDK/GenIpMgrSubscribe.py @@ -1,15 +1,24 @@ -''' -Created on Mar 23, 2012 +#============================ adjust path ===================================== -@author: alushin -''' +import sys +import os +if __name__ == "__main__": + here = sys.path[0] + sys.path.insert(0, os.path.join(here, '..')) + +#============================ imports ========================================= + +from SmartMeshSDK import ApiException +from SmartMeshSDK.ApiDefinition.IpMgrDefinition import IpMgrDefinition + +#============================ templates ======================================= TMPL = '''# Warning: Do NOT edit this file directly. Your changes may be overwritten. # This file is automatically generated by GenIpMgrSubscribe.py import threading -import ApiException +from SmartMeshSDK import ApiException class IpMgrSubscribe(object): \'\'\' @@ -162,12 +171,7 @@ def _getCallback(self, name) : return res ''' -import sys -import os -sys.path.insert(0, os.path.join(sys.path[0], '..')) - -import ApiException -from ApiDefinition.IpMgrDefinition import IpMgrDefinition +#============================ main ============================================ NOTIFICATION_ID = 20 ERROR_NOTIF = "error" @@ -264,5 +268,4 @@ def main(): if __name__ == '__main__': main() - - + diff --git a/SmartMeshSDK/HartMgrConnector/HartMgrConnector.py b/SmartMeshSDK/HartMgrConnector/HartMgrConnector.py index 9bf052b..54c0e8b 100644 --- a/SmartMeshSDK/HartMgrConnector/HartMgrConnector.py +++ b/SmartMeshSDK/HartMgrConnector/HartMgrConnector.py @@ -3,12 +3,16 @@ ''' import collections -import ApiException +from SmartMeshSDK import ApiException from HartMgrConnectorInternal import HartMgrConnectorInternal +## +# \addtogroup HartMgrConnector +# \{ +# + class HartMgrConnector(HartMgrConnectorInternal): ''' - \ingroup ApiConnector \brief Public class for the HART Manager connector using the XML API. ''' @@ -253,8 +257,10 @@ def dn_getMote(self, macAddr) : # There is no restriction on the value of this field. # - numLostPackets: 4-byte field formatted as a int.
# There is no restriction on the value of this field. + # - latencyToMote: 4-byte field formatted as a int.
+ # There is no restriction on the value of this field. # - Tuple_dn_getMoteStatistics = collections.namedtuple("Tuple_dn_getMoteStatistics", ['index', 'startTime', 'avgLatency', 'reliability', 'numJoins', 'voltage', 'chargeConsumption', 'temperature', 'numLostPackets']) + Tuple_dn_getMoteStatistics = collections.namedtuple("Tuple_dn_getMoteStatistics", ['index', 'startTime', 'avgLatency', 'reliability', 'numJoins', 'voltage', 'chargeConsumption', 'temperature', 'numLostPackets', 'latencyToMote']) ## # Get the Mote Statistics @@ -276,6 +282,30 @@ def dn_getMoteStatistics(self, macAddr, period, index) : res = HartMgrConnectorInternal.send(self, ['getMoteStatistics'], {"macAddr" : macAddr, "period" : period, "index" : index}) return HartMgrConnector.Tuple_dn_getMoteStatistics(**res) + ## + # The named tuple returned by the dn_getSourceRoute() function. + # + # - destMacAddr: 25-byte field formatted as a string.
+ # There is no restriction on the value of this field. + # - primaryPath: 16-byte field formatted as a list.
+ # There is no restriction on the value of this field. + # - secondaryPath: 16-byte field formatted as a list.
+ # There is no restriction on the value of this field. + # + Tuple_dn_getSourceRoute = collections.namedtuple("Tuple_dn_getSourceRoute", ['destMacAddr', 'primaryPath', 'secondaryPath']) + + ## + # Get the Source Route for a specific Mote + # + # \param destMacAddr 25-byte field formatted as a string.
+ # There is no restriction on the value of this field. + # + # \returns The response to the command, formatted as a #Tuple_dn_getSourceRoute named tuple. + # + def dn_getSourceRoute(self, destMacAddr) : + res = HartMgrConnectorInternal.send(self, ['getSourceRoute'], {"destMacAddr" : destMacAddr}) + return HartMgrConnector.Tuple_dn_getSourceRoute(**res) + ## # The named tuple returned by the dn_getMotes() function. # @@ -439,7 +469,7 @@ def dn_getPathStatistics(self, pathId, period, index) : ## # The named tuple returned by the dn_getSecurity() function. # - # - securityMode: 16-byte field formatted as a string.
+ # - securityMode: 20-byte field formatted as a string.
# This field can only take one of the following values: # - acceptACL: Accept ACL # - acceptCommonJoinKey: Accept common join key @@ -826,15 +856,13 @@ def dn_setNetwork(self, netName, networkId, maxMotes, optimizationEnable, access # There is no restriction on the value of this field. # \param name 16-byte field formatted as a string.
# There is no restriction on the value of this field. - # \param powerSource 16-byte field formatted as a string.
- # There is no restriction on the value of this field. # \param enableRouting 1-byte field formatted as a bool.
# There is no restriction on the value of this field. # # \returns The response to the command, formatted as a #Tuple_dn_setMote named tuple. # - def dn_setMote(self, macAddr, name, powerSource, enableRouting) : - res = HartMgrConnectorInternal.send(self, ['setMote'], {"macAddr" : macAddr, "name" : name, "powerSource" : powerSource, "enableRouting" : enableRouting}) + def dn_setMote(self, macAddr, name, enableRouting) : + res = HartMgrConnectorInternal.send(self, ['setMote'], {"macAddr" : macAddr, "name" : name, "enableRouting" : enableRouting}) return HartMgrConnector.Tuple_dn_setMote(**res) ## @@ -872,7 +900,7 @@ def dn_setSla(self, minNetReliability, maxNetLatency, minNetPathStability, apRdn ## # The named tuple returned by the dn_setSecurity() function. # - # - securityMode: 16-byte field formatted as a string.
+ # - securityMode: 20-byte field formatted as a string.
# This field can only take one of the following values: # - acceptACL: Accept ACL # - acceptCommonJoinKey: Accept common join key @@ -884,7 +912,7 @@ def dn_setSla(self, minNetReliability, maxNetLatency, minNetPathStability, apRdn ## # Set security configuration # - # \param securityMode 16-byte field formatted as a string.
+ # \param securityMode 20-byte field formatted as a string.
# This field can only take one of the following values: # - acceptACL: Accept ACL # - acceptCommonJoinKey: Accept common join key @@ -1309,12 +1337,70 @@ def dn_activateAdvertising(self, macAddr, timeout) : return HartMgrConnector.Tuple_dn_activateAdvertising(**res) ## - # The named tuple returned by the dn_decommission() function. + # The named tuple returned by the dn_startOtap() function. + # + # - result: 32-byte field formatted as a string.
+ # There is no restriction on the value of this field. + # + Tuple_dn_startOtap = collections.namedtuple("Tuple_dn_startOtap", ['result']) + + ## + # This command initiates the OTAP (Over-The-Air-Programming) process to upgrade software on motes and the Access Point. By default, the process will retry the OTAP file transmission 100 times. + # + # + # + # \returns The response to the command, formatted as a #Tuple_dn_startOtap named tuple. + # + def dn_startOtap(self, ) : + res = HartMgrConnectorInternal.send(self, ['startOtap'], {}) + return HartMgrConnector.Tuple_dn_startOtap(**res) + + ## + # The named tuple returned by the dn_startOtapWithRetries() function. + # + # - result: 32-byte field formatted as a string.
+ # There is no restriction on the value of this field. + # + Tuple_dn_startOtapWithRetries = collections.namedtuple("Tuple_dn_startOtapWithRetries", ['result']) + + ## + # This command initiates the OTAP (Over-The-Air-Programming) process to upgrade software for motes and the Access Point, using the specified number of retries. + # + # \param retries 1-byte field formatted as a int.
+ # There is no restriction on the value of this field. + # + # \returns The response to the command, formatted as a #Tuple_dn_startOtapWithRetries named tuple. + # + def dn_startOtapWithRetries(self, retries) : + res = HartMgrConnectorInternal.send(self, ['startOtapWithRetries'], {"retries" : retries}) + return HartMgrConnector.Tuple_dn_startOtapWithRetries(**res) + + ## + # The named tuple returned by the dn_cancelOtap() function. + # + # - result: 32-byte field formatted as a string.
+ # There is no restriction on the value of this field. + # + Tuple_dn_cancelOtap = collections.namedtuple("Tuple_dn_cancelOtap", ['result']) + + ## + # This command cancels the OTAP (Over-The-Air-Programming) process to upgrade software on motes and the access point. + # + # + # + # \returns The response to the command, formatted as a #Tuple_dn_cancelOtap named tuple. + # + def dn_cancelOtap(self, ) : + res = HartMgrConnectorInternal.send(self, ['cancelOtap'], {}) + return HartMgrConnector.Tuple_dn_cancelOtap(**res) + + ## + # The named tuple returned by the dn_decommissionDevice() function. # # - result: 32-byte field formatted as a string.
# There is no restriction on the value of this field. # - Tuple_dn_decommission = collections.namedtuple("Tuple_dn_decommission", ['result']) + Tuple_dn_decommissionDevice = collections.namedtuple("Tuple_dn_decommissionDevice", ['result']) ## # Decommission a device in the network @@ -1322,19 +1408,39 @@ def dn_activateAdvertising(self, macAddr, timeout) : # \param macAddr 25-byte field formatted as a string.
# There is no restriction on the value of this field. # - # \returns The response to the command, formatted as a #Tuple_dn_decommission named tuple. + # \returns The response to the command, formatted as a #Tuple_dn_decommissionDevice named tuple. + # + def dn_decommissionDevice(self, macAddr) : + res = HartMgrConnectorInternal.send(self, ['decommissionDevice'], {"macAddr" : macAddr}) + return HartMgrConnector.Tuple_dn_decommissionDevice(**res) + + ## + # The named tuple returned by the dn_promoteToOperational() function. + # + # - result: 32-byte field formatted as a string.
+ # There is no restriction on the value of this field. # - def dn_decommission(self, macAddr) : - res = HartMgrConnectorInternal.send(self, ['decommission'], {"macAddr" : macAddr}) - return HartMgrConnector.Tuple_dn_decommission(**res) + Tuple_dn_promoteToOperational = collections.namedtuple("Tuple_dn_promoteToOperational", ['result']) ## - # The named tuple returned by the dn_ping() function. + # Promote a quarantined device to operational + # + # \param macAddr 25-byte field formatted as a string.
+ # There is no restriction on the value of this field. + # + # \returns The response to the command, formatted as a #Tuple_dn_promoteToOperational named tuple. + # + def dn_promoteToOperational(self, macAddr) : + res = HartMgrConnectorInternal.send(self, ['promoteToOperational'], {"macAddr" : macAddr}) + return HartMgrConnector.Tuple_dn_promoteToOperational(**res) + + ## + # The named tuple returned by the dn_pingMote() function. # # - callbackId: 4-byte field formatted as a int.
# There is no restriction on the value of this field. # - Tuple_dn_ping = collections.namedtuple("Tuple_dn_ping", ['callbackId']) + Tuple_dn_pingMote = collections.namedtuple("Tuple_dn_pingMote", ['callbackId']) ## # Ping the specified mote. A Net Ping Reply event notification will contain the mote's response. @@ -1342,11 +1448,11 @@ def dn_decommission(self, macAddr) : # \param macAddr 25-byte field formatted as a string.
# There is no restriction on the value of this field. # - # \returns The response to the command, formatted as a #Tuple_dn_ping named tuple. + # \returns The response to the command, formatted as a #Tuple_dn_pingMote named tuple. # - def dn_ping(self, macAddr) : - res = HartMgrConnectorInternal.send(self, ['ping'], {"macAddr" : macAddr}) - return HartMgrConnector.Tuple_dn_ping(**res) + def dn_pingMote(self, macAddr) : + res = HartMgrConnectorInternal.send(self, ['pingMote'], {"macAddr" : macAddr}) + return HartMgrConnector.Tuple_dn_pingMote(**res) ## # The named tuple returned by the dn_getLicense() function. @@ -1496,6 +1602,26 @@ def dn_unsubscribe(self, ) : res = HartMgrConnectorInternal.send(self, ['unsubscribe'], {}) return HartMgrConnector.Tuple_dn_unsubscribe(**res) + ## + # The named tuple returned by the dn_cli() function. + # + # - result: 32-byte field formatted as a string.
+ # There is no restriction on the value of this field. + # + Tuple_dn_cli = collections.namedtuple("Tuple_dn_cli", ['result']) + + ## + # This command tunnels a given command through to the manager's Command Line Interface (CLI). The CLI command can be called by only one XML API client at a time. The response to the given CLI command is tunneled back to the client via the notifications channel. To receive the CLI notification, the client must be subscribed to CLI notifications (see Notification Channel) + # + # \param command 128-byte field formatted as a string.
+ # There is no restriction on the value of this field. + # + # \returns The response to the command, formatted as a #Tuple_dn_cli named tuple. + # + def dn_cli(self, command) : + res = HartMgrConnectorInternal.send(self, ['cli'], {"command" : command}) + return HartMgrConnector.Tuple_dn_cli(**res) + #======================== notifications =================================== ## @@ -1787,6 +1913,48 @@ def dn_unsubscribe(self, ) : MOTELIVE = "MoteLive" notifTupleTable[MOTELIVE] = Tuple_MoteLive = collections.namedtuple("Tuple_MoteLive", ['timeStamp', 'eventId', 'moteId', 'macAddr', 'reason']) + ## + # \brief MOTEQUARANTINE notification. + # + # + # + # Formatted as a Tuple_MoteQuarantine named tuple. It contains the following fields: + # - timeStamp 8-byte field formatted as a int.
+ # There is no restriction on the value of this field. + # - eventId 4-byte field formatted as a int.
+ # There is no restriction on the value of this field. + # - moteId 4-byte field formatted as a int.
+ # There is no restriction on the value of this field. + # - macAddr 32-byte field formatted as a string.
+ # There is no restriction on the value of this field. + # - reason 64-byte field formatted as a string.
+ # There is no restriction on the value of this field. + # + MOTEQUARANTINE = "MoteQuarantine" + notifTupleTable[MOTEQUARANTINE] = Tuple_MoteQuarantine = collections.namedtuple("Tuple_MoteQuarantine", ['timeStamp', 'eventId', 'moteId', 'macAddr', 'reason']) + + ## + # \brief MOTEJOINQUARANTINE notification. + # + # + # + # Formatted as a Tuple_MoteJoinQuarantine named tuple. It contains the following fields: + # - timeStamp 8-byte field formatted as a int.
+ # There is no restriction on the value of this field. + # - eventId 4-byte field formatted as a int.
+ # There is no restriction on the value of this field. + # - moteId 4-byte field formatted as a int.
+ # There is no restriction on the value of this field. + # - macAddr 32-byte field formatted as a string.
+ # There is no restriction on the value of this field. + # - reason 64-byte field formatted as a string.
+ # There is no restriction on the value of this field. + # - userData 64-byte field formatted as a string.
+ # There is no restriction on the value of this field. + # + MOTEJOINQUARANTINE = "MoteJoinQuarantine" + notifTupleTable[MOTEJOINQUARANTINE] = Tuple_MoteJoinQuarantine = collections.namedtuple("Tuple_MoteJoinQuarantine", ['timeStamp', 'eventId', 'moteId', 'macAddr', 'reason', 'userData']) + ## # \brief MOTEUNKNOWN notification. # @@ -2052,6 +2220,28 @@ def dn_unsubscribe(self, ) : PINGREPLY = "PingReply" notifTupleTable[PINGREPLY] = Tuple_PingReply = collections.namedtuple("Tuple_PingReply", ['timeStamp', 'eventId', 'macAddr', 'callbackId', 'latency', 'temperature', 'voltage', 'hopCount']) + ## + # \brief TRANSPORTTIMEOUT notification. + # + # + # + # Formatted as a Tuple_TransportTimeout named tuple. It contains the following fields: + # - timeStamp 8-byte field formatted as a int.
+ # There is no restriction on the value of this field. + # - eventId 4-byte field formatted as a int.
+ # There is no restriction on the value of this field. + # - srcMacAddr 32-byte field formatted as a string.
+ # There is no restriction on the value of this field. + # - destMacAddr 32-byte field formatted as a string.
+ # There is no restriction on the value of this field. + # - timeoutType 32-byte field formatted as a string.
+ # There is no restriction on the value of this field. + # - callbackId 4-byte field formatted as a int.
+ # There is no restriction on the value of this field. + # + TRANSPORTTIMEOUT = "TransportTimeout" + notifTupleTable[TRANSPORTTIMEOUT] = Tuple_TransportTimeout = collections.namedtuple("Tuple_TransportTimeout", ['timeStamp', 'eventId', 'srcMacAddr', 'destMacAddr', 'timeoutType', 'callbackId']) + ## # \brief DATA notification. # @@ -2182,3 +2372,8 @@ def getNotification(self, timeoutSec=-1) : return (ids[-1], None) except KeyError : raise ApiException.NotificationError(ids, param) + +## +# end of HartMgrConnector +# \} +# diff --git a/SmartMeshSDK/HartMgrConnector/HartMgrConnectorInternal.py b/SmartMeshSDK/HartMgrConnector/HartMgrConnectorInternal.py index a7b2ca1..ca16d63 100644 --- a/SmartMeshSDK/HartMgrConnector/HartMgrConnectorInternal.py +++ b/SmartMeshSDK/HartMgrConnector/HartMgrConnectorInternal.py @@ -1,15 +1,11 @@ -'''Connector for HART Manager XML API - -''' - -import os -import sys import copy import xmlrpclib -import ApiException -from ApiConnector import ApiConnector -from ApiDefinition import HartMgrDefinition -from NotifReader import NotifReader + +from NotifReader import NotifReader + +from SmartMeshSDK import ApiException +from SmartMeshSDK.ApiConnector import ApiConnector +from SmartMeshSDK.ApiDefinition import HartMgrDefinition # Add a log handler for the HART Manager diff --git a/SmartMeshSDK/HartMgrConnector/NotifReader.py b/SmartMeshSDK/HartMgrConnector/NotifReader.py index 15ddf9a..5a9d916 100644 --- a/SmartMeshSDK/HartMgrConnector/NotifReader.py +++ b/SmartMeshSDK/HartMgrConnector/NotifReader.py @@ -5,7 +5,7 @@ import threading import traceback -import ApiException +from SmartMeshSDK import ApiException # Set up logging diff --git a/SmartMeshSDK/HartMoteConnector/HartMoteConnector.py b/SmartMeshSDK/HartMoteConnector/HartMoteConnector.py index 361075b..c78098f 100644 --- a/SmartMeshSDK/HartMoteConnector/HartMoteConnector.py +++ b/SmartMeshSDK/HartMoteConnector/HartMoteConnector.py @@ -3,12 +3,16 @@ ''' import collections -import ApiException +from SmartMeshSDK import ApiException from HartMoteConnectorInternal import HartMoteConnectorInternal +## +# \addtogroup HartMoteConnector +# \{ +# + class HartMoteConnector(HartMoteConnectorInternal): ''' - \ingroup ApiConnector \brief Public class for the HART Mote connector, over Serial. ''' @@ -33,6 +37,7 @@ class HartMoteConnector(HartMoteConnectorInternal): # - 13: RC_INCOMPLETE_JOIN_INFO # - 14: RC_NOT_FOUND # - 15: RC_INVALID_VALUE + # - 19: RC_ERASE_FAIL # Tuple_dn_setParameter_txPower = collections.namedtuple("Tuple_dn_setParameter_txPower", ['RC']) @@ -67,6 +72,7 @@ def dn_setParameter_txPower(self, txPower) : # - 13: RC_INCOMPLETE_JOIN_INFO # - 14: RC_NOT_FOUND # - 15: RC_INVALID_VALUE + # - 19: RC_ERASE_FAIL # Tuple_dn_setParameter_joinDutyCycle = collections.namedtuple("Tuple_dn_setParameter_joinDutyCycle", ['RC']) @@ -103,6 +109,7 @@ def dn_setParameter_joinDutyCycle(self, dutyCycle) : # - 13: RC_INCOMPLETE_JOIN_INFO # - 14: RC_NOT_FOUND # - 15: RC_INVALID_VALUE + # - 19: RC_ERASE_FAIL # Tuple_dn_setParameter_batteryLife = collections.namedtuple("Tuple_dn_setParameter_batteryLife", ['RC']) @@ -111,8 +118,6 @@ def dn_setParameter_joinDutyCycle(self, dutyCycle) : # # Command 778 is deprecated in version 7.4 of the HART specification as most existing gateways do not use battery life information. # - # - # # \param batteryLife 2-byte field formatted as a int.
# There is no restriction on the value of this field. # \param powerStatus 1-byte field formatted as a int.
@@ -148,14 +153,13 @@ def dn_setParameter_batteryLife(self, batteryLife, powerStatus) : # - 13: RC_INCOMPLETE_JOIN_INFO # - 14: RC_NOT_FOUND # - 15: RC_INVALID_VALUE + # - 19: RC_ERASE_FAIL # - numServices: 1-byte field formatted as a int.
# There is no restriction on the value of this field. # Tuple_dn_setParameter_service = collections.namedtuple("Tuple_dn_setParameter_service", ['RC', 'numServices']) ## - # - # # The setParameter command is used to request new device-originated bandwidth services and modify existing device-initiated services (now called "Timetables" in WirelessHART 7.4).Calling thiscommand updates the motes internal service table, which later initiates a request to the network manager for bandwidth allocation. A subsequent serviceIndication notification will be sent indicating the response from the network manager. The getParameter command may be used to read the service table, including the state of the service request. # # The setParameter command may be sent at any time. If the network manager rejects a service request, the microprocessor can try again by re-issuing the setParameter command. @@ -202,6 +206,7 @@ def dn_setParameter_service(self, serviceId, serviceReqFlags, appDomain, destAdd # - 13: RC_INCOMPLETE_JOIN_INFO # - 14: RC_NOT_FOUND # - 15: RC_INVALID_VALUE + # - 19: RC_ERASE_FAIL # Tuple_dn_setParameter_hartDeviceStatus = collections.namedtuple("Tuple_dn_setParameter_hartDeviceStatus", ['RC']) @@ -236,6 +241,7 @@ def dn_setParameter_hartDeviceStatus(self, hartDevStatus) : # - 13: RC_INCOMPLETE_JOIN_INFO # - 14: RC_NOT_FOUND # - 15: RC_INVALID_VALUE + # - 19: RC_ERASE_FAIL # Tuple_dn_setParameter_hartDeviceInfo = collections.namedtuple("Tuple_dn_setParameter_hartDeviceInfo", ['RC']) @@ -272,12 +278,15 @@ def dn_setParameter_hartDeviceInfo(self, hartCmd0, hartCmd20) : # - 13: RC_INCOMPLETE_JOIN_INFO # - 14: RC_NOT_FOUND # - 15: RC_INVALID_VALUE + # - 19: RC_ERASE_FAIL # Tuple_dn_setParameter_eventMask = collections.namedtuple("Tuple_dn_setParameter_eventMask", ['RC']) ## # The setParameter command allows the microprocessor to subscribe to the types of events that may be sent in the motes events notification message. This command may be called at any time and takes effect at the next event notification. The mote includes an event in the notification message if the corresponding bit in is set to 1, and excludes the event if the bit is set to 0. At mote reset, the default value of is 1 for all events. # + # New event type may be added in future revisions of mote software. It is recommended that the client code only subscribe to known events and gracefully ignore all unknown events. + # # \param eventMask 4-byte field formatted as a int.
# There is no restriction on the value of this field. # @@ -306,11 +315,12 @@ def dn_setParameter_eventMask(self, eventMask) : # - 13: RC_INCOMPLETE_JOIN_INFO # - 14: RC_NOT_FOUND # - 15: RC_INVALID_VALUE + # - 19: RC_ERASE_FAIL # Tuple_dn_setParameter_writeProtect = collections.namedtuple("Tuple_dn_setParameter_writeProtect", ['RC']) ## - # The setParameter command allows the microprocessor to enable or disable access to selected WirelessHART commands via wireless or the hartPayload command. Refer to the WirelessHART Mote User Guide for the list of affected commands. If writeProtect is enabled and the mote receives any of these commands (either via wireless connection or via the hartPayload command), the command will have no effect, and the mote will return RC_7 (In Write Protect Mode). At mote boot, writeProtect is set to 0 (writes allowed). The current status of writeProtect may be read via the getParameter command. This command is for WirelessHART-compliant devices only. + # The setParameter command allows the microprocessor to enable or disable access to selected WirelessHART commands via wireless or the hartPayload command. Refer to theSmartMesh WirelessHART User's Guide for the list of affected commands. If writeProtect is enabled and the mote receives any of these commands (either via wireless connection or via the hartPayload command), the command will have no effect, and the mote will return RC_7 (In Write Protect Mode). At mote boot, writeProtect is set to 0 (writes allowed). The current status of writeProtect may be read via the getParameter command. This command is for WirelessHART-compliant devices only. # # \param writeProtect 1-byte field formatted as a int.
# This field can only take one of the following values: @@ -323,6 +333,42 @@ def dn_setParameter_writeProtect(self, writeProtect) : res = HartMoteConnectorInternal.send(self, ['setParameter', 'writeProtect'], {"writeProtect" : writeProtect}) return HartMoteConnector.Tuple_dn_setParameter_writeProtect(**res) + ## + # The named tuple returned by the dn_getParameter_joinDutyCycle() function. + # + # - RC: 1-byte field formatted as a int.
+ # This field can only take one of the following values: + # - 0: RC_OK + # - 3: RC_BUSY + # - 4: RC_INVALID_LEN + # - 5: RC_INVALID_STATE + # - 6: RC_UNSUPPORTED + # - 7: RC_UNKNOWN_PARAM + # - 8: RC_UNKNOWN_CMD + # - 9: RC_WRITE_FAIL + # - 10: RC_READ_FAIL + # - 11: RC_LOW_VOLTAGE + # - 12: RC_NO_RESOURCES + # - 13: RC_INCOMPLETE_JOIN_INFO + # - 14: RC_NOT_FOUND + # - 15: RC_INVALID_VALUE + # - 19: RC_ERASE_FAIL + # - joinDutyCycle: 1-byte field formatted as a int.
+ # There is no restriction on the value of this field. + # + Tuple_dn_getParameter_joinDutyCycle = collections.namedtuple("Tuple_dn_getParameter_joinDutyCycle", ['RC', 'joinDutyCycle']) + + ## + # The getParameter command return mote's join duty cycle, which determines the percentage of time the mote spends in radio receive mode while searching for network. The value of join duty cycle is expressed in increments of 1/255th of 100%, where 0 corresponds to 0% and 255 corresponds to 100%. + # + # + # + # \returns The response to the command, formatted as a #Tuple_dn_getParameter_joinDutyCycle named tuple. + # + def dn_getParameter_joinDutyCycle(self, ) : + res = HartMoteConnectorInternal.send(self, ['getParameter', 'joinDutyCycle'], {}) + return HartMoteConnector.Tuple_dn_getParameter_joinDutyCycle(**res) + ## # The named tuple returned by the dn_getParameter_service() function. # @@ -342,6 +388,7 @@ def dn_setParameter_writeProtect(self, writeProtect) : # - 13: RC_INCOMPLETE_JOIN_INFO # - 14: RC_NOT_FOUND # - 15: RC_INVALID_VALUE + # - 19: RC_ERASE_FAIL # - serviceId: 1-byte field formatted as a int.
# There is no restriction on the value of this field. # - serviceState: 1-byte field formatted as a int.
@@ -365,8 +412,6 @@ def dn_setParameter_writeProtect(self, writeProtect) : Tuple_dn_getParameter_service = collections.namedtuple("Tuple_dn_getParameter_service", ['RC', 'serviceId', 'serviceState', 'serviceFlags', 'appDomain', 'destAddr', 'time']) ## - # - # # The getParameter command retrieves information about the service allocation that is currently available to the field device. Services (now called "Timetables" in WirelessHART 7.4) in the range 0x00-7F are those requested by the device, and those in the range 0x80-FF are assigned independently by the network manager. # # \param serviceId 1-byte field formatted as a int.
@@ -397,6 +442,7 @@ def dn_getParameter_service(self, serviceId) : # - 13: RC_INCOMPLETE_JOIN_INFO # - 14: RC_NOT_FOUND # - 15: RC_INVALID_VALUE + # - 19: RC_ERASE_FAIL # - apiVersion: 1-byte field formatted as a int.
# There is no restriction on the value of this field. # - serialNum: 8-byte field formatted as a hex.
@@ -446,6 +492,7 @@ def dn_getParameter_moteInfo(self, ) : # - 13: RC_INCOMPLETE_JOIN_INFO # - 14: RC_NOT_FOUND # - 15: RC_INVALID_VALUE + # - 19: RC_ERASE_FAIL # - macAddress: 8-byte field formatted as a hex.
# There is no restriction on the value of this field. # - moteId: 2-byte field formatted as a int.
@@ -485,6 +532,7 @@ def dn_getParameter_networkInfo(self, ) : # - 13: RC_INCOMPLETE_JOIN_INFO # - 14: RC_NOT_FOUND # - 15: RC_INVALID_VALUE + # - 19: RC_ERASE_FAIL # - state: 1-byte field formatted as a int.
# This field can only take one of the following values: # - 0: Init @@ -496,6 +544,7 @@ def dn_getParameter_networkInfo(self, ) : # - 6: Disconnected # - 7: Radio test # - 8: Promiscuous Listen + # - 9: Suspended # - moteStateReason: 1-byte field formatted as a int.
# There is no restriction on the value of this field. # - changeCounter: 2-byte field formatted as a int.
@@ -539,16 +588,15 @@ def dn_getParameter_moteStatus(self, ) : # - 13: RC_INCOMPLETE_JOIN_INFO # - 14: RC_NOT_FOUND # - 15: RC_INVALID_VALUE - # - utcTimeSec: 8-byte field formatted as a int.
- # There is no restriction on the value of this field. - # - utcTimeMsec: 8-byte field formatted as a int.
+ # - 19: RC_ERASE_FAIL + # - utcTime: 8-byte field formatted as a hex.
# There is no restriction on the value of this field. # - asn: 5-byte field formatted as a hex.
# There is no restriction on the value of this field. # - asnOffset: 2-byte field formatted as a int.
# There is no restriction on the value of this field. # - Tuple_dn_getParameter_time = collections.namedtuple("Tuple_dn_getParameter_time", ['RC', 'utcTimeSec', 'utcTimeMsec', 'asn', 'asnOffset']) + Tuple_dn_getParameter_time = collections.namedtuple("Tuple_dn_getParameter_time", ['RC', 'utcTime', 'asn', 'asnOffset']) ## # The getParameter