Skip to content

Commit

Permalink
Ensure sketch is compatible with version of Rapiduino
Browse files Browse the repository at this point in the history
  • Loading branch information
samwedge committed Feb 12, 2021
1 parent 7e6934d commit 2e991fa
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 14 deletions.
17 changes: 17 additions & 0 deletions rapiduino/boards/arduino.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
)
from rapiduino.communication.serial import SerialConnection
from rapiduino.exceptions import (
ArduinoSketchVersionIncompatibleError,
ComponentAlreadyRegisteredError,
NotAnalogPinError,
NotPwmPinError,
Expand All @@ -33,6 +34,9 @@


class Arduino:

min_version = (0, 1, 0)

def __init__(
self,
pins: Tuple[Pin, ...],
Expand All @@ -45,6 +49,7 @@ def __init__(
self.connection = conn_class.build(port)
self.pin_register: Dict[int, str] = {}
self.reserved_pin_nums = (rx_pin, tx_pin)
self._assert_compatible_sketch_version()

@classmethod
def uno(
Expand Down Expand Up @@ -138,6 +143,18 @@ def deregister_component(self, component_token: str) -> None:
for key in keys_to_delete:
del self.pin_register[key]

def _assert_compatible_sketch_version(self) -> None:
version = self.version()
if any(
(
version[0] > self.min_version[0],
version[0] < self.min_version[0],
version[1] < self.min_version[1],
version[2] < self.min_version[2],
)
):
raise ArduinoSketchVersionIncompatibleError(version, self.min_version)

def _assert_requested_pins_are_valid(
self, component_token: str, pins: Tuple[Pin, ...]
) -> None:
Expand Down
23 changes: 22 additions & 1 deletion rapiduino/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from typing import Optional
from typing import Optional, Tuple

import rapiduino


class SerialConnectionSendDataError(Exception):
Expand Down Expand Up @@ -81,3 +83,22 @@ class ComponentAlreadyRegisteredWithArduinoError(Exception):
def __init__(self) -> None:
message = "Device is already registered to an Arduino"
super().__init__(message)


class ArduinoSketchVersionIncompatibleError(Exception):
def __init__(
self, sketch_version: Tuple[int, ...], min_version: Tuple[int, int, int]
) -> None:
sketch_version_str = (
f"{sketch_version[0]}.{sketch_version[1]}.{sketch_version[2]}"
)
min_version_str = f"{min_version[0]}.{min_version[1]}.{min_version[2]}"
max_version_str = f"{min_version[0] + 1}.0.0"

message = (
f"Arduino sketch version {sketch_version_str} is incompatible with"
f" Rapiduino version {rapiduino.__version__}.\n"
"Please upload a compatible sketch version:"
f" Greater or equal to {min_version_str}, less than {max_version_str}"
)
super().__init__(message)
92 changes: 80 additions & 12 deletions tests/test_boards/test_arduino.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
)
from rapiduino.communication.serial import SerialConnection
from rapiduino.exceptions import (
ArduinoSketchVersionIncompatibleError,
ComponentAlreadyRegisteredError,
NotAnalogPinError,
NotPwmPinError,
Expand All @@ -32,16 +33,15 @@
from rapiduino.globals.common import HIGH, INPUT, LOW, OUTPUT


@pytest.fixture
def test_arduino() -> Arduino:
def get_mock_conn_class() -> Mock:
def dummy_process_command(command: CommandSpec, *args: int) -> Tuple[int, ...]:
data: Tuple[int, ...]
if command == CMD_POLL:
data = (1,)
elif command == CMD_PARROT:
data = (args[0],)
elif command == CMD_VERSION:
data = (1, 2, 3)
data = Arduino.min_version
elif command == CMD_PINMODE:
data = ()
elif command == CMD_DIGITALREAD:
Expand All @@ -56,6 +56,16 @@ def dummy_process_command(command: CommandSpec, *args: int) -> Tuple[int, ...]:
raise ValueError(f"Mock Arduino does not know how to process {CommandSpec}")
return data

mock_conn_class = Mock(spec=SerialConnection)
mock_conn_class.build.return_value.process_command.side_effect = (
dummy_process_command
)

return mock_conn_class


@pytest.fixture
def test_arduino() -> Arduino:
pins = (
Pin(0),
Pin(1, is_analog=True),
Expand All @@ -64,9 +74,61 @@ def dummy_process_command(command: CommandSpec, *args: int) -> Tuple[int, ...]:
Pin(4),
Pin(5),
)
return Arduino(
pins=pins, port="", conn_class=get_mock_conn_class(), rx_pin=4, tx_pin=5
)


def test_if_sketch_version_is_major_change_below_minimum_required_version() -> None:
conn_class = Mock(spec=SerialConnection)
v = Arduino.min_version
sketch_version = (v[0] - 1, v[1], v[2])
conn_class.build.return_value.process_command.return_value = sketch_version
with pytest.raises(ArduinoSketchVersionIncompatibleError):
Arduino(pins=(), port="", conn_class=conn_class)


def test_if_sketch_version_is_minor_change_below_minimum_required_version() -> None:
conn_class = Mock(spec=SerialConnection)
v = Arduino.min_version
sketch_version = (v[0], v[1] - 1, v[2])
conn_class.build.return_value.process_command.return_value = sketch_version
with pytest.raises(ArduinoSketchVersionIncompatibleError):
Arduino(pins=(), port="", conn_class=conn_class)


def test_if_sketch_version_is_micro_change_below_minimum_required_version() -> None:
conn_class = Mock(spec=SerialConnection)
v = Arduino.min_version
sketch_version = (v[0], v[1], v[2] - 1)
conn_class.build.return_value.process_command.return_value = sketch_version
with pytest.raises(ArduinoSketchVersionIncompatibleError):
Arduino(pins=(), port="", conn_class=conn_class)


def test_if_sketch_version_is_a_major_change_above_required_version() -> None:
conn_class = Mock(spec=SerialConnection)
v = Arduino.min_version
sketch_version = (v[0] + 1, v[1], v[2])
conn_class.build.return_value.process_command.return_value = sketch_version
with pytest.raises(ArduinoSketchVersionIncompatibleError):
Arduino(pins=(), port="", conn_class=conn_class)


def test_if_sketch_version_is_a_minor_change_above_required_version() -> None:
conn_class = Mock(spec=SerialConnection)
v = Arduino.min_version
sketch_version = (v[0], v[1] + 1, v[2])
conn_class.build.return_value.process_command.return_value = sketch_version
Arduino(pins=(), port="", conn_class=conn_class)


def test_if_sketch_version_is_a_micro_change_above_required_version() -> None:
conn_class = Mock(spec=SerialConnection)
conn_class.build.return_value.process_command.side_effect = dummy_process_command
return Arduino(pins=pins, port="", conn_class=conn_class, rx_pin=4, tx_pin=5)
v = Arduino.min_version
sketch_version = (v[0], v[1], v[2] + 1)
conn_class.build.return_value.process_command.return_value = sketch_version
Arduino(pins=(), port="", conn_class=conn_class)


def test_poll(test_arduino: Arduino) -> None:
Expand All @@ -79,7 +141,7 @@ def test_parrot(test_arduino: Arduino) -> None:


def test_version(test_arduino: Arduino) -> None:
assert test_arduino.version() == (1, 2, 3)
assert test_arduino.version() == Arduino.min_version


def test_pin_mode_with_valid_args(test_arduino: Arduino) -> None:
Expand Down Expand Up @@ -207,9 +269,15 @@ def test_mega_pin_ids_are_sequential() -> None:
@pytest.mark.parametrize(
"arduino,expected_pins",
[
pytest.param(Arduino.uno(port="", conn_class=Mock()), get_uno_pins()),
pytest.param(Arduino.nano(port="", conn_class=Mock()), get_nano_pins()),
pytest.param(Arduino.mega(port="", conn_class=Mock()), get_mega_pins()),
pytest.param(
Arduino.uno(port="", conn_class=get_mock_conn_class()), get_uno_pins()
),
pytest.param(
Arduino.nano(port="", conn_class=get_mock_conn_class()), get_nano_pins()
),
pytest.param(
Arduino.mega(port="", conn_class=get_mock_conn_class()), get_mega_pins()
),
],
)
def test_classmethods_set_correct_pins(
Expand Down Expand Up @@ -253,9 +321,9 @@ def test_all_analog_pins_have_an_alias(
@pytest.mark.parametrize(
"arduino",
[
pytest.param(Arduino.uno(port="", conn_class=Mock())),
pytest.param(Arduino.nano(port="", conn_class=Mock())),
pytest.param(Arduino.mega(port="", conn_class=Mock())),
pytest.param(Arduino.uno(port="", conn_class=get_mock_conn_class())),
pytest.param(Arduino.nano(port="", conn_class=get_mock_conn_class())),
pytest.param(Arduino.mega(port="", conn_class=get_mock_conn_class())),
],
)
def test_serial_comms_pins_cannot_be_used(arduino: Arduino) -> None:
Expand Down
17 changes: 16 additions & 1 deletion tests/test_components/test_base.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from typing import Tuple
from unittest.mock import Mock, call

import pytest
Expand All @@ -10,6 +11,8 @@
CMD_DIGITALREAD,
CMD_DIGITALWRITE,
CMD_PINMODE,
CMD_VERSION,
CommandSpec,
)
from rapiduino.communication.serial import SerialConnection
from rapiduino.components.base_component import BaseComponent
Expand All @@ -27,8 +30,14 @@

@pytest.fixture
def serial() -> Mock:
def mock_process_command(command: CommandSpec, *args: int) -> Tuple[int, ...]:
if command == CMD_VERSION:
return Arduino.min_version
else:
return tuple([1])

mock = Mock(spec=SerialConnection)
mock.build.return_value.process_command.return_value = (1,)
mock.build.return_value.process_command.side_effect = mock_process_command
return mock


Expand Down Expand Up @@ -84,6 +93,7 @@ def test_component_connect_runs_setup(

calls = serial.build.return_value.process_command.call_args_list
expected_calls = [
call(CMD_VERSION),
call(CMD_PINMODE, DIGITAL_PIN_NUM, INPUT.value),
call(CMD_DIGITALREAD, DIGITAL_PIN_NUM),
call(CMD_DIGITALWRITE, DIGITAL_PIN_NUM, HIGH.value),
Expand Down Expand Up @@ -133,6 +143,7 @@ def test_pin_mode_if_connected(

calls = serial.build.return_value.process_command.call_args_list
expected_calls = [
call(CMD_VERSION),
call(CMD_PINMODE, DIGITAL_PIN_NUM, INPUT.value),
call(CMD_DIGITALREAD, DIGITAL_PIN_NUM),
call(CMD_DIGITALWRITE, DIGITAL_PIN_NUM, HIGH.value),
Expand All @@ -151,6 +162,7 @@ def test_digital_read_if_connected(

calls = serial.build.return_value.process_command.call_args_list
expected_calls = [
call(CMD_VERSION),
call(CMD_PINMODE, DIGITAL_PIN_NUM, INPUT.value),
call(CMD_DIGITALREAD, DIGITAL_PIN_NUM),
call(CMD_DIGITALWRITE, DIGITAL_PIN_NUM, HIGH.value),
Expand All @@ -169,6 +181,7 @@ def test_digital_write_if_connected(

calls = serial.build.return_value.process_command.call_args_list
expected_calls = [
call(CMD_VERSION),
call(CMD_PINMODE, DIGITAL_PIN_NUM, INPUT.value),
call(CMD_DIGITALREAD, DIGITAL_PIN_NUM),
call(CMD_DIGITALWRITE, DIGITAL_PIN_NUM, HIGH.value),
Expand All @@ -187,6 +200,7 @@ def test_analog_read_if_connected(

calls = serial.build.return_value.process_command.call_args_list
expected_calls = [
call(CMD_VERSION),
call(CMD_PINMODE, DIGITAL_PIN_NUM, INPUT.value),
call(CMD_DIGITALREAD, DIGITAL_PIN_NUM),
call(CMD_DIGITALWRITE, DIGITAL_PIN_NUM, HIGH.value),
Expand All @@ -205,6 +219,7 @@ def test_analog_write_if_connected(

calls = serial.build.return_value.process_command.call_args_list
expected_calls = [
call(CMD_VERSION),
call(CMD_PINMODE, DIGITAL_PIN_NUM, INPUT.value),
call(CMD_DIGITALREAD, DIGITAL_PIN_NUM),
call(CMD_DIGITALWRITE, DIGITAL_PIN_NUM, HIGH.value),
Expand Down

0 comments on commit 2e991fa

Please sign in to comment.