Skip to content

Commit

Permalink
documentation.
Browse files Browse the repository at this point in the history
  • Loading branch information
amit lissack committed Oct 29, 2021
1 parent 6b11e53 commit ea431f2
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""Package for the module status server."""
from .server import ModuleStatusServer
from .client import ModuleServerClient

from .client import ModuleStatusClient

__all__ = [
"ModuleStatusServer",
"ModuleServerClient",
"ModuleStatusClient",
]
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class ModuleServerClientError(Exception):
pass


class ModuleServerClient:
class ModuleStatusClient:
"""A module server client."""

def __init__(
Expand All @@ -28,7 +28,7 @@ async def connect(
port: int,
retries: int = 3,
interval_seconds: float = 0.1,
) -> ModuleServerClient:
) -> ModuleStatusClient:
"""Connect to the module server.
Args:
Expand All @@ -52,7 +52,7 @@ async def connect(
await asyncio.sleep(interval_seconds)

if r is not None and w is not None:
return ModuleServerClient(reader=r, writer=w)
return ModuleStatusClient(reader=r, writer=w)
else:
raise IOError(
f"Failed to connect to module_server at after {retries} retries."
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"""Utililty methods and classes for interacting with the Module Status Server."""

import asyncio
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,
ModuleStatusClient,
ModuleServerClientError,
)
from opentrons.hardware_control.emulation.module_server.models import Message
Expand All @@ -20,7 +22,7 @@ async def listen_module_connection(callback: NotifyMethod) -> None:
"""Listen for module emulator connections."""
settings = Settings()
try:
client = await ModuleServerClient.connect(
client = await ModuleStatusClient.connect(
host=settings.module_server.host,
port=settings.module_server.port,
interval_seconds=1.0,
Expand All @@ -34,7 +36,7 @@ async def listen_module_connection(callback: NotifyMethod) -> None:
class ModuleListener:
"""Provide a callback for listening for new and removed module connections."""

def __init__(self, client: ModuleServerClient, notify_method: NotifyMethod) -> None:
def __init__(self, client: ModuleStatusClient, notify_method: NotifyMethod) -> None:
"""Constructor.
Args:
Expand Down Expand Up @@ -88,7 +90,7 @@ def _next_index() -> int:


async def wait_emulators(
client: ModuleServerClient,
client: ModuleStatusClient,
modules: Sequence[ModuleType],
timeout: float,
) -> None:
Expand All @@ -105,7 +107,7 @@ async def wait_emulators(
asyncio.TimeoutError on timeout.
"""

async def _wait_modules(cl: ModuleServerClient, module_set: Set[str]) -> None:
async def _wait_modules(cl: ModuleStatusClient, module_set: Set[str]) -> None:
"""Read messages from module server waiting for modules in module_set to
be connected."""
while module_set:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,30 @@

from typing_extensions import Literal

from pydantic import BaseModel
from pydantic import BaseModel, Field


class Connection(BaseModel):
class ModuleConnection(BaseModel):
"""Model a single module connection."""

url: str
module_type: str
identifier: str
url: str = Field(
...,
description="The url (port) value the module driver should connect to. "
"For example: socket://host:port",
)
module_type: str = Field(
..., description="What kind of module this connection emulates."
)
identifier: str = Field(..., description="Unique id for this emulator.")


class Message(BaseModel):
"""A message sent to module server clients."""

status: Literal["connected", "disconnected", "dump"]
connections: List[Connection]
status: Literal["connected", "disconnected", "dump"] = Field(
...,
description="`dump` includes a complete list of connected emulators. "
"`connected` for new connections. `disconnected` for emulators "
"that have disconnected. ",
)
connections: List[ModuleConnection]
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""Server notifying of module connections."""
import asyncio
import logging
from typing import Dict, Set

from opentrons.hardware_control.emulation.module_server.models import (
Connection,
ModuleConnection,
Message,
)
from opentrons.hardware_control.emulation.proxy import ProxyListener
Expand All @@ -16,7 +17,11 @@


class ModuleStatusServer(ProxyListener):
"""Server notifying of module connections."""
"""The module status server is the emulator equivalent of inotify. A client
can know when an emulated module connects or disconnects.
Clients connect and read JSON messages (See models module).
"""

def __init__(self, settings: ModuleServerSettings) -> None:
"""Constructor
Expand All @@ -25,7 +30,7 @@ def __init__(self, settings: ModuleServerSettings) -> None:
settings: app settings
"""
self._settings = settings
self._connections: Dict[str, Connection] = {}
self._connections: Dict[str, ModuleConnection] = {}
self._clients: Set[asyncio.StreamWriter] = set()

def on_server_connected(
Expand All @@ -42,7 +47,7 @@ def on_server_connected(
"""
log.info(f"On connected {server_type} {client_uri} {identifier}")
connection = Connection(
connection = ModuleConnection(
module_type=server_type, url=client_uri, identifier=identifier
)
self._connections[identifier] = connection
Expand Down Expand Up @@ -87,6 +92,7 @@ async def _handle_connection(
"""Handle a client connection to the server."""
log.info("Client connected to module server.")

# A client connected. Send a dump of all connected modules.
m = Message(status="dump", connections=list(self._connections.values()))

writer.write(m.json().encode())
Expand Down
6 changes: 5 additions & 1 deletion api/src/opentrons/hardware_control/emulation/proxy.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""The Proxy class module."""
from __future__ import annotations
import asyncio
import logging
Expand All @@ -14,14 +15,17 @@

@dataclass(frozen=True)
class Connection:
"""A connection."""
"""Attributes of a client connected on the server port (module emulator)."""

identifier: str
reader: asyncio.StreamReader
writer: asyncio.StreamWriter


class ProxyListener(ABC):
"""Interface defining an object needing to know when a server (module emulator)
connected/disconnected."""

@abstractmethod
def on_server_connected(
self, server_type: str, client_uri: str, identifier: str
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from opentrons.drivers.rpi_drivers.types import USBPort
from opentrons.hardware_control.emulation.module_server import (
helpers,
ModuleServerClient,
ModuleStatusClient,
)
from opentrons.hardware_control.emulation.module_server import models
from opentrons.hardware_control.modules import ModuleAtPort
Expand All @@ -20,7 +20,7 @@ def mock_callback() -> AsyncMock:
@pytest.fixture
def mock_client() -> AsyncMock:
"""Mock client."""
return AsyncMock(spec=ModuleServerClient)
return AsyncMock(spec=ModuleStatusClient)


@pytest.fixture
Expand All @@ -30,10 +30,10 @@ def subject(mock_callback: AsyncMock, mock_client: AsyncMock) -> helpers.ModuleL


@pytest.fixture
def connections() -> List[models.Connection]:
def connections() -> List[models.ModuleConnection]:
"""Connection models."""
return [
models.Connection(
models.ModuleConnection(
url=f"url{i}", module_type=f"module_type{i}", identifier=f"identifier{i}"
)
for i in range(5)
Expand Down Expand Up @@ -65,7 +65,7 @@ async def test_handle_message_connected_empty(
async def test_handle_message_connected_one(
subject: helpers.ModuleListener,
mock_callback: AsyncMock,
connections: List[models.Connection],
connections: List[models.ModuleConnection],
modules_at_port: List[ModuleAtPort],
) -> None:
"""It should call the call back with the correct modules to add."""
Expand All @@ -77,7 +77,7 @@ async def test_handle_message_connected_one(
async def test_handle_message_connected_many(
subject: helpers.ModuleListener,
mock_callback: AsyncMock,
connections: List[models.Connection],
connections: List[models.ModuleConnection],
modules_at_port: List[ModuleAtPort],
) -> None:
"""It should call the call back with the correct modules to add."""
Expand All @@ -98,7 +98,7 @@ async def test_handle_message_disconnected_empty(
async def test_handle_message_disconnected_one(
subject: helpers.ModuleListener,
mock_callback: AsyncMock,
connections: List[models.Connection],
connections: List[models.ModuleConnection],
modules_at_port: List[ModuleAtPort],
) -> None:
"""It should call the call back with the correct modules to remove."""
Expand All @@ -110,7 +110,7 @@ async def test_handle_message_disconnected_one(
async def test_handle_message_disconnected_many(
subject: helpers.ModuleListener,
mock_callback: AsyncMock,
connections: List[models.Connection],
connections: List[models.ModuleConnection],
modules_at_port: List[ModuleAtPort],
) -> None:
"""It should call the call back with the correct modules to remove."""
Expand All @@ -131,7 +131,7 @@ async def test_handle_message_dump_empty(
async def test_handle_message_dump_one(
subject: helpers.ModuleListener,
mock_callback: AsyncMock,
connections: List[models.Connection],
connections: List[models.ModuleConnection],
modules_at_port: List[ModuleAtPort],
) -> None:
"""It should call the call back with the correct modules to load."""
Expand All @@ -143,7 +143,7 @@ async def test_handle_message_dump_one(
async def test_handle_message_dump_many(
subject: helpers.ModuleListener,
mock_callback: AsyncMock,
connections: List[models.Connection],
connections: List[models.ModuleConnection],
modules_at_port: List[ModuleAtPort],
) -> None:
"""It should call the call back with the correct modules to load."""
Expand Down
4 changes: 2 additions & 2 deletions api/tests/opentrons/hardware_control/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import pytest
import asyncio

from opentrons.hardware_control.emulation.module_server import ModuleServerClient
from opentrons.hardware_control.emulation.module_server import ModuleStatusClient
from opentrons.hardware_control.emulation.module_server.helpers import wait_emulators
from opentrons.hardware_control.emulation.scripts import run_app
from opentrons.hardware_control.emulation.types import ModuleType
Expand Down Expand Up @@ -35,7 +35,7 @@ def _run_app() -> None:
asyncio.run(run_app.run(emulator_settings, modules=[m.value for m in modules]))

async def _wait_ready() -> None:
c = await ModuleServerClient.connect(
c = await ModuleStatusClient.connect(
host="localhost",
port=emulator_settings.module_server.port,
interval_seconds=1,
Expand Down

0 comments on commit ea431f2

Please sign in to comment.