diff --git a/otbox.py b/otbox.py index 1a61bb7..831cd76 100644 --- a/otbox.py +++ b/otbox.py @@ -18,6 +18,7 @@ import base64 import argparse import logging +import sys try: from PIL import Image @@ -84,7 +85,7 @@ def __init__(self, otbox): self.firmware_eui64_retrieval = os.path.join(os.path.dirname(__file__), 'bootloaders', 'opentestbed', '01bsp_eui64_prog.ihex') - self.firmware_temp = os.path.join(os.path.dirname(__file__), 'bootloaders', 'opentestbed', 'firmware_mote.ihex') + self.firmware_temp_dir = os.path.join(os.path.dirname(__file__), 'bootloaders', 'opentestbed') def bootload_mote(self, serialport, firmware_file): return subprocess.Popen( @@ -126,10 +127,19 @@ def _mqtt_handler_picturetoscreen(self, deviceType, deviceId, payload): {{TESTBED}}/deviceType/box/deviceId/box1/cmd/picturetoscreen ''' assert deviceType==DEVICETYPE_BOX - image = Image.open(requests.get(json.loads(payload)['url'], stream=True).raw) - image.thumbnail((480,320),Image.ANTIALIAS) - self._otbox.change_image_queue.put(image) - self._otbox.change_image_queue.join() + try: + image_url = json.loads(payload)['url'] + response = requests.get(image_url, stream=True) + image = Image.open(response.raw) + except IOError: + # the image is not available now; skip this one + print('image at {0} is not available, '.format(image_url) + + 'HTTP response code {0}'.format(response.status_code)) + sys.stdout.flush() + else: + image.thumbnail((480,320),Image.ANTIALIAS) + self._otbox.change_image_queue.put(image) + self._otbox.change_image_queue.join() return {} def _mqtt_handler_colortoscreen(self, deviceType, deviceId, payload): @@ -171,11 +181,7 @@ def bootload_mote(self, serialport, firmware_file): stderr=subprocess.PIPE) def on_mqtt_connect(self): - # in case of IoT-LAB mote discovery is started immediately upon `otbox.py` startup - payload_status = { - 'token': 123 - } - self._otbox._mqtt_handler_discovermotes('box', 'all', json.dumps(payload_status)) + pass class WilabTestbed(Testbed): @@ -202,11 +208,7 @@ def bootload_mote(self, serialport, firmware_file): return subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) def on_mqtt_connect(self): - # in case of WiLab mote discovery is started immediately upon `otbox.py` startup - payload_status = { - 'token': 123 - } - self._otbox._mqtt_handler_discovermotes('box', 'all', json.dumps(payload_status)) + pass AVAILABLE_TESTBEDS = { @@ -307,13 +309,15 @@ def _on_mqtt_connect(self, client, userdata, flags, rc): self.tb.on_mqtt_connect() + # discover motes + self._execute_command_safely('box', 'all', json.dumps({'token': 123}), 'discovermotes') def _on_mqtt_message(self, client, userdata, message): # call the handler - self._excecute_commands(message.topic, message.payload) + self._execute_commands(message.topic, message.payload) - def _excecute_commands(self, topic, payload): + def _execute_commands(self, topic, payload): # parse the topic to extract deviceType, deviceId and cmd ([0-9\-]+) try: m = re.search('{0}/deviceType/([a-z]+)/deviceId/([\w,\-]+)/cmd/([a-z]+)'.format(self.testbed), topic) @@ -342,7 +346,7 @@ def _excecute_commands(self, topic, payload): for d in device_to_comand: commands_handlers += [threading.Thread( name = '{0}_command_{1}'.format(d, cmd), - target = self._excecute_command_safely, + target = self._execute_command_safely, args = (deviceType, d, payload, cmd)) ] for handler in commands_handlers: @@ -350,7 +354,7 @@ def _excecute_commands(self, topic, payload): except: self.logger.exception("Could not parse command with topic %s", topic) - def _excecute_command_safely(self, deviceType, deviceId, payload, cmd): + def _execute_command_safely(self, deviceType, deviceId, payload, cmd): ''' Executes the handler of a command in a try/except environment so exception doesn't crash server. ''' @@ -483,6 +487,7 @@ def _mqtt_handler_discovermotes(self, deviceType, deviceId, payload): break else: self.logger.debug("No EUI64 found in '%s'", line) + ser.close() for e in self.motesinfo: if 'EUI64' in e: @@ -549,15 +554,25 @@ def _mqtt_handler_program(self, deviceType, deviceId, payload): # disconnect from the serialports self.SerialportHandlers[mote['serialport']].disconnectSerialPort() - time.sleep(2) # wait 2 seconds to release the serial ports + if isinstance(self.tb, OpenTestbed): + # For OpenTestbed, we will use a separate firmware_temp + # for each deviceId. Although other testbeds may need to + # do the same, we keep the original implementation for + # them for now. + firmware_name = '{0}_{1}'.format(deviceId, payload['description']) + firmware_temp = os.path.join(self.tb.firmware_temp_dir, + firmware_name) + else: + firmware_temp = self.tb.firmware_temp + if 'url' in payload and payload['url'].startswith("ftp://"): # use urllib to get firmware from ftp server (requests doesn't support for ftp) - urllib.urlretrieve(payload['url'],self.tb.firmware_temp) + urllib.urlretrieve(payload['url'],firmware_temp) urllib.urlcleanup() else: # store the firmware to load into a temporary file - with open(self.tb.firmware_temp, 'wb') as f: + with open(firmware_temp, 'wb') as f: if 'url' in payload: # download file from url if present file = requests.get(payload['url'], allow_redirects=True) f.write(file.content) @@ -570,7 +585,7 @@ def _mqtt_handler_program(self, deviceType, deviceId, payload): # bootload the mote bootload_success = self._bootload_motes( serialports = [mote['serialport']], - firmware_file = self.tb.firmware_temp, + firmware_file = firmware_temp, ) assert len(bootload_success)==1 @@ -593,6 +608,7 @@ def _mqtt_handler_tomoteserialbytes(self, deviceType, deviceId, payload): mote = self._eui64_to_moteinfoelem(deviceId) serialHandler = serial.Serial(mote['serialport'], baudrate=self.tb.baudrate, xonxoff=True) serialHandler.write(bytearray(payload['serialbytes'])) + serialHandler.close() self.SerialportHandlers[mote['serialport']].connectSerialPort() def _mqtt_handler_reset(self, deviceType, deviceId, payload): @@ -603,14 +619,7 @@ def _mqtt_handler_reset(self, deviceType, deviceId, payload): mote = self._eui64_to_moteinfoelem(deviceId) self.SerialportHandlers[mote['serialport']].disconnectSerialPort() - pyserialHandler = serial.Serial(mote['serialport'], baudrate=self.tb.baudrate) - pyserialHandler.setDTR(False) - pyserialHandler.setRTS(True) - time.sleep(0.2) - pyserialHandler.setDTR(True) - pyserialHandler.setRTS(False) - time.sleep(0.2) - pyserialHandler.setDTR(False) + self._reset_mote(mote['serialport']) ## start serial self.SerialportHandlers[mote['serialport']].connectSerialPort() @@ -638,13 +647,13 @@ def _mqtt_handler_disable(self, deviceType, deviceId, payload): def _heartbeatthread_func(self): while True: - # wait a bit - time.sleep(OtBox.HEARTBEAT_PERIOD) # publish a heartbeat message self.mqttclient.publish( topic = '{0}/heartbeat'.format(self.mqtttopic_box_notif_prefix), payload = json.dumps({'software_version': OTBOX_VERSION}), ) + # wait a bit + time.sleep(OtBox.HEARTBEAT_PERIOD) #=== helpers @@ -661,6 +670,9 @@ def _bootload_motes(self, serialports, firmware_file): # simply the name port = serialport.split('/')[-1] + # reset the mote first + self._reset_mote(serialport) + # stop serial reader ongoing_bootloads[port] = self.tb.bootload_mote(serialport, firmware_file) @@ -675,6 +687,8 @@ def _bootload_motes(self, serialports, firmware_file): # record whether bootload worked or not returnVal += [ongoing_bootloads[ongoing_bootload].returncode== 0] + if not returnVal[-1]: + raise RuntimeError(stdout, stderr) self.logger.debug("Finished bootload_motes with returnVal %s", str(returnVal)) return returnVal @@ -695,7 +709,16 @@ def _reboot_function(self): time.sleep(3) subprocess.call(["sudo","reboot"]) - + def _reset_mote(self, serial_port): + pyserialHandler = serial.Serial(serial_port, baudrate=self.tb.baudrate) + pyserialHandler.setDTR(False) + pyserialHandler.setRTS(True) + time.sleep(0.2) + pyserialHandler.setDTR(True) + pyserialHandler.setRTS(False) + time.sleep(0.2) + pyserialHandler.setDTR(False) + pyserialHandler.close() def _discover_serialports_availables(self): serialports_available = []