-
Notifications
You must be signed in to change notification settings - Fork 179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(api): make modules aware of available fw updates in file system #4856
Conversation
Creates a generalized approach for determining whether an attached module is capable of being updated, based on the module firmware artifacts bundled in the filesystem. In addition to adding this functionality, it includes a couple of refactors to dedupe common module subclass responsibilities. In debugging this branch, I also included some loop level error handling so exceptions in asyncio tasks (e.g. module instance creation) don't get swallowed. re #4575
aebf2db
to
5f370a2
Compare
Codecov Report
@@ Coverage Diff @@
## edge #4856 +/- ##
==========================================
- Coverage 57.97% 57.78% -0.19%
==========================================
Files 960 962 +2
Lines 26311 26639 +328
==========================================
+ Hits 15253 15393 +140
- Misses 11058 11246 +188
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Early thoughts on the draft. Structurally it looks ok but there's some inline comments
def use_or_initialize_loop(loop: Optional[asyncio.AbstractEventLoop]): | ||
checked_loop = loop or asyncio.get_event_loop() | ||
|
||
# TODO: BC 2020-01-24 use loop.add_signal_handler for proper cleanup |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i give this a big maybe - signal handlers have to be added from the main thread every time, and some signals can't be handled anyway.
@@ -44,6 +44,19 @@ def _log_call_inner(*args, **kwargs): | |||
return _log_call_inner | |||
|
|||
|
|||
def handle_loop_exception(loop: asyncio.AbstractEventLoop, context): | |||
msg = context.get("exception", context["message"]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bit of a nitpick but we should really log both the exception and the message. Per the docs, neither is optional so we shouldn't have to use get here.
@@ -44,6 +44,19 @@ def _log_call_inner(*args, **kwargs): | |||
return _log_call_inner | |||
|
|||
|
|||
def handle_loop_exception(loop: asyncio.AbstractEventLoop, context): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
context
could probably be typed as Dict[str, Any]
mod_log.error(f"Caught exception: {msg}") | ||
|
||
|
||
def use_or_initialize_loop(loop: Optional[asyncio.AbstractEventLoop]): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Annotate return type as asyncio.AbstractEventLoop
simulating: bool = False, | ||
loop: asyncio.AbstractEventLoop = None) -> None: | ||
self._port = port | ||
if None is loop: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can use that helper function here
fw_dir = CONFIG['robot_firmware_dir'] | ||
fw_resources = [fw_dir / item for item in os.listdir(fw_dir)] | ||
for fw_resource in fw_resources: | ||
matches = MODULE_FW_RE.search(str(fw_resource)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could and probably should do a match
on fw_resource.name
def has_available_update(self) -> bool: | ||
""" Return whether a newer firmware file is available """ | ||
if self._device_info is not None: | ||
raw_device_version = self._device_info.get('version', None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In what conditions do we expect device_info to be valid but not have a version? Also we can just do .get('version')
, returning None on key miss is implicit
else: | ||
self._loop = loop | ||
self._device_info = None | ||
self._available_update_version: Optional[str] = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO we should bundle these two into a named tuple or something and have a function that returns Optional[ModuleFirmware]
so we can't possibly be in a one-but-not-the-other situation
api/src/opentrons/main.py
Outdated
resources.extend([ROBOT_FIRMWARE_DIR / item | ||
for item in os.listdir(ROBOT_FIRMWARE_DIR)]) | ||
fw_dir = CONFIG['robot_firmware_dir'] | ||
resources.extend([fw_dir / item for item in os.listdir(fw_dir)]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can be resources.extend(fw_dir.iterdir())
(or resources.extend(list(fw_dir.iterdir()))
if extend doesn't iterate)
@@ -402,6 +395,10 @@ def set_loop(self, newLoop): | |||
def port(self): | |||
return self._port | |||
|
|||
@property | |||
def available_update_path(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needs a return value annotation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking really good!
overview
Creates a generalized approach for determining whether an attached module is capable of being
updated, based on the module firmware artifacts bundled in the filesystem. In addition to adding
this functionality, it includes a couple of refactors to dedupe common module subclass
responsibilities. In debugging this branch, I also included some loop level error handling so
exceptions in asyncio tasks (e.g. module instance creation) don't get swallowed.
re #4575
changelog
review requests