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

Automatically materialize USB and CAN mixins for compatibility #283

Merged
merged 1 commit into from
Aug 16, 2023
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
26 changes: 25 additions & 1 deletion electronics_abstract_parts/IoController.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from itertools import chain
from typing import List, Dict, Tuple, Type
from typing import List, Dict, Tuple, Type, Optional

from deprecated import deprecated

Expand All @@ -25,6 +25,14 @@ def __init__(self, *args, **kwargs) -> None:
self.i2c = self.Port(Vector(I2cController.empty()), optional=True)
self.uart = self.Port(Vector(UartPort.empty()), optional=True)

# USB and CAN are now mixins, but automatically materialized for compatibility and simplicity
# In new code, explicit mixin syntax should be used.
self.usb: Vector[UsbDevicePort]
self.can: Vector[CanControllerPort]
from electronics_abstract_parts import IoControllerUsb, IoControllerCan
self._usb_mixin: Optional[IoControllerUsb] = None
self._can_mixin: Optional[IoControllerCan] = None

self.spi_peripheral = self.Port(Vector(SpiPeripheral.empty()), optional=True)
self.i2c_target = self.Port(Vector(I2cTarget.empty()), optional=True)

Expand All @@ -33,6 +41,22 @@ def __init__(self, *args, **kwargs) -> None:
self._io_ports: List[BasePort] = [ # ordered by assignment order, most restrictive should be first
self.adc, self.spi, self.i2c, self.uart, self.spi_peripheral, self.i2c_target, self.gpio]

def __getattr__(self, item):
# automatically materialize USB and CAN mixins on abstract classes, only if this is IoController
# note, getattr ONLY called when the field does not exist, and hasattr is implemented via getattr
if self.__class__ is IoController and item == 'usb':
if self._usb_mixin is None:
from electronics_abstract_parts import IoControllerUsb
self._usb_mixin = self.with_mixin(IoControllerUsb())
return self._usb_mixin.usb
elif self.__class__ is IoController and item == 'can':
if self._can_mixin is None:
from electronics_abstract_parts import IoControllerCan
self._can_mixin = self.with_mixin(IoControllerCan())
return self._can_mixin.can
else:
raise AttributeError(item) # ideally we'd use super().__getattr__(...), but that's not defined in base classes

def _type_of_io(self, io_port: BasePort) -> Type[Port]:
if isinstance(io_port, Vector):
return io_port.elt_type()
Expand Down
7 changes: 3 additions & 4 deletions examples/test_can_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ def contents(self) -> None:
) as imp:
self.mcu = imp.Block(IoController())

(self.usb_esd, ), _ = self.chain(self.usb.usb, imp.Block(UsbEsdDiode()),
self.mcu.with_mixin(IoControllerUsb()).usb.request())
(self.xcvr, ), self.can_chain = self.chain(self.mcu.with_mixin(IoControllerCan()).can.request('can'),
imp.Block(Iso1050dub()))
# this uses the legacy / simple (non-mixin) USB and CAN IO style
(self.usb_esd, ), _ = self.chain(self.usb.usb, imp.Block(UsbEsdDiode()), self.mcu.usb.request())
(self.xcvr, ), self.can_chain = self.chain(self.mcu.can.request('can'), imp.Block(Iso1050dub()))

(self.sw_usb, ), _ = self.chain(imp.Block(DigitalSwitch()), self.mcu.gpio.request('sw_usb'))
(self.sw_can, ), _ = self.chain(imp.Block(DigitalSwitch()), self.mcu.gpio.request('sw_can'))
Expand Down
6 changes: 3 additions & 3 deletions examples/test_datalogger.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ def contents(self) -> None:
) as imp:
self.mcu = imp.Block(IoController())

self.connect(self.mcu.with_mixin(IoControllerUsb()).usb.request(), self.usb_conn.usb)
# this uses the legacy / simple (non-mixin) USB and CAN IO style
self.connect(self.mcu.usb.request(), self.usb_conn.usb)

(self.can, ), _ = self.chain(self.mcu.with_mixin(IoControllerCan()).can.request('can'),
imp.Block(CalSolCanBlock()))
(self.can, ), _ = self.chain(self.mcu.can.request('can'), imp.Block(CalSolCanBlock()))

# TODO need proper support for exported unconnected ports
self.can_gnd_load = self.Block(DummyVoltageSink())
Expand Down
Loading