Skip to content
This repository has been archived by the owner on Jan 25, 2024. It is now read-only.

Arduino Uno Hardware Backend #307

Merged
merged 26 commits into from
Jul 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9d04b52
Refactor serial logic into AbstractSerialBackend
trickeydan Jun 9, 2019
0effdf6
Refactor raw_usb backend to match the serial backend.
trickeydan Jun 12, 2019
765f875
Implement discovery of Arduino
trickeydan Jun 12, 2019
aa5318f
Check the firmware version and get the serial num
trickeydan Jun 12, 2019
f7ba1d2
Apply suggestions from code review
trickeydan Jun 26, 2019
9d61ce4
Merge branch 'master' into uno-hardware
kierdavis Jul 13, 2019
6768cd7
Use same name for mocked-out arguments
kierdavis Jul 13, 2019
9074abf
Add test for creating a ArduinoUnoHardwareBackend
kierdavis Jul 13, 2019
e31ffd6
Refactor MockSerial to not require overriding the constructor
kierdavis Jul 13, 2019
9d8e147
Refactor MockSerial for clarity
kierdavis Jul 13, 2019
7c367ee
Improve arduino hardware backend initialisation test
kierdavis Jul 13, 2019
233f52f
Add test for version number check
kierdavis Jul 13, 2019
6ef1492
Fix version number check
kierdavis Jul 13, 2019
b7b3613
Use a timedelta for timeout in SerialHardwardBackend constructor
kierdavis Jul 13, 2019
2f2e805
Add isort command to makefile
kierdavis Jul 13, 2019
79d17b3
Change order of tasks in Makefile
kierdavis Jul 13, 2019
31f5d50
Update error message
kierdavis Jul 13, 2019
47bf90d
Implement all the things
kierdavis Jul 13, 2019
cbe07db
Big boi rename
kierdavis Jul 13, 2019
49c6937
Merge branch 'master' into uno-hardware
kierdavis Jul 13, 2019
1f1a282
Fix arduino analogue syntax
kierdavis Jul 13, 2019
25375cf
Whitespace cleanup
kierdavis Jul 13, 2019
c64dc81
Add test script for arduino
kierdavis Jul 13, 2019
f1cc862
Merge remote-tracking branch 'origin/master' into uno-hardware
trickeydan Jul 17, 2019
e932029
Update exception types
kierdavis Jul 18, 2019
fdaaa76
Use repr for debugging values in exception messages
kierdavis Jul 18, 2019
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
1 change: 0 additions & 1 deletion j5/backends/console/arduino/__init__.py

This file was deleted.

1 change: 1 addition & 0 deletions j5/backends/console/sb/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Backends for SourceBots boards in the Console Environment."""
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""Console Backend for the Arduino Uno."""
"""Console Backend for the SourceBots Arduino."""
from typing import Mapping, Optional, Set, Type

from j5.backends import Backend
from j5.backends.console import Console, ConsoleEnvironment
from j5.boards import Board
from j5.boards.arduino import ArduinoUnoBoard
from j5.boards.sb import SBArduinoBoard
from j5.components import GPIOPinInterface, GPIOPinMode, LEDInterface


Expand All @@ -19,11 +19,11 @@ def __init__(self, *, mode: GPIOPinMode, digital_state: bool):
self.digital_state = digital_state


class ArduinoUnoConsoleBackend(GPIOPinInterface, LEDInterface, Backend):
"""Console Backend for the Arduino Uno."""
class SBArduinoConsoleBackend(GPIOPinInterface, LEDInterface, Backend):
"""Console Backend for the SourceBots Arduino."""

environment = ConsoleEnvironment
board = ArduinoUnoBoard
board = SBArduinoBoard

@classmethod
def discover(cls) -> Set[Board]:
Expand Down Expand Up @@ -97,7 +97,7 @@ def write_gpio_pin_dac_value(self, identifier: int, scaled_value: float) -> None

def write_gpio_pin_pwm_value(self, identifier: int, duty_cycle: float) -> None:
"""Write a scaled analogue value to the PWM on the GPIO pin."""
# Not implemented on ArduinoUnoBoard yet.
# Not implemented on SBArduinoBoard yet.
raise NotImplementedError

def get_led_state(self, identifier: int) -> bool:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
"""Abstract hardware backend implemention provided by j5 for Raw USB communication."""
"""
Abstract hardware backend implemention provided by j5 for Raw USB communication.
trickeydan marked this conversation as resolved.
Show resolved Hide resolved

This has been written to reduce code duplication between backends for boards that
trickeydan marked this conversation as resolved.
Show resolved Hide resolved
communicate very similarly. It has been written such that it could potentially be
distributed separately in the future, to remove the PyUSB dependency from the j5 core.
"""

from abc import abstractmethod
from functools import wraps
Expand Down
23 changes: 0 additions & 23 deletions j5/backends/hardware/j5/raw_usb/__init__.py

This file was deleted.

125 changes: 125 additions & 0 deletions j5/backends/hardware/j5/serial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"""Abstract hardware backend implementation provided by j5 for serial comms."""
from abc import abstractmethod
from datetime import timedelta
from functools import wraps
from typing import TYPE_CHECKING, Callable, Optional, Set, Type, TypeVar

from serial import Serial, SerialException, SerialTimeoutException

from j5.backends import BackendMeta, CommunicationError, Environment
from j5.boards import Board

RT = TypeVar("RT") # pragma: nocover

if TYPE_CHECKING:
from typing_extensions import Protocol
else:
class Protocol:
"""Dummy class since typing_extensions is not available at runtime."""

pass


def handle_serial_error(func: Callable[..., RT]) -> Callable[..., RT]: # type: ignore
"""
Wrap functions that use the serial port, and rethrow the errors.

This is a decorator that should be used to wrap any functions that call the serial
interface. It will catch and rethrow the errors as a CommunicationError, so that it
is more explicit what is going wrong.
"""
@wraps(func)
def catch_exceptions(*args, **kwargs): # type: ignore
try:
return func(*args, **kwargs)
except SerialTimeoutException as e:
raise CommunicationError(f"Serial Timeout Error: {e}")
except SerialException as e:
raise CommunicationError(f"Serial Error: {e}")
return catch_exceptions


class Seriallike(Protocol):
"""
Something that walks like a Serial and quacks like a Serial.

This is used instead of hardcoding the Serial class to allow it to be mocked out.
"""

def __init__(self,
kierdavis marked this conversation as resolved.
Show resolved Hide resolved
port: Optional[str] = None,
baudrate: int = 9600,
bytesize: int = 8,
parity: str = 'N',
stopbits: float = 1,
timeout: Optional[float] = None):
...

def close(self) -> None:
"""Close the connection."""
...

def flush(self) -> None:
"""Flush all pending write operations."""
...

def readline(self) -> bytes:
"""Read a line from the serial port."""
...

def write(self, data: bytes) -> int:
"""Write data to the serial port."""
...


class SerialHardwareBackend(metaclass=BackendMeta):
"""An abstract class for creating backends that use USB serial communication."""

@handle_serial_error
def __init__(
self,
serial_port: str,
serial_class: Type[Seriallike] = Serial,
baud: int = 115200,
timeout: timedelta = timedelta(milliseconds=250),
) -> None:
timeout_secs = timeout / timedelta(seconds=1)
self._serial = serial_class(
port=serial_port,
baudrate=baud,
timeout=timeout_secs,
)

@classmethod
@abstractmethod
def discover(cls) -> Set[Board]:
"""Discover boards that this backend can control."""
raise NotImplementedError # pragma: no cover

@property
@abstractmethod
def environment(self) -> Environment:
"""Environment the backend belongs too."""
raise NotImplementedError # pragma: no cover

@property
@abstractmethod
def firmware_version(self) -> Optional[str]:
"""The firmware version of the board."""
raise NotImplementedError # pragma: no cover

@handle_serial_error
def read_serial_line(self, empty: bool = False) -> str:
"""Read a line from the serial interface."""
bdata = self._serial.readline()

if len(bdata) == 0:
if empty:
return ""
raise CommunicationError(
"No response from board. "
kierdavis marked this conversation as resolved.
Show resolved Hide resolved
"Is it correctly powered?",
)

ldata = bdata.decode('utf-8')
return ldata.rstrip()
1 change: 1 addition & 0 deletions j5/backends/hardware/sb/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Backends for SourceBots boards in the Hardware Environment."""
Loading