diff --git a/docs/quickstart.md b/docs/quickstart.md index fcf1da4..d2cfc6c 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -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 diff --git a/rapiduino/components/base.py b/rapiduino/components/base.py index 5692595..24b3291 100644 --- a/rapiduino/components/base.py +++ b/rapiduino/components/base.py @@ -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 diff --git a/rapiduino/components/led.py b/rapiduino/components/led.py index 367dd95..9cec5f0 100644 --- a/rapiduino/components/led.py +++ b/rapiduino/components/led.py @@ -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) diff --git a/rapiduino/exceptions.py b/rapiduino/exceptions.py index 2344312..778e546 100644 --- a/rapiduino/exceptions.py +++ b/rapiduino/exceptions.py @@ -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""" diff --git a/tests/test_components/test_base.py b/tests/test_components/test_base.py index b7b52d7..05ad36d 100644 --- a/tests/test_components/test_base.py +++ b/tests/test_components/test_base.py @@ -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 @@ -35,6 +39,16 @@ 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) @@ -42,18 +56,32 @@ def _setup(self) -> None: 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), @@ -73,6 +101,21 @@ 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: @@ -80,3 +123,128 @@ def test_component_disconnect_deregisters_component( 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) diff --git a/tests/test_components/test_led.py b/tests/test_components/test_led.py index f05318b..190b247 100644 --- a/tests/test_components/test_led.py +++ b/tests/test_components/test_led.py @@ -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: