From 35c4847f88d55ce1f32c7ef88ea822e436e86e93 Mon Sep 17 00:00:00 2001 From: "Thomas Watteyne [linear]" Date: Thu, 4 Aug 2016 12:03:37 -0700 Subject: [PATCH] SmartMesh SDK 1.0.8.142 --- PKG-INFO | 2 +- app/OapClient/OapClient.py | 136 ++++++++++++++---- app/PublishToWeb/PublishToWeb.py | 134 +++++++++++++++++ app/PublishToWeb/PublishToWebRandom.py | 42 ++++++ app/PublishToWeb/README.md | 1 + app/PublishToWeb/clouddata_server.py | 43 ++++++ .../ApiDefinition/HartMgrDefinition.py | 33 ++++- libs/SmartMeshSDK/ApiDefinition/xmlutils.py | 11 +- .../HartMgrConnector/HartMgrConnector.py | 13 +- libs/SmartMeshSDK/protocols/oap/OAPNotif.py | 34 ++++- .../protocols/otap/OTAPCommunicator.py | 1 + libs/SmartMeshSDK/sdk_version.py | 2 +- requirements.txt | 1 + setup.py | 5 + 14 files changed, 418 insertions(+), 40 deletions(-) create mode 100644 app/PublishToWeb/PublishToWeb.py create mode 100644 app/PublishToWeb/PublishToWebRandom.py create mode 100644 app/PublishToWeb/README.md create mode 100644 app/PublishToWeb/clouddata_server.py diff --git a/PKG-INFO b/PKG-INFO index 1eb3b9c..72eddbc 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: SmartMeshSDK -Version: 1.0.6.139 +Version: 1.0.8.142 Summary: UNKNOWN Home-page: UNKNOWN Author: Linear Technology diff --git a/app/OapClient/OapClient.py b/app/OapClient/OapClient.py index b0d5700..a3cf183 100644 --- a/app/OapClient/OapClient.py +++ b/app/OapClient/OapClient.py @@ -30,15 +30,15 @@ from SmartMeshSDK import sdk_version #============================ defines ========================================= -BCAST_CNT = 2 -LOGFILE = "OapClient_log.txt" + +NUM_BCAST_TO_SEND = 2 +LOGFILE = "OapClient_log.txt" #============================ globals ========================================= #============================ helpers ========================================= def printExcAndQuit(err): - output = [] output += ["="*30] output += ["error"] @@ -80,19 +80,25 @@ def getOperationalMotes(): AppData().get('oap_dispatch'), ) + return len(operationalmotes) + def printOperationalMotes(): + output = [] - output += ["\nOperational motes:"] + output += ["{0} operational motes:".format(len(AppData().get('operationalmotes')))] for (i,m) in enumerate(AppData().get('operationalmotes')): output += ['{0}: {1}'.format(i,FormatUtils.formatMacString(m))] output = '\n'.join(output) print output -def selectOperationalMotes(moteNum): +def selectOperationalMote(moteNum): if moteNum>len(AppData().get('operationalmotes')): - print 'Cannot select mote {0}, out of range'.format(moteNum) + print 'Cannot select mote {0}, there are only {1} motes'.format( + moteNum, + len(AppData().get('operationalmotes')), + ) return AppData().set('currentmote',moteNum) @@ -103,6 +109,7 @@ def selectOperationalMotes(moteNum): ) def togglePrintNotifs(): + if AppData().get('printNotifs')==False: AppData().set('printNotifs',True) print "notifications are ON." @@ -111,6 +118,7 @@ def togglePrintNotifs(): print "notifications are OFF." def toggleLogNotifs(): + if AppData().get('logNotifs')==False: AppData().set('logNotifs',True) print "logging to logfile is ON." @@ -143,6 +151,9 @@ def set(self,k,v): def get(self,k): with self.dataLock: return self.data[k] + def delete(self,k): + with self.dataLock: + del self.data[k] class Manager(object): @@ -165,9 +176,10 @@ def __init__(self): # list operational motes AppData().set('oap_clients',{}) - getOperationalMotes() - printOperationalMotes() - selectOperationalMotes(0) + numMotes = getOperationalMotes() + if numMotes: + printOperationalMotes() + selectOperationalMote(0) AppData().set('printNotifs',False) togglePrintNotifs() @@ -188,6 +200,7 @@ def disconnect(self): #======================== private ========================================= def _cb_NOTIFDATA(self,notifName,notifParams): + AppData().get('oap_dispatch').dispatch_pkt(notifName, notifParams) if AppData().get('logNotifs'): if notifParams.data[0] == 0: @@ -196,30 +209,33 @@ def _cb_NOTIFDATA(self,notifName,notifParams): def _handle_oap_notif(self,mac,notif): receive_time = float(time.time()) - self.mapmgrtime.pctomgr_time_offset - output = " Received-Time = {0} OAP notification from {1}: {2}".format( - receive_time, + output = "OAP notification from {0} (receive time {1}):\n{2}".format( FormatUtils.formatMacString(mac), + receive_time, notif ) - + if AppData().get('printNotifs'): print output if AppData().get('logNotifs'): self.log_file.write('{0}\n'.format(output)) class MgrTime(threading.Thread): - # This class sends getTime API command to map network time to UTC time. The offset is then - # used to calculate the pkt arrival time for the same time base as the mote + ''' + This class periodically sends a getTime() API command to the manager to map + network time to UTC time. The offset is used to calculate the pkt arrival + time for the same time base as the mote. + ''' def __init__(self, pctomgr_time_offset, sleepperiod): # init the parent threading.Thread.__init__(self) self.event = threading.Event() - self.sleepperiod = sleepperiod + self.sleepperiod = sleepperiod self.daemon = True self.pctomgr_time_offset = pctomgr_time_offset # give this thread a name - self.name = 'MgrTimePin' + self.name = 'MgrTime' def run(self): while True: @@ -229,7 +245,7 @@ def run(self): mgr_time = mgr_timepinres.utcSecs + mgr_timepinres.utcUsecs / 1000000.0 mgr_asn = int(''.join(["%02x"%i for i in mgr_timepinres.asn]),16) self.pctomgr_time_offset = pc_time - mgr_time - + self.event.wait(self.sleepperiod) #============================ CLI handlers ==================================== @@ -239,6 +255,14 @@ def connect_clicb(params): # filter params port = params[0] + try: + AppData().get('connector') + except KeyError: + pass + else: + print 'already connected.' + return + # create a connector AppData().set('connector',IpMgrConnectorSerial.IpMgrConnectorSerial()) @@ -248,7 +272,12 @@ def connect_clicb(params): 'port': port, }) except ConnectionError as err: - printExcAndQuit(err) + print 'Could not connect to {0}: {1}'.format( + port, + err, + ) + AppData().delete('connector') + return # start threads AppData().set('manager',Manager()) @@ -258,7 +287,7 @@ def list_clicb(params): printOperationalMotes() def select_clicb(params): - selectOperationalMotes(int(params[0])) + selectOperationalMote(int(params[0])) def notifs_clicb(params): togglePrintNotifs() @@ -266,6 +295,9 @@ def notifs_clicb(params): def writelogfile_clicb(params): toggleLogNotifs() +def _resppoipoi(mac, resp, trans): + print (mac, resp, trans) + def led_clicb(params): # filter params @@ -276,6 +308,13 @@ def led_clicb(params): isBcast = True ledState = params[1] + if moteId>len(AppData().get('operationalmotes')): + print 'moteId {0} impossible, there are only {1} motes'.format( + moteId, + len(AppData().get('operationalmotes')), + ) + return + if ledState=="0": ledVal = 0 else: @@ -287,6 +326,7 @@ def led_clicb(params): cmd_type = OAPMessage.CmdType.PUT, addr = [3,2], data_tags = [OAPMessage.TLVByte(t=0,v=ledVal)], + cb = _respPoipoi, ) else: # build OAP message @@ -300,8 +340,8 @@ def led_clicb(params): ) oap_msg = [ord(b) for b in oap_msg] - # send OAP message broadcast BCAST_CNT times - for i in range (BCAST_CNT): + # send OAP message broadcast NUM_BCAST_TO_SEND times + for i in range (NUM_BCAST_TO_SEND): AppData().get('connector').dn_sendData( macAddress = [0xff]*8, priority = 0, @@ -322,6 +362,13 @@ def temp_clicb(params): tempOn = int(params[1]) pktPeriod = int(params[2]) + if moteId>len(AppData().get('operationalmotes')): + print 'moteId {0} impossible, there are only {1} motes'.format( + moteId, + len(AppData().get('operationalmotes')), + ) + return + # send OAP command ... single or all broadcast if not isBcast: AppData().get('oap_clients')[AppData().get('operationalmotes')[moteId]].send( @@ -347,8 +394,8 @@ def temp_clicb(params): ) oap_msg = [ord(b) for b in oap_msg] - # send OAP message broadcast BCAST_CNT times - for i in range (BCAST_CNT): + # send OAP message broadcast NUM_BCAST_TO_SEND times + for i in range (NUM_BCAST_TO_SEND): AppData().get('connector').dn_sendData( macAddress = [0xff]*8, priority = 0, @@ -371,6 +418,13 @@ def pkgen_clicb(params): pktSize = int(params[3]) pktstartPID = 0 + if moteId>len(AppData().get('operationalmotes')): + print 'moteId {0} impossible, there are only {1} motes'.format( + moteId, + len(AppData().get('operationalmotes')), + ) + return + # send OAP command ... single mote, or all unicast, or all broadcast if isBcast == False: AppData().get('oap_clients')[AppData().get('operationalmotes')[moteId]].send( @@ -418,8 +472,8 @@ def pkgen_clicb(params): ) oap_msg = [ord(b) for b in oap_msg] - # send OAP message broadcast BCAST_CNT times - for i in range (BCAST_CNT): + # send OAP message broadcast NUM_BCAST_TO_SEND times + for i in range (NUM_BCAST_TO_SEND): AppData().get('connector').dn_sendData( macAddress = [0xff]*8, priority = 0, @@ -431,6 +485,30 @@ def pkgen_clicb(params): else: print (' unknown paramater ... {0}'.format(params[0])) +def analog_clicb(params): + + # filter params + moteId = int(params[0]) + channel = int(params[1]) + enable = int(params[2]) + rate = int(params[3]) + + if moteId>len(AppData().get('operationalmotes')): + print 'moteId {0} impossible, there are only {1} motes'.format( + moteId, + len(AppData().get('operationalmotes')), + ) + return + + AppData().get('oap_clients')[AppData().get('operationalmotes')[moteId]].send( + cmd_type = OAPMessage.CmdType.PUT, + addr = [4,channel], + data_tags = [ + OAPMessage.TLVByte(t=0,v=enable), # enable + OAPMessage.TLVLong(t=1,v=rate), # rate + ], + ) + def quit_clicb(): if AppData().get('connector'): @@ -511,6 +589,14 @@ def main(): callback = pkgen_clicb, dontCheckParamsLength = False, ) + cli.registerCommand( + name = 'analog', + alias = 'a', + description = 'set the analog application on the mote', + params = ['moteId','channel','enable','rate'], + callback = analog_clicb, + dontCheckParamsLength = False, + ) # print SmartMesh SDK version print 'SmartMesh SDK {0}'.format('.'.join([str(i) for i in sdk_version.VERSION])) diff --git a/app/PublishToWeb/PublishToWeb.py b/app/PublishToWeb/PublishToWeb.py new file mode 100644 index 0000000..b7d1680 --- /dev/null +++ b/app/PublishToWeb/PublishToWeb.py @@ -0,0 +1,134 @@ +#!/usr/bin/python + +#============================ adjust path ===================================== + +import sys +import os +if __name__ == "__main__": + here = sys.path[0] + sys.path.insert(0, os.path.join(here, '..', '..','libs')) + sys.path.insert(0, os.path.join(here, '..', '..','external_libs')) + +#============================ verify installation ============================= + +from SmartMeshSDK.utils import SmsdkInstallVerifier +(goodToGo,reason) = SmsdkInstallVerifier.verifyComponents( + [ + SmsdkInstallVerifier.PYTHON, + SmsdkInstallVerifier.PYSERIAL, + ] +) +if not goodToGo: + print "Your installation does not allow this application to run:\n" + print reason + raw_input("Press any button to exit") + sys.exit(1) + +#============================ imports ========================================= + +import requests +import json + +from SmartMeshSDK import sdk_version +from SmartMeshSDK.utils import AppUtils, \ + FormatUtils +from SmartMeshSDK.IpMgrConnectorSerial import IpMgrConnectorSerial +from SmartMeshSDK.IpMgrConnectorMux import IpMgrSubscribe +from SmartMeshSDK.protocols.oap import OAPDispatcher, \ + OAPNotif + +#============================ logging ========================================= + +# local +import logging +class NullHandler(logging.Handler): + def emit(self, record): + pass +log = logging.getLogger('App') +log.setLevel(logging.ERROR) +log.addHandler(NullHandler()) + +# global +AppUtils.configureLogging() + +#============================ defines ========================================= + +DEFAULT_SERIALPORT = 'COM7' +SERVER_HOST = 'clouddata.dustcloud.org' +SERVER_PORT = '80' + +#============================ helper functions ================================ + +# called when the manager generates a data notification +def handle_data(notifName, notifParams): + + # have the OAP dispatcher parse the packet. + # It will call handle_oap_data() is this data is a valid OAP data. + oapdispatcher.dispatch_pkt(notifName, notifParams) + +# called when the OAP dispatcher can successfully parse received data as OAP +def handle_oap_data(mac,notif): + + if isinstance(notif,OAPNotif.OAPTempSample): + + mac = FormatUtils.formatMacString(mac) + temperature = float(notif.samples[0])/100 + + try: + r = requests.post( + "http://{0}:{1}/api/v1/oap".format(SERVER_HOST,SERVER_PORT), + data = json.dumps({ + 'mac': mac, + 'temperature': temperature, + }), + headers = { + 'Content-type': 'application/json', + } + ) + except Exception as err: + print err + else: + print 'sent mac={0} temperature={1:.2f}C'.format(mac,temperature) + +#============================ main ============================================ + +# print banner +print 'PublishToWeb - (c) Dust Networks' +print 'SmartMesh SDK {0}'.format('.'.join([str(b) for b in sdk_version.VERSION])) + +# set up the OAP dispatcher (which parses OAP packets) +oapdispatcher = OAPDispatcher.OAPDispatcher() +oapdispatcher.register_notif_handler(handle_oap_data) + +# ask user for serial port number +serialport = raw_input('\nSmartMesh IP manager\'s API serial port (leave blank for '+DEFAULT_SERIALPORT+'): ') +if not serialport.strip(): + serialport = DEFAULT_SERIALPORT + +# connect to manager +connector = IpMgrConnectorSerial.IpMgrConnectorSerial() +try: + connector.connect({ + 'port': serialport, + }) +except Exception as err: + print 'failed to connect to manager at {0}, error ({1})\n{2}'.format( + serialport, + type(err), + err + ) + raw_input('Aborting. Press Enter to close.') + sys.exit(1) +else: + print 'Connected to {0}.\n'.format(serialport) + +# subscribe to data notifications +subscriber = IpMgrSubscribe.IpMgrSubscribe(connector) +subscriber.start() +subscriber.subscribe( + notifTypes = [ + IpMgrSubscribe.IpMgrSubscribe.NOTIFDATA, + ], + fun = handle_data, + isRlbl = False, +) diff --git a/app/PublishToWeb/PublishToWebRandom.py b/app/PublishToWeb/PublishToWebRandom.py new file mode 100644 index 0000000..5c400a8 --- /dev/null +++ b/app/PublishToWeb/PublishToWebRandom.py @@ -0,0 +1,42 @@ +#!/usr/bin/python + +#============================ imports ========================================= + +import time +import requests +import random +import json + +#============================ defines ========================================= + +SERVER_HOST = 'clouddata.dustcloud.org' +SERVER_PORT = '80' + +#============================ main ============================================ + +print 'PublishToWebRandom - (c) Dust Networks' + +while True: + time.sleep(1) + + try: + mac = random.choice([ + '33-33-33-33-33-33-33-33', + '44-44-44-44-44-44-44-44', + ]) + temperature = 20+10*random.random() + + r = requests.post( + "http://{0}:{1}/api/v1/oap".format(SERVER_HOST,SERVER_PORT), + data = json.dumps({ + 'mac': mac, + 'temperature': temperature, + }), + headers = { + 'Content-type': 'application/json', + } + ) + except Exception as err: + print err + else: + print 'sent mac={0} temperature={1:.2f}C'.format(mac,temperature) diff --git a/app/PublishToWeb/README.md b/app/PublishToWeb/README.md new file mode 100644 index 0000000..821c8bd --- /dev/null +++ b/app/PublishToWeb/README.md @@ -0,0 +1 @@ +Documentation at https://dustcloud.atlassian.net/wiki/display/SMSDK/PublishToWeb. \ No newline at end of file diff --git a/app/PublishToWeb/clouddata_server.py b/app/PublishToWeb/clouddata_server.py new file mode 100644 index 0000000..9023a8d --- /dev/null +++ b/app/PublishToWeb/clouddata_server.py @@ -0,0 +1,43 @@ +#!/usr/bin/python + +#============================ define ========================================== + +DFLT_PORT = 8080 +INFLUX_HOST = 'localhost' +INFLUX_PORT = 8086 +INFLUX_DBNAME = 'grafana' + +#============================ imports ========================================= + +from bottle import post,request,run +import influxdb + +print 'CloudData Server' + +influxClient = influxdb.client.InfluxDBClient( + host = INFLUX_HOST, + port = INFLUX_PORT, + database = INFLUX_DBNAME, +) + +@post('/oap') +def root_post(): + mac = request.json['mac'] + temperature = request.json['temperature'] + influxClient.write_points( + [ + { + "measurement": "temperature", + "tags": { + "mac": mac, + }, + "fields": { + "value": temperature, + } + }, + ] + ) + print 'received mac={0} temperature={1}'.format(mac,temperature) + +print 'Server started on port {0}'.format(DFLT_PORT) +run(host='0.0.0.0', port=DFLT_PORT, quiet=True) diff --git a/libs/SmartMeshSDK/ApiDefinition/HartMgrDefinition.py b/libs/SmartMeshSDK/ApiDefinition/HartMgrDefinition.py index 2baa00a..22efd46 100644 --- a/libs/SmartMeshSDK/ApiDefinition/HartMgrDefinition.py +++ b/libs/SmartMeshSDK/ApiDefinition/HartMgrDefinition.py @@ -854,6 +854,14 @@ def serialize_setConfig(self, commandArray, fields) : config_doc = xmlutils.dict_to_xml(param_dict, prefix) return [config_doc] + def serialize_setBlacklist(self, commandArray, fields): + cmd_metadata = self.getDefinition(self.COMMAND, commandArray) + prefix = [] + if 'serializerParam' in cmd_metadata : + prefix = cmd_metadata['serializerParam'] + params = fields['frequency'].split() + return [xmlutils.list_to_xml(params, 'frequency', prefix)] + def _build_stat_set(self, period, index = 0): STAT_PERIOD_QUERY_TMPL = '<{0}Set><{0}>{1}' if period in ['current']: @@ -953,6 +961,20 @@ def deserialize_getOpenAlarms(self, cmd_metadata, xmlrpc_resp): for alarm in alarms] return result + def deserialize_blacklist(self, cmd_metadata, xmlrpc_resp): + # the same deserializer is used for getConfig and setConfig operations + resp_fields = self.getResponseFields(self.COMMAND, [cmd_metadata['name']]) + resp_obj = cmd_metadata['response'].keys()[0] + # cmd_metadata['isResponseArray'] should be True + resp_dict = xmlutils.parse_xml_obj(xmlrpc_resp, resp_obj, resp_fields)[0] + # resp_dict['frequency'] contains either a single string or a list of string values + if type(resp_dict['frequency']) is list: + resp = [{'frequency': int(freq)} for freq in resp_dict['frequency']] + else: + resp = [{'frequency': int(resp_dict['frequency'])}] + # we return a list of objects containing frequency values + return resp + # Commands commands = [ # Get Config commands @@ -1210,7 +1232,7 @@ def deserialize_getOpenAlarms(self, cmd_metadata, xmlrpc_resp): { 'id' : 'getConfig', 'name' : 'getBlacklist', - 'description': 'Get the channel blacklist', + 'description': 'Get the channel blacklist. The output is a list of the blacklisted frequency values.', 'request' : [ ], 'response' : { @@ -1221,6 +1243,7 @@ def deserialize_getOpenAlarms(self, cmd_metadata, xmlrpc_resp): 'serializer' : 'serialize_getConfig', 'serializerParam': ['config', 'Network', 'ChannelBlackList'], 'isResponseArray': True, + 'deserializer': 'deserialize_blacklist', }, # getConfig.getMote { @@ -1794,17 +1817,19 @@ def deserialize_getOpenAlarms(self, cmd_metadata, xmlrpc_resp): { 'id' : 'setConfig', 'name' : 'setBlacklist', - 'description': 'Update the channel blacklist', + 'description': 'Update the channel blacklist. The input is a list of blacklisted frequency values separated by spaces.', 'request' : [ - ['frequency', INT, 4, None], + ['frequency', STRING, 64, None], ], 'response' : { 'ChannelBlackList': [ ['frequency', INT, 4, None], ], }, - 'serializer' : 'serialize_setConfig', + 'serializer' : 'serialize_setBlacklist', 'serializerParam' : ['config', 'Network', 'ChannelBlackList'], + 'isResponseArray': True, + 'deserializer': 'deserialize_blacklist', }, # setConfig.setNetwork { diff --git a/libs/SmartMeshSDK/ApiDefinition/xmlutils.py b/libs/SmartMeshSDK/ApiDefinition/xmlutils.py index 708db71..9de7bab 100644 --- a/libs/SmartMeshSDK/ApiDefinition/xmlutils.py +++ b/libs/SmartMeshSDK/ApiDefinition/xmlutils.py @@ -75,6 +75,13 @@ def dict_to_xml(inpDict, prefix = None) : if prefix : outList.append(''.join([''.format(p) for p in reversed(prefix)])) return ''.join([l for l in outList]) - - +def list_to_xml(inpList, el_tag, prefix = None): + outList = [] + if prefix : + outList.append(''.join(['<{0}>'.format(p) for p in prefix])) + for el in inpList: + outList.append('<{0}>{1}'.format(el_tag, el)) + if prefix : + outList.append(''.join([''.format(p) for p in reversed(prefix)])) + return ''.join(outList) diff --git a/libs/SmartMeshSDK/HartMgrConnector/HartMgrConnector.py b/libs/SmartMeshSDK/HartMgrConnector/HartMgrConnector.py index 1c36f78..8619072 100644 --- a/libs/SmartMeshSDK/HartMgrConnector/HartMgrConnector.py +++ b/libs/SmartMeshSDK/HartMgrConnector/HartMgrConnector.py @@ -356,7 +356,7 @@ def dn_getAcls(self, ) : Tuple_dn_getBlacklist = collections.namedtuple("Tuple_dn_getBlacklist", ['frequency']) ## - # Get the channel blacklist + # Get the channel blacklist. The output is a list of the blacklisted frequency values. # # # @@ -1339,16 +1339,19 @@ def dn_setAcl(self, macAddr, joinKey) : Tuple_dn_setBlacklist = collections.namedtuple("Tuple_dn_setBlacklist", ['frequency']) ## - # Update the channel blacklist + # Update the channel blacklist. The input is a list of blacklisted frequency values separated by spaces. # - # \param frequency 4-byte field formatted as a int.
+ # \param frequency 64-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_setBlacklist named tuple. + # \returns The response to the command, formatted as a list of #Tuple_dn_setBlacklist named tuple. # def dn_setBlacklist(self, frequency) : res = HartMgrConnectorInternal.send(self, ['setBlacklist'], {"frequency" : frequency}) - return HartMgrConnector.Tuple_dn_setBlacklist(**res) + tupleList = [] + for r in res : + tupleList.append(HartMgrConnector.Tuple_dn_setBlacklist(**r)) + return tupleList ## # The named tuple returned by the dn_setNetwork() function. diff --git a/libs/SmartMeshSDK/protocols/oap/OAPNotif.py b/libs/SmartMeshSDK/protocols/oap/OAPNotif.py index 951a6c0..7489f22 100644 --- a/libs/SmartMeshSDK/protocols/oap/OAPNotif.py +++ b/libs/SmartMeshSDK/protocols/oap/OAPNotif.py @@ -5,6 +5,7 @@ INFO_ADDRESS = array('B', [0]) DIGITAL_IN_ADDRESS = array('B', [0]) +ANALOG_ADDRESS = 4 TEMP_ADDRESS = array('B', [5]) PKGEN_ADDRESS = array('B', [254]) @@ -62,7 +63,7 @@ def parse_oap_notif(data, index = 0): #===== create and populate result structure - if channel == TEMP_ADDRESS: + if channel == TEMP_ADDRESS: result = OAPTempSample() result.packet_timestamp = (secs, usecs) result.rate = rate @@ -72,6 +73,16 @@ def parse_oap_notif(data, index = 0): temp = struct.unpack('!h', data[index:index+2])[0] index += 2 result.samples.append(temp) + elif len(channel)==2 and channel[0]==ANALOG_ADDRESS: + result = OAPAnalogSample() + result.packet_timestamp = (secs, usecs) + result.rate = rate + result.num_samples = num_samples + result.sample_size = sample_size + for i in range(num_samples): + temp = struct.unpack('!h', data[index:index+2])[0] + index += 2 + result.samples.append(temp) else: result = OAPSample() result.packet_timestamp = (secs, usecs) @@ -224,7 +235,26 @@ def __init__(self): self.samples = [] def __str__(self): - return 'TEMP=%d' % (self.samples[0]) + return 'TEMPERATURE {0:.2f} C'.format( + float(self.samples[0])/100, + ) + +class OAPAnalogSample(OAPSample): + ''' + \brief representation of an analog sample notification. + ''' + def __init__(self): + self.rate = 0 + self.input = 0 + self.num_samples = 0 + self.sample_size = 0 + self.samples = [] + + def __str__(self): + return 'ANALOG input={0} voltage={1:.3f} V'.format( + self.input, + float(self.samples[0])/1000, + ) class OAPAnalogStats(OAPNotif): ''' diff --git a/libs/SmartMeshSDK/protocols/otap/OTAPCommunicator.py b/libs/SmartMeshSDK/protocols/otap/OTAPCommunicator.py index af6520e..e75e174 100644 --- a/libs/SmartMeshSDK/protocols/otap/OTAPCommunicator.py +++ b/libs/SmartMeshSDK/protocols/otap/OTAPCommunicator.py @@ -29,6 +29,7 @@ def emit(self, record): OK = 0 END_OF_LIST = 11 DISCONNECTED = 5 +NACK = 14 # TODO: refactor -- MacAddress should be a class diff --git a/libs/SmartMeshSDK/sdk_version.py b/libs/SmartMeshSDK/sdk_version.py index 9161b73..c5778bb 100644 --- a/libs/SmartMeshSDK/sdk_version.py +++ b/libs/SmartMeshSDK/sdk_version.py @@ -3,6 +3,6 @@ # # PLEASE DO NOT CHANGE THE SYNTAX OF THE VERSION VALUE # -VERSION = (1, 0, 6, 139) +VERSION = (1, 0, 8, 142) # END OF FILE diff --git a/requirements.txt b/requirements.txt index 7d6626a..c2042b4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ xively-python==0.1.0-rc1 cherrypy bottle plotly +influxdb \ No newline at end of file diff --git a/setup.py b/setup.py index f35030c..2a00c58 100644 --- a/setup.py +++ b/setup.py @@ -52,6 +52,7 @@ {'script': os.path.join('app', 'NetworkHealth', 'NetworkHealth.py'),}, {'script': os.path.join('app', 'OapClient', 'OapClient.py'),}, {'script': os.path.join('app', 'OTAPCommunicator', 'OTAPCommunicator.py'),}, + {'script': os.path.join('app', 'PublishToWeb', 'PublishToWeb.py'),}, {'script': os.path.join('app', 'RangeTest', 'RangeTest.py'),}, {'script': os.path.join('app', 'RawSerial', 'RawSerial.py'),}, {'script': os.path.join('app', 'Simple', 'SimpleHartMote.py'),}, @@ -123,6 +124,8 @@ 'app/OapClient/OapClient.py', 'app/OTAPCommunicator/OTAPCommunicator.py', 'app/PkGen/PkGen.py', + 'app/PublishToWeb/PublishToWeb.py', + 'app/PublishToWeb/PublishToWebRandom.py', 'app/RangeTest/RangeTest.py', 'app/RawSerial/RawSerial.py', 'app/SensorDataReceiver/SensorDataReceiver.py', @@ -212,6 +215,8 @@ ('app/OapClient', ['app/OapClient/README.md']), ('app/OTAPCommunicator', ['app/OTAPCommunicator/README.md']), ('app/PkGen', ['app/PkGen/README.md']), + ('app/PublishToWeb', ['app/PublishToWeb/clouddata_server.py']), + ('app/PublishToWeb', ['app/PublishToWeb/README.md']), ('app/RangeTest', ['app/RangeTest/README.md']), ('app/RawSerial', ['app/RawSerial/README.md']), ('app/SensorDataReceiver', ['app/SensorDataReceiver/README.md']),