Skip to content

Commit

Permalink
Reconfigure BaseComponent to allow custom init in subclasses
Browse files Browse the repository at this point in the history
  • Loading branch information
samwedge committed Feb 5, 2021
1 parent e38b8ce commit 1751c43
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 19 deletions.
2 changes: 1 addition & 1 deletion docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ to the component. Let's look at an example with an LED:

```python
from rapiduino.components.led import LED
led = LED.create(arduino, 13)
led = LED(arduino, 13)
```

This creates an LED object and registers it to the arduino against pin 13. When binding, the code automatically
Expand Down
57 changes: 50 additions & 7 deletions rapiduino/components/base.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,81 @@
import uuid
from abc import ABC, abstractmethod
from typing import Tuple
from typing import Optional, Tuple

from rapiduino.boards.arduino import Arduino
from rapiduino.boards.pins import Pin
from rapiduino.exceptions import (
ComponentAlreadyRegisteredWithArduinoError,
ComponentNotRegisteredWithArduinoError,
)
from rapiduino.globals.common import PinMode, PinState


class BaseComponent(ABC):
def __init__(self, board: Arduino, pins: Tuple[Pin, ...]) -> None:
self._pins = pins
self.__board = board
self.__token = uuid.uuid4().hex
_board: Arduino
_pins: Tuple[Pin, ...]
__board: Optional[Arduino] = None
__pins: Optional[Tuple[Pin, ...]] = None
__token: Optional[str] = None

def connect(self) -> None:
self.__board.register_component(self.__token, self._pins)
if self.__board is not None and self.__pins is not None:
raise ComponentAlreadyRegisteredWithArduinoError(
"Device is already registered to an Arduino"
)
self.__token = uuid.uuid4().hex
self.__board = self._board
self.__pins = self._pins
self.__board.register_component(self.__token, self.__pins)
self._setup()

def disconnect(self) -> None:
self.__board.deregister_component(self.__token)
if self.__token is None:
raise ComponentNotRegisteredWithArduinoError(
"Device must be registered to an Arduino"
)
self._board.deregister_component(self.__token)
self.__token = None

def set_pins(self, *pins: Pin) -> None:
self._pins = pins

def set_board(self, board: Arduino) -> None:
self._board = board

def _pin_mode(self, pin_no: int, mode: PinMode) -> None:
if self.__board is None or self.__pins is None:
raise ComponentNotRegisteredWithArduinoError(
"Device must be registered to an Arduino"
)
self.__board.pin_mode(pin_no, mode, self.__token)

def _digital_read(self, pin_no: int) -> PinState:
if self.__board is None or self.__pins is None:
raise ComponentNotRegisteredWithArduinoError(
"Device must be registered to an Arduino"
)
return self.__board.digital_read(pin_no, self.__token)

def _digital_write(self, pin_no: int, state: PinState) -> None:
if self.__board is None or self.__pins is None:
raise ComponentNotRegisteredWithArduinoError(
"Device must be registered to an Arduino"
)
self.__board.digital_write(pin_no, state, self.__token)

def _analog_read(self, pin_no: int) -> int:
if self.__board is None or self.__pins is None:
raise ComponentNotRegisteredWithArduinoError(
"Device must be registered to an Arduino"
)
return self.__board.analog_read(pin_no, self.__token)

def _analog_write(self, pin_no: int, value: int) -> None:
if self.__board is None or self.__pins is None:
raise ComponentNotRegisteredWithArduinoError(
"Device must be registered to an Arduino"
)
self.__board.analog_write(pin_no, value, self.__token)

@abstractmethod
Expand Down
10 changes: 4 additions & 6 deletions rapiduino/components/led.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@


class LED(BaseComponent):
@classmethod
def create(cls, board: Arduino, anode_pin_no: int) -> "LED":
pins = (Pin(anode_pin_no),)
led = cls(board, pins)
led.connect()
return led
def __init__(self, board: Arduino, anode_pin_no: int) -> None:
self.set_pins(Pin(pin_id=anode_pin_no))
self.set_board(board)
self.connect()

def _setup(self) -> None:
self._pin_mode(self._pins[0].pin_id, OUTPUT)
Expand Down
10 changes: 10 additions & 0 deletions rapiduino/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,13 @@ class PinDoesNotExistError(RapiduinoError):
class ProtectedPinError(RapiduinoError):
"""The action cannot be completed because the specified pin is registered to a
component"""


class ComponentNotRegisteredWithArduinoError(RapiduinoError):
"""Action cannot be performed because the component is not registered to an
Arduino"""


class ComponentAlreadyRegisteredWithArduinoError(RapiduinoError):
"""Action cannot be performed because the component is already registered
to an Arduino"""
176 changes: 172 additions & 4 deletions tests/test_components/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
)
from rapiduino.communication.serial import SerialConnection
from rapiduino.components.base import BaseComponent
from rapiduino.globals.common import HIGH, INPUT
from rapiduino.exceptions import (
ComponentAlreadyRegisteredWithArduinoError,
ComponentNotRegisteredWithArduinoError,
)
from rapiduino.globals.common import HIGH, INPUT, PinMode, PinState

DIGITAL_PIN_NUM = 2
PWM_PIN_NUM = 3
Expand All @@ -35,25 +39,49 @@ def arduino(serial: Mock) -> Arduino:


class DummyComponent(BaseComponent):
def __init__(
self, arduino: Arduino, digital_pin_no: int, pwm_pin_no: int, analog_pin_no: int
) -> None:
self.set_pins(
Pin(digital_pin_no),
Pin(pwm_pin_no, is_pwm=True),
Pin(analog_pin_no, is_analog=True),
)
self.set_board(arduino)

def _setup(self) -> None:
self._pin_mode(DIGITAL_PIN_NUM, INPUT)
self._digital_read(DIGITAL_PIN_NUM)
self._digital_write(DIGITAL_PIN_NUM, HIGH)
self._analog_read(ANALOG_PIN_NUM)
self._analog_write(PWM_PIN_NUM, ANALOG_WRITE_VALUE)

def pin_mode(self, pin_no: int, mode: PinMode) -> None:
self._pin_mode(pin_no, mode)

def digital_read(self, pin_no: int) -> PinState:
return self._digital_read(pin_no)

def digital_write(self, pin_no: int, state: PinState) -> None:
self._digital_write(pin_no, state)

def analog_read(self, pin_no: int) -> int:
return self._analog_read(pin_no)

def analog_write(self, pin_no: int, value: int) -> None:
self._analog_write(pin_no, value)


@pytest.fixture
def dummy_component(arduino: Arduino) -> DummyComponent:
return DummyComponent(
arduino, (Pin(DIGITAL_PIN_NUM), Pin(PWM_PIN_NUM), Pin(ANALOG_PIN_NUM))
)
return DummyComponent(arduino, DIGITAL_PIN_NUM, PWM_PIN_NUM, ANALOG_PIN_NUM)


def test_component_connect_runs_setup(
serial: Mock, dummy_component: DummyComponent
) -> None:
dummy_component.connect()

calls = serial.build.return_value.process_command.call_args_list
expected_calls = [
call(CMD_PINMODE, DIGITAL_PIN_NUM, INPUT.value),
Expand All @@ -73,10 +101,150 @@ def test_component_connect_registers_component(
assert len(set(arduino.pin_register.values())) == 1


def test_component_connect_cannot_be_done_twice(
dummy_component: DummyComponent, arduino: Arduino
) -> None:
dummy_component.connect()
with pytest.raises(ComponentAlreadyRegisteredWithArduinoError):
dummy_component.connect()


def test_component_disconnect_cannot_be_done_if_not_connected(
dummy_component: DummyComponent, arduino: Arduino
) -> None:
with pytest.raises(ComponentNotRegisteredWithArduinoError):
dummy_component.disconnect()


def test_component_disconnect_deregisters_component(
serial: Mock, dummy_component: DummyComponent, arduino: Arduino
) -> None:
dummy_component.connect()
dummy_component.disconnect()

assert len(arduino.pin_register.values()) == 0


def test_pin_mode_if_connected(
serial: Mock, dummy_component: DummyComponent, arduino: Arduino
) -> None:
dummy_component.connect()
dummy_component.pin_mode(DIGITAL_PIN_NUM, INPUT)

calls = serial.build.return_value.process_command.call_args_list
expected_calls = [
call(CMD_PINMODE, DIGITAL_PIN_NUM, INPUT.value),
call(CMD_DIGITALREAD, DIGITAL_PIN_NUM),
call(CMD_DIGITALWRITE, DIGITAL_PIN_NUM, HIGH.value),
call(CMD_ANALOGREAD, ANALOG_PIN_NUM),
call(CMD_ANALOGWRITE, PWM_PIN_NUM, ANALOG_WRITE_VALUE),
call(CMD_PINMODE, DIGITAL_PIN_NUM, INPUT.value),
]
assert calls == expected_calls


def test_digital_read_if_connected(
serial: Mock, dummy_component: DummyComponent, arduino: Arduino
) -> None:
dummy_component.connect()
dummy_component.digital_read(DIGITAL_PIN_NUM)

calls = serial.build.return_value.process_command.call_args_list
expected_calls = [
call(CMD_PINMODE, DIGITAL_PIN_NUM, INPUT.value),
call(CMD_DIGITALREAD, DIGITAL_PIN_NUM),
call(CMD_DIGITALWRITE, DIGITAL_PIN_NUM, HIGH.value),
call(CMD_ANALOGREAD, ANALOG_PIN_NUM),
call(CMD_ANALOGWRITE, PWM_PIN_NUM, ANALOG_WRITE_VALUE),
call(CMD_DIGITALREAD, DIGITAL_PIN_NUM),
]
assert calls == expected_calls


def test_digital_write_if_connected(
serial: Mock, dummy_component: DummyComponent, arduino: Arduino
) -> None:
dummy_component.connect()
dummy_component.digital_write(DIGITAL_PIN_NUM, HIGH)

calls = serial.build.return_value.process_command.call_args_list
expected_calls = [
call(CMD_PINMODE, DIGITAL_PIN_NUM, INPUT.value),
call(CMD_DIGITALREAD, DIGITAL_PIN_NUM),
call(CMD_DIGITALWRITE, DIGITAL_PIN_NUM, HIGH.value),
call(CMD_ANALOGREAD, ANALOG_PIN_NUM),
call(CMD_ANALOGWRITE, PWM_PIN_NUM, ANALOG_WRITE_VALUE),
call(CMD_DIGITALWRITE, DIGITAL_PIN_NUM, HIGH.value),
]
assert calls == expected_calls


def test_analog_read_if_connected(
serial: Mock, dummy_component: DummyComponent, arduino: Arduino
) -> None:
dummy_component.connect()
dummy_component.analog_read(ANALOG_PIN_NUM)

calls = serial.build.return_value.process_command.call_args_list
expected_calls = [
call(CMD_PINMODE, DIGITAL_PIN_NUM, INPUT.value),
call(CMD_DIGITALREAD, DIGITAL_PIN_NUM),
call(CMD_DIGITALWRITE, DIGITAL_PIN_NUM, HIGH.value),
call(CMD_ANALOGREAD, ANALOG_PIN_NUM),
call(CMD_ANALOGWRITE, PWM_PIN_NUM, ANALOG_WRITE_VALUE),
call(CMD_ANALOGREAD, ANALOG_PIN_NUM),
]
assert calls == expected_calls


def test_analog_write_if_connected(
serial: Mock, dummy_component: DummyComponent, arduino: Arduino
) -> None:
dummy_component.connect()
dummy_component.analog_write(PWM_PIN_NUM, ANALOG_WRITE_VALUE)

calls = serial.build.return_value.process_command.call_args_list
expected_calls = [
call(CMD_PINMODE, DIGITAL_PIN_NUM, INPUT.value),
call(CMD_DIGITALREAD, DIGITAL_PIN_NUM),
call(CMD_DIGITALWRITE, DIGITAL_PIN_NUM, HIGH.value),
call(CMD_ANALOGREAD, ANALOG_PIN_NUM),
call(CMD_ANALOGWRITE, PWM_PIN_NUM, ANALOG_WRITE_VALUE),
call(CMD_ANALOGWRITE, PWM_PIN_NUM, ANALOG_WRITE_VALUE),
]
assert calls == expected_calls


def test_pin_mode_cannot_be_done_if_not_connected(
serial: Mock, dummy_component: DummyComponent, arduino: Arduino
) -> None:
with pytest.raises(ComponentNotRegisteredWithArduinoError):
dummy_component.pin_mode(DIGITAL_PIN_NUM, INPUT)


def test_digital_read_cannot_be_done_if_not_connected(
serial: Mock, dummy_component: DummyComponent, arduino: Arduino
) -> None:
with pytest.raises(ComponentNotRegisteredWithArduinoError):
dummy_component.digital_read(DIGITAL_PIN_NUM)


def test_digital_write_cannot_be_done_if_not_connected(
serial: Mock, dummy_component: DummyComponent, arduino: Arduino
) -> None:
with pytest.raises(ComponentNotRegisteredWithArduinoError):
dummy_component.digital_write(DIGITAL_PIN_NUM, HIGH)


def test_analog_read_cannot_be_done_if_not_connected(
serial: Mock, dummy_component: DummyComponent, arduino: Arduino
) -> None:
with pytest.raises(ComponentNotRegisteredWithArduinoError):
dummy_component.analog_read(DIGITAL_PIN_NUM)


def test_analog_write_cannot_be_done_if_not_connected(
serial: Mock, dummy_component: DummyComponent, arduino: Arduino
) -> None:
with pytest.raises(ComponentNotRegisteredWithArduinoError):
dummy_component.analog_write(DIGITAL_PIN_NUM, ANALOG_WRITE_VALUE)
2 changes: 1 addition & 1 deletion tests/test_components/test_led.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def arduino() -> Mock:

@pytest.fixture
def led(arduino: Arduino) -> LED:
return LED.create(arduino, PIN_NUM)
return LED(arduino, PIN_NUM)


def test_setup(arduino: Mock, led: LED) -> None:
Expand Down

0 comments on commit 1751c43

Please sign in to comment.