Skip to content

Commit

Permalink
feat(api): add module firmware update endpoint
Browse files Browse the repository at this point in the history
Closes #1654
  • Loading branch information
sanni-t committed Aug 30, 2018
1 parent 886b4df commit 8a4c813
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 71 deletions.
34 changes: 18 additions & 16 deletions api/opentrons/modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,14 @@ def discover_and_connect():

def enter_bootloader(module):
"""
Using the driver method, enter bootloader mode of the atmega32u4. The bootloader mode opens a new port on the uC to
upload the hex file. After receiving a 'dfu' command, the firmware provides a 3-second window to close the
current port so as to do a clean switch to the bootloader port. The new port shows up as 'ttyn_bootloader' on the pi
Use this port to upload firmware
NOTE: Modules with old bootloader will have the bootloader port show up as a regular module port- 'ttyn_tempdeck'/
'ttyn_magdeck' with the port number being either different or same as the one that the module was originally on.
Using the driver method, enter bootloader mode of the atmega32u4.
The bootloader mode opens a new port on the uC to upload the hex file.
After receiving a 'dfu' command, the firmware provides a 3-second window to
close the current port so as to do a clean switch to the bootloader port.
The new port shows up as 'ttyn_bootloader' on the pi; upload fw through it.
NOTE: Modules with old bootloader will have the bootloader port show up as
a regular module port- 'ttyn_tempdeck'/ 'ttyn_magdeck' with the port number
being either different or same as the one that the module was originally on
So we check for changes in ports and use the appropriate one
"""
ports_before_dfu_mode = discover_ports() # Required only for old bootloadr
Expand All @@ -111,14 +113,15 @@ async def update_firmware(module, firmware_file_path, config_file_path, loop):
"""
Run avrdude firmware upload command. Switch back to normal module port
Note: For modules with old bootloader, the kernel could assign the module a new port after the update
(since the board is automatically reset). Scan for such a port change and use the appropriate port
Note: For modules with old bootloader, the kernel could assign the module
a new port after the update (since the board is automatically reset).
Scan for such a port change and use the appropriate port
"""
# TODO: Make sure the module isn't in the middle of operation

ports_before_update = discover_ports()
print("update_firmware sending file to port:{}".format(module._port))
avrdude_cmd = {
avrdude_options = {
'config_file': config_file_path,
'part_no': 'atmega32u4',
'programmer_id': 'avr109',
Expand All @@ -127,13 +130,12 @@ async def update_firmware(module, firmware_file_path, config_file_path, loop):
'firmware_file': firmware_file_path
}
proc = await asyncio.create_subprocess_exec(
'avrdude '
'-C{config_file} '
'-v -p{part_no} '
'-c{programmer_id} '
'-P{port_name} '
'-b{baudrate} -D '
'-Uflash:w:{firmware_file}:i'.format(**avrdude_cmd),
'avrdude', '-C{config_file}'.format(**avrdude_options), '-v',
'-p{part_no}'.format(**avrdude_options),
'-c{programmer_id}'.format(**avrdude_options),
'-P{port_name}'.format(**avrdude_options),
'-b{baudrate}'.format(**avrdude_options),
'-D', '-Uflash:w:{firmware_file}:i'.format(**avrdude_options),
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE, loop=loop)
await proc.wait()
Expand Down
51 changes: 0 additions & 51 deletions api/opentrons/server/endpoints/serverlib_fallback.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,33 +66,6 @@ async def install_smoothie_firmware(data, loop):
return {'message': msg, 'filename': filename}


async def install_module_firmware(module_serial, data, loop=None):
import opentrons
from opentrons.server.endpoints.update import _update_module_firmware

fw_filename = data.filename
log.info('Flashing image "{}", this will take about a minute'.format(
fw_filename))
content = data.file.read()

with open(fw_filename, 'wb') as wf:
wf.write(content)

config_file_path = os.path.join(
os.path.abspath(os.path.dirname(opentrons.__file__)),
'config', 'modules', 'avrdude.conf')

msg = await _update_module_firmware(module_serial, fw_filename,
config_file_path, loop=loop)
log.debug('Firmware update complete')
try:
os.remove(fw_filename)
except OSError:
pass
log.debug("Result: {}".format(msg))
return {'message': msg, 'filename': fw_filename}


def _set_ignored_version(version):
"""
Private helper function that writes the most updated
Expand Down Expand Up @@ -217,30 +190,6 @@ async def update_firmware(request):
return web.json_response(res, status=status)


async def update_module_firmware(request):
"""
This handler accepts a POST request with Content-Type: multipart/form-data
and a file field in the body named "module_firmware". The file should
be a valid HEX image to be flashed to the atmega32u4. The received file is
sent via USB to the board and flashed by the avr109 bootloader. The file
is then deleted and a success code is returned
"""
log.debug('Update Firmware request received')
data = await request.post()
module_serial = request.match_info['serial']
try:
res = await install_module_firmware(module_serial,
data['module_firmware'],
request.loop)
status = 200
except Exception as e:
log.exception("Exception during firmware update:")
res = {'message': 'Exception {} raised by update of {}: {}'.format(
type(e), data, e.__traceback__)}
status = 500
return web.json_response(res, status=status)


async def restart(request):
"""
Returns OK, then waits approximately 1 second and restarts container
Expand Down
52 changes: 52 additions & 0 deletions api/opentrons/server/endpoints/update.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import logging
import asyncio
import os
from aiohttp import web
from opentrons import robot

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -44,6 +46,56 @@ async def _update_firmware(filename, loop):
return res


async def update_module_firmware(request):
"""
This handler accepts a POST request with Content-Type: multipart/form-data
and a file field in the body named "module_firmware". The file should
be a valid HEX image to be flashed to the atmega32u4. The received file is
sent via USB to the board and flashed by the avr109 bootloader. The file
is then deleted and a success code is returned
"""
log.debug('Update Firmware request received')
data = await request.post()
module_serial = request.match_info['serial']
try:
res = await install_module_firmware(module_serial,
data['module_firmware'],
request.loop)
status = 200
except Exception as e:
log.exception("Exception during firmware update:")
res = {'message': 'Exception {} raised by update of {}: {}'.format(
type(e), data, e.__traceback__)}
status = 500
return web.json_response(res, status=status)


async def install_module_firmware(module_serial, data, loop=None):
import opentrons

fw_filename = data.filename
log.info('Flashing image "{}", this will take about a minute'.format(
fw_filename))
content = data.file.read()

with open(fw_filename, 'wb') as wf:
wf.write(content)

config_file_path = os.path.join(
os.path.abspath(os.path.dirname(opentrons.__file__)),
'config', 'modules', 'avrdude.conf')

msg = await _update_module_firmware(module_serial, fw_filename,
config_file_path, loop=loop)
log.debug('Firmware update complete')
try:
os.remove(fw_filename)
except OSError:
pass
log.debug("Result: {}".format(msg))
return {'message': msg, 'filename': fw_filename}


async def _update_module_firmware(
serialnum, fw_filename, config_file_path, loop):
"""
Expand Down
6 changes: 2 additions & 4 deletions api/opentrons/server/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
from opentrons.api import MainRouter
from opentrons.server.rpc import Server
from opentrons.server import endpoints as endp
from opentrons.server.endpoints import (wifi, control, settings)
from opentrons.server.endpoints.serverlib_fallback \
import update_module_firmware
from opentrons.server.endpoints import (wifi, control, settings, update)
from opentrons.config import feature_flags as ff
from opentrons.util import environment
from opentrons.deck_calibration import endpoints as dc_endp
Expand Down Expand Up @@ -181,7 +179,7 @@ def init(loop=None):
server.app.router.add_post(
'/server/update/firmware', endpoints.update_firmware)
server.app.router.add_post(
'/modules/{serial}/update', update_module_firmware)
'/modules/{serial}/update', update.update_module_firmware)
server.app.router.add_get(
'/server/update/ignore', endpoints.get_ignore_version)
server.app.router.add_post(
Expand Down

0 comments on commit 8a4c813

Please sign in to comment.