from __future__ import print_function import sys, time, os, socket import struct, binascii, re, csv from datetime import datetime, timedelta from twisted.protocols.basic import LineReceiver from twisted.internet import reactor from twisted.python import usage, log from twisted.internet.serialport import SerialPort from twisted.web.server import Site from twisted.web.static import File try: # version > 0.8.0 from autobahn.wamp1.protocol import exportRpc except: from autobahn.wamp import exportRpc iddict = {'f': '10', 'x': '11', 'y': '12', 'z': '13', 'df': '14', 't': '30', 'rh': '33', 'p': '35', 'w': '38'} """ 0: clientname -- str (atlas) 1: timestamp (PC) -- str (2013-01-23 12:10:32.712475) 2: date (PC) -- str (2013-01-23) 3: outtime (PC) -- str (12:10:32.712475) 4: timestamp (sensor) -- str (2013-01-23 12:10:32.712475) 5: GPS coordinates -- str (??.??N ??.??E) 9: Sensor Description -- str (to be found in the adict) 10: f -- float (48633.04) [nT] 11: x -- float (20401.3) [nT] 12: y -- float (-30.0) [nT] 13: z -- float (43229.7) [nT] 14: df -- float (0.06) [nT] 30: T (ambient) -- float (7.2) [C] 31: T (sensor) -- float (10.0) [C] 32: T (electronics) -- float (12.5) [C] 33: rh (relative humidity) -- float (99.0) [%] 34: T (dewpoint) -- float (6.0) [C] 38: W (weight) -- float (24.0042) [g] 40: Error code (POS1) -- float (80) [-] 60: VDD (support voltage) -- float (5.02) [V] 61: VAD (measured voltage) -- float (2.03) [V] 62: VIS (measured voltage) -- float (0.00043) [V] """ def timeToArray(timestring): # Converts time string of format 2013-12-12 23:12:23.122324 # to an array similiar to a datetime object try: splittedfull = timestring.split(' ') splittedday = splittedfull[0].split('-') splittedsec = splittedfull[1].split('.') splittedtime = splittedsec[0].split(':') datearray = splittedday + splittedtime datearray.append(splittedsec[1]) datearray = map(int,datearray) return datearray except: log.msg('Error while extracting time array') return [] def dataToFile(outputdir, sensorid, filedate, bindata, header): # File Operations try: hostname = socket.gethostname() path = os.path.join(outputdir,hostname,sensorid) # outputdir defined in main options class if not os.path.exists(path): os.makedirs(path) savefile = os.path.join(path, sensorid+'_'+filedate+".bin") if not os.path.isfile(savefile): with open(savefile, "wb") as myfile: myfile.write(header + "\n") myfile.write(bindata + "\n") else: with open(savefile, "a") as myfile: myfile.write(bindata + "\n") except: log.err("PalmAcq - Protocol: Error while saving file") ## PalmAcq protocol ## -------------------- class PalmAcqProtocol(LineReceiver): """ Protocol to read Arduino data (usually from ttyACM0) Tested so far only for Arduino Uno on a Linux machine The protocol works only if the serial output follows the MagPy convention: Up to 99 Sensors are supported identified by unique sensor names and ID's. ARDUINO OUTPUT: - serial output on ttyACM0 needs to follow the MagPy definition: Three data sequences are supported: 1.) The meta information The meta information line contains all information for a specific sensor. If more than one sensor is connected, then several meta information lines should be sent (e.g. M1:..., M2:..., M99:...) Meta lines should be resent once in a while (e.g. every 10-100 data points) Example: M1: SensorName: MySensor, SensorID: 12345, SensorRevision: 0001 2.) The header line The header line contains information on the provided data for each sensor. The typical format includes the MagPy key, the actual Variable and the unit. Key and Variable are separeted by an underscore, unit is provided in brackets. Like the Meta information the header should be sent out once in a while Example: H1: f_F [nT], t1_Temp [deg C], var1_Quality [None], var2_Pressure [mbar] 3.) The data line: The data line containes all data from a specific sensor Example: D1: 46543.7898, 6.9, 10, 978.000 - recording starts after meta and header information have been received MARTAS requirements: - add the following line to the sensor.txt ARDUINO ACM0 9600 - on the MARTAS machine an additional information file will be created containing the sensor information for connected ARDUINO boards: arduinolist.csv: "HMC5883_12345_0001","['x', 'y', 'z']" This file is used by the MARCOS machine to identify connected sensors and their keys """ delimiter = "\r" ## need a reference to our WS-MCU gateway factory to dispatch PubSub events ## def __init__(self, wsMcuFactory, sensor, outputdir): self.wsMcuFactory = wsMcuFactory self.sensorid = sensor self.hostname = socket.gethostname() self.outputdir = outputdir self.sensor = '' self.sensordict = {} self.ConversionConstant = 40/4/float(int("0x800000",16)) eventstring = "evt0,evt1,evt3,evt11,evt12,evt13,evt32,evt60,evt99" self.eventlist = eventstring.split(',') def connectionMade(self): log.msg('%s connected.' % self.sensorid) def extractPalmAcqData(self, line): """ Method to convert hexadecimals to doubles Returns a data array """ # INTERPRETING INCOMING DATA AND CONVERTING HEXDECIMALS TO DOUBLE if line.startswith('*'): try: data = [] chunks = [] line = line.strip('*') chunks.append(line[:6]) chunks.append(line[6:12]) chunks.append(line[12:18]) trigger = line[18] ar = line.split(':') if len(ar) == 2: extended = ar[1] chunks.append(extended[:4]) chunks.append(extended[4:8]) chunks.append(extended[8:12]) chunks.append(extended[12:16]) chunks.append(extended[16:20]) for idx, chunk in enumerate(chunks): if len(chunk) == 6: val = hex(int('0x'+chunk,16) ^ int('0x800000',16)) val = hex(int(val,16) - int('0x800000',16)) # Conversion constanst should be obtained from palmacq-init val = float(int(val,16)) * self.ConversionConstant elif len(chunk) == 4: val = hex(int('0x'+chunk,16) ^ int('0x8000',16)) val = hex(int(val,16) - int('0x8000',16)) if idx == 3: val = float(int(val,16)) * 0.000575 + 1.0 elif idx == 4: val = float(int(val,16)) / 128.0 elif idx > 4: val = float(int(val,16)) / 8000.0 data.append(val) # SOME TEST OUTPUT #if len(data)> 4: # print datetime.utcnow(), data #print data, trigger return data, trigger except: #print "PALMACQ: an error occurred while interpreting the hexadecimal code" return [], 'N' else: return [], 'N' def processPalmAcqData(self, data): """Convert raw ADC counts into SI units as per datasheets""" printdata = False currenttime = datetime.utcnow() outdate = datetime.strftime(currenttime, "%Y-%m-%d") filename = outdate outtime = datetime.strftime(currenttime, "%H:%M:%S") # IMPORTANT : GET TIMESTAMP FROM DATA !!!!!! timestamp = datetime.strftime(currenttime, "%Y-%m-%d %H:%M:%S.%f") datearray = timeToArray(timestamp) packcode = '6hL' # Would probably be good to preserve the hexadecimal format # Seems to be extremely effective regarding accuracy and storage x = data[0] y = data[1] z = data[2] v = 0.0 t = 0.0 p = 0.0 q = 0.0 r = 0.0 if len(data) > 4: v = data[3] t = data[4] p = data[5] q = data[6] r = data[7] datearray.append(x) datearray.append(y) datearray.append(z) datearray.append(int(float(v)*10000)) datearray.append(int(float(t)*10000)) datearray.append(p) datearray.append(q) datearray.append(r) packcode = packcode + 'fffllfff' multiplier = [1,1,1,10000,10000,1,1,1] try: data_bin = struct.pack(packcode,*datearray) except: log.msg('Error while packing binary data') pass header = "# MagPyBin %s %s %s %s %s %s %d" % (self.sensorid, "[x,y,z,v,t,p,q,r]", "[x,y,z,v,t,p,q,r]", "[V,V,V,V,C,V,V,V]", str(multiplier).replace(" ",""), packcode, struct.calcsize(packcode)) if printdata: #print header print(timestamp) # File Operations try: dataToFile(self.outputdir, self.sensorid, filename, data_bin, header) except: log.msg('Saving failed') pass evt0 = {'id': 0, 'value': self.hostname} evt1 = {'id': 1, 'value': timestamp} evt3 = {'id': 3, 'value': outtime} evt11 = {'id': 11, 'value': x} evt12 = {'id': 12, 'value': y} evt13 = {'id': 13, 'value': z} evt32 = {'id': 32, 'value': t} evt60 = {'id': 60, 'value': v} evt99 = {'id': 99, 'value': 'eol'} return evt0,evt1,evt3,evt11,evt12,evt13,evt32,evt60,evt99 def lineReceived(self, line): data=[] if line: data, trigger = self.extractPalmAcqData(line) if len(data) > 1: evt0,evt1,evt3,evt11,evt12,evt13,evt32,evt60,evt99 = self.processPalmAcqData(data) dispatch_url = "http://example.com/"+self.hostname+"/pal#"+self.sensorid+"-value" # eventlist defined in init for event in self.eventlist: self.wsMcuFactory.dispatch(dispatch_url, eval(event))