Skip to content

Commit

Permalink
[RSDK-7267] Board cleanup part 1: analogs can be written to (viamrobo…
Browse files Browse the repository at this point in the history
…tics#606)

- `AnalogReader` is now just `Analog`. Similarly, `analog_reader_by_name` is just `analog_by_name`, etc.
- The `Analog` abstract class can now `.write()` in addition to `.read()`.
- (The underlying RPCs are unchanged: it's still a `WriteAnalog` RPC on the board. but SDK users don't interact directly with the RPCs.)

In an upcoming PR, I'll have more board cleanup to stream digital interrupts. but that's separate enough to warrant a separate PR.
  • Loading branch information
penguinland authored May 9, 2024
1 parent 09baf6d commit f4f6d26
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 112 deletions.
22 changes: 11 additions & 11 deletions examples/server/v1/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,14 +227,17 @@ async def get_geometries(self, extra: Optional[Dict[str, Any]] = None, **kwargs)
return GEOMETRIES


class ExampleAnalogReader(Board.AnalogReader):
class ExampleAnalog(Board.Analog):
def __init__(self, name: str, value: int):
self.value = value
super().__init__(name)

async def read(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> int:
return self.value

async def write(self, value: int, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float], **kwargs):
self.value = value


class ExampleDigitalInterrupt(Board.DigitalInterrupt):
def __init__(self, name: str):
Expand Down Expand Up @@ -275,20 +278,20 @@ class ExampleBoard(Board):
def __init__(
self,
name: str,
analog_readers: Dict[str, Board.AnalogReader],
analogs: Dict[str, Board.Analog],
digital_interrupts: Dict[str, Board.DigitalInterrupt],
gpio_pins: Dict[str, Board.GPIOPin],
):
self.analog_readers = analog_readers
self.analogs = analogs
self.digital_interrupts = digital_interrupts
self.gpios = gpio_pins
super().__init__(name)

async def analog_reader_by_name(self, name: str) -> Board.AnalogReader:
async def analog_by_name(self, name: str) -> Board.Analog:
try:
return self.analog_readers[name]
return self.analogs[name]
except KeyError:
raise ResourceNotFoundError("Board.AnalogReader", name)
raise ResourceNotFoundError("Board.Analog", name)

async def digital_interrupt_by_name(self, name: str) -> Board.DigitalInterrupt:
try:
Expand All @@ -302,18 +305,15 @@ async def gpio_pin_by_name(self, name: str) -> Board.GPIOPin:
except KeyError:
raise ResourceNotFoundError("Board.GPIOPin", name)

async def analog_reader_names(self) -> List[str]:
return [key for key in self.analog_readers.keys()]
async def analog_names(self) -> List[str]:
return [key for key in self.analogs.keys()]

async def digital_interrupt_names(self) -> List[str]:
return [key for key in self.digital_interrupts.keys()]

async def set_power_mode(self, **kwargs):
raise NotImplementedError()

async def write_analog(self, pin: str, value: int, *, timeout: Optional[float] = None, **kwargs):
raise NotImplementedError()

async def stream_ticks(self, interrupts: List[Board.DigitalInterrupt], *, timeout: Optional[float] = None, **kwargs) -> TickStream:
raise NotImplementedError()

Expand Down
6 changes: 3 additions & 3 deletions examples/server/v1/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from viam.rpc.server import Server

from .components import (
ExampleAnalogReader,
ExampleAnalog,
ExampleArm,
ExampleAudioInput,
ExampleBase,
Expand All @@ -33,8 +33,8 @@ async def run(host: str, port: int, log_level: int):
my_base = ExampleBase("base0")
my_board = ExampleBoard(
name="board",
analog_readers={
"reader1": ExampleAnalogReader("reader1", 3),
analogs={
"reader1": ExampleAnalog("reader1", 3),
},
digital_interrupts={
"interrupt1": ExampleDigitalInterrupt("interrupt1"),
Expand Down
4 changes: 2 additions & 2 deletions src/viam/components/board/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@


async def create_status(component: Board) -> Status:
(analog_names, digital_interrupt_names) = await asyncio.gather(component.analog_reader_names(), component.digital_interrupt_names())
(analog_names, digital_interrupt_names) = await asyncio.gather(component.analog_names(), component.digital_interrupt_names())
analogs, digital_interrupts = {}, {}
for x in analog_names:
analog = await component.analog_reader_by_name(x)
analog = await component.analog_by_name(x)
read = await analog.read()
analogs[x] = read

Expand Down
72 changes: 32 additions & 40 deletions src/viam/components/board/board.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
class Board(ComponentBase):
"""
Board represents a physical general purpose compute board that contains various
components such as analog readers, and digital interrupts.
components such as analog readers/writers, and digital interrupts.
This acts as an abstract base class for any drivers representing specific
board implementations. This cannot be used on its own. If the ``__init__()`` function is
Expand All @@ -30,45 +30,55 @@ class Board(ComponentBase):
RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, "board"
)

class AnalogReader:
class Analog:
"""
AnalogReader represents an analog pin reader that resides on a Board.
AnalogReader represents an analog pin reader or writer that resides on a Board.
"""

name: str
"""The name of the analog reader"""
"""The name of the analog pin"""

def __init__(self, name: str):
self.name = name

@abc.abstractmethod
async def read(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None, **kwargs) -> int:
"""
Read the current value.
Read the current value from the reader.
::
my_board = Board.from_robot(robot=robot, name="my_board")
# Get the GPIOPin with pin number 15.
pin = await my_board.gpio_pin_by_name(name="15")
# Get if it is true or false that the pin is set to high.
duty_cycle = await pin.get_pwm()
# Get the AnalogReader "my_example_analog_reader".
reader = await my_board.analog_reader_by_name(
name="my_example_analog_reader")
# Get the value of the digital signal "my_example_analog_reader" has most
# recently measured.
reading = reader.read()
reading = await reader.read()
Returns:
int: The current value.
"""
...

@abc.abstractmethod
async def write(self, value: int, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None, **kwargs):
"""
Write a value to the analog writer.
::
my_board = Board.from_robot(robot=robot, name="my_board")
# Get the AnalogWriter "my_example_analog_writer".
writer = await my_board.analog_by_name(
name="my_example_analog_writer")
await writer.write(42)
"""
...

class DigitalInterrupt:
"""
DigitalInterrupt represents a configured interrupt on the Board that
Expand Down Expand Up @@ -246,22 +256,22 @@ async def set_pwm_frequency(
...

@abc.abstractmethod
async def analog_reader_by_name(self, name: str) -> AnalogReader:
async def analog_by_name(self, name: str) -> Analog:
"""
Get an AnalogReader by ``name``.
Get an Analog (reader or writer) by ``name``.
::
my_board = Board.from_robot(robot=robot, name="my_board")
# Get the AnalogReader "my_example_analog_reader".
reader = await my_board.analog_reader_by_name(name="my_example_analog_reader")
# Get the Analog "my_example_analog_reader".
reader = await my_board.analog_by_name(name="my_example_analog_reader")
Args:
name (str): Name of the analog reader to be retrieved.
Returns:
AnalogReader: The analog reader.
Analog: The analog reader or writer.
"""
...

Expand Down Expand Up @@ -307,19 +317,19 @@ async def gpio_pin_by_name(self, name: str) -> GPIOPin:
...

@abc.abstractmethod
async def analog_reader_names(self) -> List[str]:
async def analog_names(self) -> List[str]:
"""
Get the names of all known analog readers.
Get the names of all known analog readers and/or writers.
::
my_board = Board.from_robot(robot=robot, name="my_board")
# Get the name of every AnalogReader configured on the board.
names = await my_board.analog_reader_names()
# Get the name of every Analog configured on the board.
names = await my_board.analog_names()
Returns:
List[str]: The list of names of all known analog readers.
List[str]: The list of names of all known analog readers/writers.
"""
...

Expand Down Expand Up @@ -360,24 +370,6 @@ async def set_power_mode(
"""
...

@abc.abstractmethod
async def write_analog(self, pin: str, value: int, *, timeout: Optional[float] = None, **kwargs):
"""
Write an analog value to a pin on the board.
::
my_board = Board.from_robot(robot=robot, name="my_board")
# Set pin 11 to value 48.
await my_board.write_analog(pin="11", value=48)
Args:
pin (str): The name of the pin.
value (int): The value to write.
"""
...

@abc.abstractmethod
async def stream_ticks(self, interrupts: List[DigitalInterrupt], *, timeout: Optional[float] = None, **kwargs) -> TickStream:
"""
Expand Down
31 changes: 22 additions & 9 deletions src/viam/components/board/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
LOGGER = getLogger(__name__)


class AnalogReaderClient(Board.AnalogReader):
class AnalogClient(Board.Analog):
def __init__(self, name: str, board: "BoardClient"):
self.board = board
super().__init__(name)
Expand All @@ -55,6 +55,19 @@ async def read(
response: ReadAnalogReaderResponse = await self.board.client.ReadAnalogReader(request, timeout=timeout)
return response.value

async def write(
self,
value: int,
*,
extra: Optional[Dict[str, Any]] = None,
timeout: Optional[float] = None,
**kwargs,
):
if extra is None:
extra = {}
request = WriteAnalogRequest(name=self.board.name, pin=self.name, value=value, extra=dict_to_struct(extra))
await self.board.client.WriteAnalog(request, timeout=timeout)


class DigitalInterruptClient(Board.DigitalInterrupt):
def __init__(self, name: str, board: "BoardClient"):
Expand Down Expand Up @@ -164,19 +177,19 @@ class BoardClient(Board, ReconfigurableResourceRPCClientBase):
gRPC client for the Board component.
"""

_analog_reader_names: List[str]
_analog_names: List[str]
_digital_interrupt_names: List[str]

def __init__(self, name: str, channel: Channel):
self.channel = channel
self.client = BoardServiceStub(channel)
self._analog_reader_names = []
self._analog_names = []
self._digital_interrupt_names = []
super().__init__(name)

async def analog_reader_by_name(self, name: str) -> Board.AnalogReader:
self._analog_reader_names.append(name)
return AnalogReaderClient(name, self)
async def analog_by_name(self, name: str) -> Board.Analog:
self._analog_names.append(name)
return AnalogClient(name, self)

async def digital_interrupt_by_name(self, name: str) -> Board.DigitalInterrupt:
self._digital_interrupt_names.append(name)
Expand All @@ -185,10 +198,10 @@ async def digital_interrupt_by_name(self, name: str) -> Board.DigitalInterrupt:
async def gpio_pin_by_name(self, name: str) -> Board.GPIOPin:
return GPIOPinClient(name, self)

async def analog_reader_names(self) -> List[str]:
if self._analog_reader_names is None:
async def analog_names(self) -> List[str]:
if self._analog_names is None:
return []
return self._analog_reader_names
return self._analog_names

async def digital_interrupt_names(self) -> List[str]:
if self._digital_interrupt_names is None:
Expand Down
8 changes: 6 additions & 2 deletions src/viam/components/board/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ async def ReadAnalogReader(self, stream: Stream[ReadAnalogReaderRequest, ReadAna
name = request.board_name
board = self.get_resource(name)
try:
analog_reader = await board.analog_reader_by_name(request.analog_reader_name)
analog_reader = await board.analog_by_name(request.analog_reader_name)
except ResourceNotFoundError as e:
raise e.grpc_error
timeout = stream.deadline.time_remaining() if stream.deadline else None
Expand Down Expand Up @@ -176,8 +176,12 @@ async def WriteAnalog(self, stream: Stream[WriteAnalogRequest, WriteAnalogRespon
assert request is not None
name = request.name
board = self.get_resource(name)
try:
analog_writer = await board.analog_by_name(request.pin)
except ResourceNotFoundError as e:
raise e.grpc_error
timeout = stream.deadline.time_remaining() if stream.deadline else None
await board.write_analog(pin=request.pin, value=request.value, timeout=timeout, metadata=stream.metadata)
await analog_writer.write(value=request.value, timeout=timeout, metadata=stream.metadata, extra=struct_to_dict(request.extra))
response = WriteAnalogResponse()
await stream.send_message(response)

Expand Down
Loading

0 comments on commit f4f6d26

Please sign in to comment.