Skip to content

Commit

Permalink
redo gcode testing's emulator setup.
Browse files Browse the repository at this point in the history
  • Loading branch information
amit lissack committed Oct 29, 2021
1 parent c7553cb commit 6b11e53
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
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 ModuleServerClient:
"""A module server client."""

Expand Down Expand Up @@ -55,9 +60,12 @@ async def connect(

async def read(self) -> Message:
"""Read a message from the module server."""
b = await self._reader.readuntil(MessageDelimiter)
m: Message = Message.parse_raw(b)
return m
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."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
from typing import Sequence, Set, Callable, List, Awaitable

from opentrons.drivers.rpi_drivers.types import USBPort
from opentrons.hardware_control.emulation.module_server.client import ModuleServerClient
from opentrons.hardware_control.emulation.module_server.client import (
ModuleServerClient,
ModuleServerClientError,
)
from opentrons.hardware_control.emulation.module_server.models import Message
from opentrons.hardware_control.emulation.module_server.server import log
from opentrons.hardware_control.emulation.settings import Settings
Expand Down Expand Up @@ -48,8 +51,12 @@ def __init__(self, client: ModuleServerClient, notify_method: NotifyMethod) -> N
async def run(self) -> None:
"""Run the listener."""
while True:
m = await self._client.read()
await self.handle_message(message=m)
try:
m = await self._client.read()
await self.handle_message(message=m)
except ModuleServerClientError:
log.exception("Read error.")
break

async def handle_message(self, message: Message) -> None:
"""Call callback with results of message.
Expand Down
2 changes: 0 additions & 2 deletions api/src/opentrons/hardware_control/emulation/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,6 @@ class Settings(BaseSettings):
plate_temperature=TemperatureModelSettings(),
)

host: str = "0.0.0.0"

heatershaker_proxy: ProxySettings = ProxySettings(
emulator_port=9000, driver_port=9995
)
Expand Down
5 changes: 3 additions & 2 deletions api/tests/opentrons/hardware_control/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
def emulator_settings() -> Settings:
"""Emulator settings"""
return Settings(
host="0.0.0.0",
smoothie=SmoothieSettings(
left=PipetteSettings(model="p20_multi_v2.0", id="P3HMV202020041605"),
right=PipetteSettings(model="p20_single_v2.0", id="P20SV202020070101"),
Expand All @@ -37,7 +36,9 @@ def _run_app() -> None:

async def _wait_ready() -> None:
c = await ModuleServerClient.connect(
host="localhost", port=emulator_settings.module_server.port
host="localhost",
port=emulator_settings.module_server.port,
interval_seconds=1,
)
await wait_emulators(client=c, modules=modules, timeout=5)
c.close()
Expand Down
2 changes: 1 addition & 1 deletion g-code-testing/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def _diff(self) -> str:
else:
text = "No difference between compared strings"

return text
return text

def _configurations(self) -> str:
"""Get a list of runnable G-Code Configurations"""
Expand Down
72 changes: 35 additions & 37 deletions g-code-testing/g_code_parsing/g_code_engine.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import os
import sys
import asyncio
import time
from multiprocessing import Process
from typing import Generator, Callable
from typing import Generator, Callable, Iterator
from collections import namedtuple

from opentrons.hardware_control.emulation.settings import Settings
Expand Down Expand Up @@ -59,23 +60,24 @@ def _get_loop() -> asyncio.AbstractEventLoop:
asyncio.set_event_loop(_loop)
return asyncio.get_event_loop()

@staticmethod
def _start_emulation_app(emulator_settings: Settings) -> Process:
"""Start emulated OT-2"""
@contextmanager
def _emulate(self) -> Iterator[ThreadManager]:
"""Context manager that starts emulated OT-2 hardware environment. A
hardware controller is returned."""
modules = [ModuleType.Magnetic, ModuleType.Temperature, ModuleType.Thermocycler]

# Entry point for the emulator app process
def _run_app():
asyncio.run(
run_app.run(emulator_settings, modules=[m.value for m in modules])
)
asyncio.run(run_app.run(self._config, modules=[m.value for m in modules]))

proc = Process(target=_run_app)
proc.daemon = True
proc.start()

# Entry point for process that waits for emulation to be ready.
async def _wait_ready() -> None:
c = await ModuleServerClient.connect(
host="localhost", port=emulator_settings.module_server.port
host="localhost", port=self._config.module_server.port
)
await wait_emulators(client=c, modules=modules, timeout=5)
c.close()
Expand All @@ -88,18 +90,22 @@ def _run_wait_ready():
ready_proc.start()
ready_proc.join()

return proc

@staticmethod
def _emulate_hardware(settings: Settings) -> ThreadManager:
"""Created emulated smoothie"""
# Hardware controller
conf = build_config({})
emulator = ThreadManager(
API.build_hardware_controller,
conf,
GCodeEngine.URI_TEMPLATE % settings.smoothie.port,
GCodeEngine.URI_TEMPLATE % self._config.smoothie.port,
)
return emulator
# Wait for modules to be present
while len(emulator.attached_modules) != len(modules):
time.sleep(0.1)

yield emulator

# Finished. Stop the emulator
proc.kill()
proc.join()

@staticmethod
def _get_protocol(file_path: str) -> Protocol:
Expand All @@ -118,20 +124,16 @@ def run_protocol(self, path: str) -> Generator:
:return: GCodeProgram with all the parsed data
"""
file_path = os.path.join(get_configuration_dir(), path)
app_process = self._start_emulation_app(emulator_settings=self._config)
protocol = self._get_protocol(file_path)
context = ProtocolContext(
implementation=ProtocolContextImplementation(
hardware=self._emulate_hardware(settings=self._config)
),
loop=self._get_loop(),
)
parsed_protocol = parse(protocol.text, protocol.filename)
with GCodeWatcher(emulator_settings=self._config) as watcher:
execute.run_protocol(parsed_protocol, context=context)
yield GCodeProgram.from_g_code_watcher(watcher)
app_process.terminate()
app_process.join()
with self._emulate() as h:
protocol = self._get_protocol(file_path)
context = ProtocolContext(
implementation=ProtocolContextImplementation(hardware=h),
loop=self._get_loop(),
)
parsed_protocol = parse(protocol.text, protocol.filename)
with GCodeWatcher(emulator_settings=self._config) as watcher:
execute.run_protocol(parsed_protocol, context=context)
yield GCodeProgram.from_g_code_watcher(watcher)

@contextmanager
def run_http(self, executable: Callable):
Expand All @@ -140,11 +142,7 @@ def run_http(self, executable: Callable):
:param executable: Function connected to HTTP Request to execute
:return:
"""
app_process = self._start_emulation_app(emulator_settings=self._config)
with GCodeWatcher(emulator_settings=self._config) as watcher:
asyncio.run(
executable(hardware=self._emulate_hardware(settings=self._config))
)
yield GCodeProgram.from_g_code_watcher(watcher)
app_process.terminate()
app_process.join()
with self._emulate() as h:
with GCodeWatcher(emulator_settings=self._config) as watcher:
asyncio.run(executable(hardware=h))
yield GCodeProgram.from_g_code_watcher(watcher)
1 change: 0 additions & 1 deletion g-code-testing/tests/g_code_parsing/test_g_code_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from g_code_parsing.utils import get_configuration_dir

CONFIG = Settings(
host="0.0.0.0",
smoothie=SmoothieSettings(
left=PipetteSettings(model="p20_single_v2.0", id="P20SV202020070101"),
right=PipetteSettings(model="p20_single_v2.0", id="P20SV202020070101"),
Expand Down

0 comments on commit 6b11e53

Please sign in to comment.