Skip to content
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

Allow disjoing components #73

Merged
merged 3 commits into from
Aug 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/configs/iobox.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- examples.devices.iobox.IoBox:
name: MrBox
inputs: {}
100 changes: 100 additions & 0 deletions examples/devices/iobox.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from dataclasses import dataclass

from tickit.adapters.composed import ComposedAdapter
from tickit.adapters.interpreters.command import CommandInterpreter, RegexCommand
from tickit.adapters.servers.tcp import TcpServer
from tickit.core.adapter import Server
from tickit.core.components.component import Component, ComponentConfig
from tickit.core.components.device_simulation import DeviceSimulation
from tickit.core.device import Device, DeviceUpdate
from tickit.core.typedefs import SimTime
from tickit.utils.byte_format import ByteFormat
from tickit.utils.compat.typing_compat import TypedDict


class IoBoxDevice(Device):
"""An isolated toy device which stores a message.

The device takes no inputs and produces no outputs. It interacts exclusively with
an adapter which sets incoming messages to it and reads stored messages from it.
"""

#: An empty typed mapping of device inputs
Inputs: TypedDict = TypedDict("Inputs", {})
#: A typed mapping containing the current output value
Outputs: TypedDict = TypedDict("Outputs", {})

def __init__(self, inital_value: str = "Hello") -> None:
"""The IoBox constructor, sets intial message value."""
self.message = inital_value

def update(self, time: SimTime, inputs: Inputs) -> DeviceUpdate[Outputs]:
"""The update method which produces an output mapping containing the observed value.

For this device the DeviceUpdate produces no outputs.

Args:
time (SimTime): The current simulation time (in nanoseconds).
inputs (State): A mapping of inputs to the device and their values.

Returns:
DeviceUpdate[Outputs]:
The produced update event which contains the observed value, in this
case nothing is provided. The device never requests a callback.
"""
return DeviceUpdate(IoBoxDevice.Outputs(), None)


class IOBoxAdapater(ComposedAdapter):
"""A Composed adapter for setting and getting the device property."""

device: IoBoxDevice

def __init__(
self,
server: Server,
) -> None:
"""Constructor of the IoBoc Adapter, builds the configured server.

Args:
server (Server): The immutable data container used to configure a
server.
"""
super().__init__(
server,
CommandInterpreter(),
)

@RegexCommand(r"m=([a-zA-Z0-9_!.?-]+)", interrupt=True, format="utf-8")
async def set_message(self, value: str) -> None:
"""Regex string command which sets the value of the message.

Args:
value (str): The new value of the message.
"""
self.device.message = value

@RegexCommand(r"m\?", format="utf-8")
async def get_message(self) -> bytes:
"""Regex string command which returns the utf-8 encoded value of the message.

Returns:
bytes: The utf-8 encoded value of the message.
"""
return str(self.device.message).encode("utf-8")


@dataclass
class IoBox(ComponentConfig):
"""IO Box you can talk to over TCP."""

host: str = "localhost"
port: int = 25565
format: ByteFormat = ByteFormat(b"%b\r\n")

def __call__(self) -> Component: # noqa: D102
return DeviceSimulation(
name=self.name,
device=IoBoxDevice(),
adapters=[IOBoxAdapater(TcpServer(self.host, self.port, self.format))],
)
4 changes: 4 additions & 0 deletions tests/core/management/test_event_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@ def wiring_struct():
ComponentID("Mid1"): {
PortID("Mid1>1"): {ComponentPort(ComponentID("In1"), PortID("In1<1"))}
},
ComponentID("In1"): {},
}


@pytest.fixture
def inverse_wiring_struct():
return {
ComponentID("Out1"): {},
ComponentID("Out2"): {},
ComponentID("Mid1"): {
PortID("Mid1<1"): ComponentPort(ComponentID("Out1"), PortID("Out1>1")),
PortID("Mid1<2"): ComponentPort(ComponentID("Out2"), PortID("Out2>1")),
Expand Down Expand Up @@ -136,6 +139,7 @@ def test_event_router_output_components(event_router: EventRouter):

def test_event_router_component_tree(event_router: EventRouter):
assert {
"In1": set(),
"Out1": {"Mid1"},
"Out2": {"Mid1", "In1"},
"Mid1": {"In1"},
Expand Down
6 changes: 4 additions & 2 deletions tickit/core/management/event_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def from_inverse_wiring(cls, inverse_wiring: "InverseWiring") -> "Wiring":
"""
wiring: Wiring = cls()
for in_dev, in_ios in inverse_wiring.items():
wiring[in_dev]
for in_io, (out_dev, out_io) in in_ios.items():
wiring[out_dev][out_io].add(ComponentPort(in_dev, in_io))
return wiring
Expand All @@ -82,7 +83,7 @@ def __init__(self, wiring: Optional[Inverse_Wiring_Struct] = None) -> None:
Defaults to None.
"""
_wiring = (
{dev: DefaultDict(None, io) for dev, io in wiring.items() if io}
{dev: DefaultDict(None, io) for dev, io in wiring.items()}
if wiring
else dict()
)
Expand All @@ -101,6 +102,7 @@ def from_wiring(cls, wiring: Wiring) -> "InverseWiring":
"""
inverse_wiring: InverseWiring = cls()
for out_dev, out_ids in wiring.items():
inverse_wiring[out_dev]
for out_io, ports in out_ids.items():
for in_dev, in_io in ports:
inverse_wiring[in_dev][in_io] = ComponentPort(out_dev, out_io)
Expand Down Expand Up @@ -174,7 +176,7 @@ def output_components(self) -> Set[ComponentID]:
Returns:
Set[ComponentID]: A set of components which provide outputs.
"""
return set(self.wiring.keys())
return {component for component, port in self.wiring.items() if port}

@cached_property
def input_components(self) -> Set[ComponentID]:
Expand Down