Separating device code from orchestrator code and facilitating cross-communication

For example, I want to control Jubilee remotely without exposing the entire OS.

Ideally, this would be something that makes it easy to make an MQTT interlayer, keeping in mind that often microcontrollers are being used.

The way I’m doing this is by converting control code into a RESTful webAPI using fastapi. If you use class factories and design it right, building the API is as simple as adding appropriate decorators to methods you want exposed, Fastapi will handle the rest and generate openapi specification docs and a swagger interface to test the automation.
I like using this method because REST APIs are very popular. Most programming languages support them well and commercial hardware quite often implements them themselves (saving you time). It’s also incredibly easy to do cross device communication using them due to their nature.

1 Like

An option that is compatible with a microcontroller is whitelisting a set of commands: https://chatgpt.com/share/73d1a292-0cd2-43bf-ba7c-314ccb7e5e02. There are some ways to do over-the-air (OTA) updates, e.g., by having the microcontroller check for changes on a file hosted on GitHub and run the new file if it’s been updated. However, some have had issues with robustness I think. xref: Considerations for performing software updates to hardware when in-use · sparks-baird/self-driving-lab-demo · Discussion #98 · GitHub

OTA updates and updates on a running system can quickly become a complex topic depending on your specific requirements (e.g. regarding availability/uptime, consistency/integrity, and efficiency of network bandwidth usage). I think the most common simple pattern for OTA updates in embedded systems and computers (e.g. phones, Chromebooks, automotive ECUs, etc.) where integrity & robustness matter (so that interrupting the update process, e.g. by unplanned loss of power or network connectivity, will not leave it in a weird/unknown/degraded/nonfunctioning state) is an “A/B system” where you always have an active copy of all the software and an inactive copy of all the software:

  • While the active copy is running, you can modify/replace the inactive copy in the background, and once you’re done making those changes you can have the update system swap between the active copy and the inactive copy as an atomic (all-or-nothing) operation, which usually also involves restarting the relevant software/firmware running on the device.
  • If you’re updating the entire system (e.g. a firmware update on a microcontroller or an extensive OS upgrade on a computer) then you’ll probably need to restart the device; if you have a supervisor which runs software modules (e.g. Docker running containers on a computer, or systemd running services on a computer) and you only need to update those modules, then you can just restart those modules without restarting the entire device. If you can schedule downtime to restart devices for updates, that will be the simplest option. No-downtime alternatives (e.g. hot code reloading as a way of hotswapping your code; or other deployment patterns used in the cloud) usually introduce significant system-level design complexity or have other caveats which may limit their applicability for controlling lab equipment.
  • The A/B system also enables transactional updates: if the updated version is problematic, e.g. because it introduces a new bug which you cannot tolerate, the A/B system makes it simple to switch back to the inactive copy as a way of “rolling back” from the update.

For updating software/firmware which runs lab equipment where safety matters, you will probably require an update system which provides atomic updates, e.g. with an A/B system.

I think OTA updates can/should be a mostly-independent topic vs. methods for controlling a device over a network protocol - at least under the assumption that your device’s control surface is an API which includes a way to know about version compatibility (e.g. via a version string which the API client can check) in the semantics of API endpoints before vs. after an update. Then the API client’s error-handling mechanisms (e.g. if they include “retry” logic) need to handle the situation that the API is incompatible with the client.

1 Like

I would say both the API aspect and the OTA update depend on the IT system level / architekture we are operating on. On a microcontroller-level I would agree that the A/B system mentioned is the best approach, which is also well supported from vendors like Cypress/Infineon. Also, lightwight protocols like MQTT make sense in this scenario. But if we can afford to run at least an ARM device like RaspberryPis IMHO we should make use of containerization (exposing UART, GPIO to it and wrapping to REST/OPC-UA) handling our own software while we could rely on OS & package updates running stable 99.9% of the time (at least with pretests).

1 Like

amendment: having a proper LInux OS + SSH available would allow to use ansible for automated and reliable rollout + maintenance of software configurations, including containers

1 Like

@SimonStier thoughts on GitHub - FreeOpcUa/opcua-asyncio: OPC UA library for python >= 3.7 as a Python library for OPC-UA?

We make heavy use of that (also I’m not aware of an alternative). Its not an official implementation like the C/C++ stack, but pretty stable.

1 Like