An Idiot’s Guide to RPMA Development – Part 3
In the third and final part of this blog series, I’ll focus on building a parser to grab our data from Intellect and send it on up to AT&T’s M2X platform.
So now that our Arduino is pulling some measurements and sending them to a properly configured RPMA DevKit board (rACM), it’s time to pull that data out of Intellect and send it on to M2X. This part got pretty tricky for me, as it was my first real foray into Python. Plus, M2X is really picky about the data you feed it.
Our Starter Pack includes a handful of REST examples that you can use to read device data, send data downstream to a device from the cloud, and so on. The readmeConsole.txt doc is surprisingly helpful here. Read it! Our engineers have crafted a few parsers that work with M2X, but the serial example is limited to a single value. Since we’re sending two values in one string, we’ll have to split it up with a custom parser before sending it up to M2X. There are three files that we’re going to modify in our M2M_REST_Examples folder: createDevices.py, parsers.py, and restm2x.py.
createDevices.py
devices = [ | |
{‘desc’:’Arduino-rACM’, // whatever you feel like | |
‘nodeId’:’0x3451f’, // add your Node ID here | |
‘parser’:’serial_m2x’, // let’s reference the parser we’ll create | |
‘m2x_device_id’: ‘z547acf8d450fc94863a5b7b498b6135’, // grab from M2X | |
‘m2x_primary_key’:’n0b9b4d39c1bb86e733f37b62c666128′, // same | |
‘alarm_email_enabled’:0, | |
‘alarm_email_list’:[]}, | |
] |
After creating an M2X account, you’re offered the chance to create a device. Make yours numerical! If it prompts you to create your first stream, name it temp_f and then name a second stream humidity. Then copy-paste Device ID and Primary API keys from M2X into createDevices.py. Save your changes and you’re done there. Take care throughout this whole process that your text editor doesn’t change apostrophes and quotation marks from straight-up-and-down marks to fancy curved open and close marks. TextEdit.app does this by default and it is absolutely maddening.
parsers.py
#—————————————————————————— | |
# Temp/Humidity Arduino | |
# APP_INTF7 Application UART A_UART_RX J207 pin 7 | |
# Sample of a serial-based Arduino-to-rACM setup. Add additional sensor inputs | |
# under ‘expectedSensors’. Since we’re sending all sensor data to rACM as a single | |
# serial string all sensorIds are ‘6’ and sensorType is ‘0xFFF’. sensorName should | |
# correspond with your M2X stream names. | |
#—————————————————————————— | |
def parser_serial_m2x(raw_hex): | |
expectedSensors = [{‘sensorId’: 6, ‘sensorType’:0xFFF, ‘sensorName’:’humidity’, | |
‘sensorDesc’:’Relative Humidity, Arduino’}, | |
{‘sensorId’: 6, ‘sensorType’:0xFFF, ‘sensorName’:’temp_f’, | |
‘sensorDesc’:’Temperature (deg F), Arduino’}] | |
expectedAlarmTypes = [] # no alarms from this device | |
msg = RacmUlMssg(raw_hex, expectedSensors, expectedAlarmTypes) | |
if msg.getStatus() == ‘OK’: | |
if msg.getMsgType() == ‘Alarm’: | |
return(msg.getMsgType(), msg.getAlarmData()) | |
else: | |
return(msg.getMsgType(), msg.getData()) | |
else: | |
# Debug | |
print ‘ERROR: ‘, msg.getError() | |
print ‘raw_hex = ‘, raw_hex | |
return(‘ERROR’,”) |
rest2m2x.py
# ————————————————————————————————— | |
# Temp/Humidity (ARDUINO Serial) | |
# This bit calls the new parser and also parses the serial payload into segments for M2X. It grabs the serial | |
# string from an arduino and parses it into chunks delineated by the ‘_’ character. Then it formats/sanitizes | |
# the separate strings into numeric characters for M2X ingest. | |
# Each segment can be called by using text[0] (first segment) text[1] (second segment) and so on. | |
# NOTE: You can set the M2X stream data type to numeric with this parser. | |
# ————————————————————————————————— | |
elif dev_info[‘parser’] == ‘serial_m2x’: # Referencing the appropriate parser over in /parsers.py | |
print ‘nBegin parsing ULSDU at %s with: serial_m2x, sdu_id %s nnodeId %s rx_timestamp %s payload %s’ | |
% (datetime.datetime.now(), sdu_id, hex(int(node_id_hex, 16)), rx_timestamp, payload) | |
(msgType, data) = parsers.parser_serial_m2x(payload) | |
# Note we do not send the test button alarm to M2x, so we only accept one message type | |
if msgType == ‘Serial’: # We’re still pulling a serial-type message from Intellect | |
allMeasAccepted = 1 # Default means M2X accepted all measurements | |
for i in range(len(data)): | |
timestamp = convertToDateTime(rx_timestamp).strftime(“%Y-%m-%dT%H:%M:%SZ”) # Timestamp format m2x expects | |
serialDATA = hex2text(data[i][‘data’]) # Converting hex to ASCII. | |
text = serialDATA.split(“_”); # Splitting ASCII string into chunks | |
# Replace ‘temp_f’ below with the name of your first M2X stream | |
# ‘re.sub(“[^0-9^.]”, “”, text[0])’ below sanitizes string for M2X by stripping out any non-numeric characters. M2X is picky. | |
res = sendStream2M2x(dev_info[‘m2x_primary_key’], dev_info[‘m2x_device_id’], | |
‘temp_f’, timestamp, re.sub(“[^0-9^.]”, “”, text[0])) # Replace ‘temp_f’ with name of your first M2X stream | |
if res == ‘{“status”:”accepted”}’: | |
print “OK: M2X Stream Updated on ATT Servers”, ‘temp_f’,datetime.datetime.now(), | |
‘n text = ‘, text[0], hex(int(node_id_hex, 16)) | |
else: | |
print “ERROR: M2X Stream Failed to Update on ATT Servers reason: %s” % (res) | |
print data[i][‘sensorName’], timestamp, data[i][‘data’], text[0], hex(int(node_id_hex, 16)), | |
datetime.datetime.now() | |
# We can’t let a misconfigured device block all other UL SDUs | |
# so we drop this SDU and must go back and push it to M2x in the future | |
incrementNextUlSduPointer(nextSduFile, last_sdu_id) | |
allMeasAccepted = 0 | |
# Now we run the same sequence again for our second M2X stream ‘humidity’. Rinse and repeat for each additional stream you want data sent to. | |
res = sendStream2M2x(dev_info[‘m2x_primary_key’], dev_info[‘m2x_device_id’], | |
‘humidity’, timestamp, re.sub(“[^0-9^.]”, “”, text[1])) # Replace ‘humidity’ with name of your second M2X stream | |
if res == ‘{“status”:”accepted”}’: | |
print “OK: M2X Stream Updated on ATT Servers”, ‘humidity’,datetime.datetime.now(), | |
‘n text = ‘, text[1], hex(int(node_id_hex, 16)) | |
else: | |
print “ERROR: M2X Stream Failed to Update on ATT Servers reason: %s” % (res) | |
print data[i][‘sensorName’], timestamp, data[i][‘data’], text[1], hex(int(node_id_hex, 16)), | |
datetime.datetime.now() | |
# We can’t let a misconfigured device block all other UL SDUs | |
# so we drop this SDU and must go back and push it to M2x in the future | |
incrementNextUlSduPointer(nextSduFile, last_sdu_id) | |
allMeasAccepted = 0 | |
# We only increment next SDU if the current SDU made it to M2X completely | |
# or in the case of certain errors | |
if allMeasAccepted: | |
incrementNextUlSduPointer(nextSduFile, last_sdu_id) | |
else: | |
print ‘INFO: msgType will not be sent to M2x: ‘, msgType | |
print ‘Data: ‘, data | |
incrementNextUlSduPointer(nextSduFile, last_sdu_id) # increment if we discard and don’t send to M2X |