-
Notifications
You must be signed in to change notification settings - Fork 178
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(emulation): emulation refactor (#8587)
* proxy. run_emulator * Linting/Formatting * emulator * integration tests pass. * lint passes. * gcode parsing. * gcode parsing uses process for emulator. * run_emulator_client retries. * wait for emulator connection method. * scripts. * utils * g-code-testing lint * refactor(emulation): integrated with module control (#8586) * Module control refactors * new package. * start module control integration. * tests for module control integration. * implement connection listener. * module server integrated into module control. * lint * g-code-testing * redo docker compose to deal with separate emulator apps. * usb port. * expand module emulation settings. * update docker readme. * format-js * lint * go back to threadings. I don't want to debug windows nonsense. * fix bug. * redo gcode testing's emulator setup. * documentation. * clean up. * don't listen for emulators on robot. * chore(gcode): reduce time of gcode-testing (#8683) * faster g-code-tests in ci * reduce hold time. * use dev folder in s3 * wait. Co-authored-by: Derek Maggio <[email protected]>
- Loading branch information
amitlissack
and
Derek Maggio
authored
Nov 9, 2021
1 parent
4f50044
commit b97f1bb
Showing
48 changed files
with
1,662 additions
and
325 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,86 +1,67 @@ | ||
import asyncio | ||
import logging | ||
from opentrons.hardware_control.emulation.connection_handler import ConnectionHandler | ||
from opentrons.hardware_control.emulation.magdeck import MagDeckEmulator | ||
|
||
from opentrons.hardware_control.emulation.module_server import ModuleStatusServer | ||
from opentrons.hardware_control.emulation.parser import Parser | ||
from opentrons.hardware_control.emulation.proxy import Proxy | ||
from opentrons.hardware_control.emulation.run_emulator import run_emulator_server | ||
from opentrons.hardware_control.emulation.settings import Settings | ||
from opentrons.hardware_control.emulation.tempdeck import TempDeckEmulator | ||
from opentrons.hardware_control.emulation.thermocycler import ThermocyclerEmulator | ||
from opentrons.hardware_control.emulation.smoothie import SmoothieEmulator | ||
from opentrons.hardware_control.emulation.types import ModuleType | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
SMOOTHIE_PORT = 9996 | ||
THERMOCYCLER_PORT = 9997 | ||
TEMPDECK_PORT = 9998 | ||
MAGDECK_PORT = 9999 | ||
|
||
class Application: | ||
"""The emulator application.""" | ||
|
||
class ServerManager: | ||
""" | ||
Class to start and stop emulated smoothie and modules. | ||
""" | ||
def __init__(self, settings: Settings) -> None: | ||
"""Constructor. | ||
def __init__(self, settings=Settings()) -> None: | ||
host = settings.host | ||
self._mag_emulator = MagDeckEmulator(parser=Parser()) | ||
self._temp_emulator = TempDeckEmulator(parser=Parser()) | ||
self._therm_emulator = ThermocyclerEmulator(parser=Parser()) | ||
Args: | ||
settings: Application settings. | ||
""" | ||
self._settings = settings | ||
self._status_server = ModuleStatusServer(settings.module_server) | ||
self._smoothie_emulator = SmoothieEmulator( | ||
parser=Parser(), settings=settings.smoothie | ||
) | ||
|
||
self._mag_server = self._create_server( | ||
host=host, | ||
port=MAGDECK_PORT, | ||
handler=ConnectionHandler(self._mag_emulator), | ||
self._magdeck = Proxy( | ||
ModuleType.Magnetic, self._status_server, self._settings.magdeck_proxy | ||
) | ||
self._temp_server = self._create_server( | ||
host=host, | ||
port=TEMPDECK_PORT, | ||
handler=ConnectionHandler(self._temp_emulator), | ||
self._temperature = Proxy( | ||
ModuleType.Temperature, | ||
self._status_server, | ||
self._settings.temperature_proxy, | ||
) | ||
self._therm_server = self._create_server( | ||
host=host, | ||
port=THERMOCYCLER_PORT, | ||
handler=ConnectionHandler(self._therm_emulator), | ||
self._thermocycler = Proxy( | ||
ModuleType.Thermocycler, | ||
self._status_server, | ||
self._settings.thermocycler_proxy, | ||
) | ||
self._smoothie_server = self._create_server( | ||
host=host, | ||
port=SMOOTHIE_PORT, | ||
handler=ConnectionHandler(self._smoothie_emulator), | ||
self._heatershaker = Proxy( | ||
ModuleType.Heatershaker, | ||
self._status_server, | ||
self._settings.heatershaker_proxy, | ||
) | ||
|
||
async def run(self): | ||
async def run(self) -> None: | ||
"""Run the application.""" | ||
await asyncio.gather( | ||
self._mag_server, | ||
self._temp_server, | ||
self._therm_server, | ||
self._smoothie_server, | ||
self._status_server.run(), | ||
run_emulator_server( | ||
host=self._settings.smoothie.host, | ||
port=self._settings.smoothie.port, | ||
emulator=self._smoothie_emulator, | ||
), | ||
self._magdeck.run(), | ||
self._temperature.run(), | ||
self._thermocycler.run(), | ||
self._heatershaker.run(), | ||
) | ||
|
||
@staticmethod | ||
async def _create_server(host: str, port: int, handler: ConnectionHandler) -> None: | ||
"""Run a server.""" | ||
server = await asyncio.start_server(handler, host, port) | ||
|
||
async with server: | ||
await server.serve_forever() | ||
|
||
def reset(self): | ||
self._smoothie_emulator.reset() | ||
self._mag_emulator.reset() | ||
self._temp_emulator.reset() | ||
self._therm_emulator.reset() | ||
|
||
def stop(self): | ||
self._smoothie_server.close() | ||
self._temp_server.close() | ||
self._therm_server.close() | ||
self._mag_server.close() | ||
|
||
|
||
if __name__ == "__main__": | ||
logging.basicConfig(format="%(asctime)s:%(message)s", level=logging.DEBUG) | ||
asyncio.run(ServerManager().run()) | ||
s = Settings() | ||
asyncio.run(Application(settings=s).run()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
api/src/opentrons/hardware_control/emulation/module_server/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
"""Package for the module status server.""" | ||
from .server import ModuleStatusServer | ||
from .client import ModuleStatusClient | ||
|
||
__all__ = [ | ||
"ModuleStatusServer", | ||
"ModuleStatusClient", | ||
] |
72 changes: 72 additions & 0 deletions
72
api/src/opentrons/hardware_control/emulation/module_server/client.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
from __future__ import annotations | ||
import asyncio | ||
from asyncio import IncompleteReadError, LimitOverrunError | ||
from typing import Optional | ||
|
||
from opentrons.hardware_control.emulation.module_server.models import Message | ||
from opentrons.hardware_control.emulation.module_server.server import MessageDelimiter | ||
|
||
|
||
class ModuleServerClientError(Exception): | ||
pass | ||
|
||
|
||
class ModuleStatusClient: | ||
"""A module server client.""" | ||
|
||
def __init__( | ||
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter | ||
) -> None: | ||
"""Constructor.""" | ||
self._reader = reader | ||
self._writer = writer | ||
|
||
@classmethod | ||
async def connect( | ||
cls, | ||
host: str, | ||
port: int, | ||
retries: int = 3, | ||
interval_seconds: float = 0.1, | ||
) -> ModuleStatusClient: | ||
"""Connect to the module server. | ||
Args: | ||
host: module server host. | ||
port: module server port. | ||
retries: number of retries | ||
interval_seconds: time between retries. | ||
Returns: | ||
None | ||
Raises: | ||
IOError on retry expiry. | ||
""" | ||
r: Optional[asyncio.StreamReader] = None | ||
w: Optional[asyncio.StreamWriter] = None | ||
for i in range(retries): | ||
try: | ||
r, w = await asyncio.open_connection(host=host, port=port) | ||
break | ||
except OSError: | ||
await asyncio.sleep(interval_seconds) | ||
|
||
if r is not None and w is not None: | ||
return ModuleStatusClient(reader=r, writer=w) | ||
else: | ||
raise IOError( | ||
f"Failed to connect to module_server at after {retries} retries." | ||
) | ||
|
||
async def read(self) -> Message: | ||
"""Read a message from the module server.""" | ||
try: | ||
b = await self._reader.readuntil(MessageDelimiter) | ||
m: Message = Message.parse_raw(b) | ||
return m | ||
except (IncompleteReadError, LimitOverrunError) as e: | ||
raise ModuleServerClientError(str(e)) | ||
|
||
def close(self) -> None: | ||
"""Close the client.""" | ||
self._writer.close() |
Oops, something went wrong.