Skip to content

Commit f4f6d26

Browse files
authored
[RSDK-7267] Board cleanup part 1: analogs can be written to (#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.
1 parent 09baf6d commit f4f6d26

File tree

8 files changed

+129
-112
lines changed

8 files changed

+129
-112
lines changed

examples/server/v1/components.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -227,14 +227,17 @@ async def get_geometries(self, extra: Optional[Dict[str, Any]] = None, **kwargs)
227227
return GEOMETRIES
228228

229229

230-
class ExampleAnalogReader(Board.AnalogReader):
230+
class ExampleAnalog(Board.Analog):
231231
def __init__(self, name: str, value: int):
232232
self.value = value
233233
super().__init__(name)
234234

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

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

239242
class ExampleDigitalInterrupt(Board.DigitalInterrupt):
240243
def __init__(self, name: str):
@@ -275,20 +278,20 @@ class ExampleBoard(Board):
275278
def __init__(
276279
self,
277280
name: str,
278-
analog_readers: Dict[str, Board.AnalogReader],
281+
analogs: Dict[str, Board.Analog],
279282
digital_interrupts: Dict[str, Board.DigitalInterrupt],
280283
gpio_pins: Dict[str, Board.GPIOPin],
281284
):
282-
self.analog_readers = analog_readers
285+
self.analogs = analogs
283286
self.digital_interrupts = digital_interrupts
284287
self.gpios = gpio_pins
285288
super().__init__(name)
286289

287-
async def analog_reader_by_name(self, name: str) -> Board.AnalogReader:
290+
async def analog_by_name(self, name: str) -> Board.Analog:
288291
try:
289-
return self.analog_readers[name]
292+
return self.analogs[name]
290293
except KeyError:
291-
raise ResourceNotFoundError("Board.AnalogReader", name)
294+
raise ResourceNotFoundError("Board.Analog", name)
292295

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

305-
async def analog_reader_names(self) -> List[str]:
306-
return [key for key in self.analog_readers.keys()]
308+
async def analog_names(self) -> List[str]:
309+
return [key for key in self.analogs.keys()]
307310

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

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

314-
async def write_analog(self, pin: str, value: int, *, timeout: Optional[float] = None, **kwargs):
315-
raise NotImplementedError()
316-
317317
async def stream_ticks(self, interrupts: List[Board.DigitalInterrupt], *, timeout: Optional[float] = None, **kwargs) -> TickStream:
318318
raise NotImplementedError()
319319

examples/server/v1/server.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from viam.rpc.server import Server
77

88
from .components import (
9-
ExampleAnalogReader,
9+
ExampleAnalog,
1010
ExampleArm,
1111
ExampleAudioInput,
1212
ExampleBase,
@@ -33,8 +33,8 @@ async def run(host: str, port: int, log_level: int):
3333
my_base = ExampleBase("base0")
3434
my_board = ExampleBoard(
3535
name="board",
36-
analog_readers={
37-
"reader1": ExampleAnalogReader("reader1", 3),
36+
analogs={
37+
"reader1": ExampleAnalog("reader1", 3),
3838
},
3939
digital_interrupts={
4040
"interrupt1": ExampleDigitalInterrupt("interrupt1"),

src/viam/components/board/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313

1414

1515
async def create_status(component: Board) -> Status:
16-
(analog_names, digital_interrupt_names) = await asyncio.gather(component.analog_reader_names(), component.digital_interrupt_names())
16+
(analog_names, digital_interrupt_names) = await asyncio.gather(component.analog_names(), component.digital_interrupt_names())
1717
analogs, digital_interrupts = {}, {}
1818
for x in analog_names:
19-
analog = await component.analog_reader_by_name(x)
19+
analog = await component.analog_by_name(x)
2020
read = await analog.read()
2121
analogs[x] = read
2222

src/viam/components/board/board.py

Lines changed: 32 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
class Board(ComponentBase):
1616
"""
1717
Board represents a physical general purpose compute board that contains various
18-
components such as analog readers, and digital interrupts.
18+
components such as analog readers/writers, and digital interrupts.
1919
2020
This acts as an abstract base class for any drivers representing specific
2121
board implementations. This cannot be used on its own. If the ``__init__()`` function is
@@ -30,45 +30,55 @@ class Board(ComponentBase):
3030
RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, "board"
3131
)
3232

33-
class AnalogReader:
33+
class Analog:
3434
"""
35-
AnalogReader represents an analog pin reader that resides on a Board.
35+
AnalogReader represents an analog pin reader or writer that resides on a Board.
3636
"""
3737

3838
name: str
39-
"""The name of the analog reader"""
39+
"""The name of the analog pin"""
4040

4141
def __init__(self, name: str):
4242
self.name = name
4343

4444
@abc.abstractmethod
4545
async def read(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None, **kwargs) -> int:
4646
"""
47-
Read the current value.
47+
Read the current value from the reader.
4848
4949
::
5050
5151
my_board = Board.from_robot(robot=robot, name="my_board")
5252
53-
# Get the GPIOPin with pin number 15.
54-
pin = await my_board.gpio_pin_by_name(name="15")
55-
56-
# Get if it is true or false that the pin is set to high.
57-
duty_cycle = await pin.get_pwm()
58-
5953
# Get the AnalogReader "my_example_analog_reader".
6054
reader = await my_board.analog_reader_by_name(
6155
name="my_example_analog_reader")
6256
6357
# Get the value of the digital signal "my_example_analog_reader" has most
6458
# recently measured.
65-
reading = reader.read()
59+
reading = await reader.read()
6660
6761
Returns:
6862
int: The current value.
6963
"""
7064
...
7165

66+
@abc.abstractmethod
67+
async def write(self, value: int, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None, **kwargs):
68+
"""
69+
Write a value to the analog writer.
70+
71+
::
72+
my_board = Board.from_robot(robot=robot, name="my_board")
73+
74+
# Get the AnalogWriter "my_example_analog_writer".
75+
writer = await my_board.analog_by_name(
76+
name="my_example_analog_writer")
77+
78+
await writer.write(42)
79+
"""
80+
...
81+
7282
class DigitalInterrupt:
7383
"""
7484
DigitalInterrupt represents a configured interrupt on the Board that
@@ -246,22 +256,22 @@ async def set_pwm_frequency(
246256
...
247257

248258
@abc.abstractmethod
249-
async def analog_reader_by_name(self, name: str) -> AnalogReader:
259+
async def analog_by_name(self, name: str) -> Analog:
250260
"""
251-
Get an AnalogReader by ``name``.
261+
Get an Analog (reader or writer) by ``name``.
252262
253263
::
254264
255265
my_board = Board.from_robot(robot=robot, name="my_board")
256266
257-
# Get the AnalogReader "my_example_analog_reader".
258-
reader = await my_board.analog_reader_by_name(name="my_example_analog_reader")
267+
# Get the Analog "my_example_analog_reader".
268+
reader = await my_board.analog_by_name(name="my_example_analog_reader")
259269
260270
Args:
261271
name (str): Name of the analog reader to be retrieved.
262272
263273
Returns:
264-
AnalogReader: The analog reader.
274+
Analog: The analog reader or writer.
265275
"""
266276
...
267277

@@ -307,19 +317,19 @@ async def gpio_pin_by_name(self, name: str) -> GPIOPin:
307317
...
308318

309319
@abc.abstractmethod
310-
async def analog_reader_names(self) -> List[str]:
320+
async def analog_names(self) -> List[str]:
311321
"""
312-
Get the names of all known analog readers.
322+
Get the names of all known analog readers and/or writers.
313323
314324
::
315325
316326
my_board = Board.from_robot(robot=robot, name="my_board")
317327
318-
# Get the name of every AnalogReader configured on the board.
319-
names = await my_board.analog_reader_names()
328+
# Get the name of every Analog configured on the board.
329+
names = await my_board.analog_names()
320330
321331
Returns:
322-
List[str]: The list of names of all known analog readers.
332+
List[str]: The list of names of all known analog readers/writers.
323333
"""
324334
...
325335

@@ -360,24 +370,6 @@ async def set_power_mode(
360370
"""
361371
...
362372

363-
@abc.abstractmethod
364-
async def write_analog(self, pin: str, value: int, *, timeout: Optional[float] = None, **kwargs):
365-
"""
366-
Write an analog value to a pin on the board.
367-
368-
::
369-
370-
my_board = Board.from_robot(robot=robot, name="my_board")
371-
372-
# Set pin 11 to value 48.
373-
await my_board.write_analog(pin="11", value=48)
374-
375-
Args:
376-
pin (str): The name of the pin.
377-
value (int): The value to write.
378-
"""
379-
...
380-
381373
@abc.abstractmethod
382374
async def stream_ticks(self, interrupts: List[DigitalInterrupt], *, timeout: Optional[float] = None, **kwargs) -> TickStream:
383375
"""

src/viam/components/board/client.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
LOGGER = getLogger(__name__)
3838

3939

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

58+
async def write(
59+
self,
60+
value: int,
61+
*,
62+
extra: Optional[Dict[str, Any]] = None,
63+
timeout: Optional[float] = None,
64+
**kwargs,
65+
):
66+
if extra is None:
67+
extra = {}
68+
request = WriteAnalogRequest(name=self.board.name, pin=self.name, value=value, extra=dict_to_struct(extra))
69+
await self.board.client.WriteAnalog(request, timeout=timeout)
70+
5871

5972
class DigitalInterruptClient(Board.DigitalInterrupt):
6073
def __init__(self, name: str, board: "BoardClient"):
@@ -164,19 +177,19 @@ class BoardClient(Board, ReconfigurableResourceRPCClientBase):
164177
gRPC client for the Board component.
165178
"""
166179

167-
_analog_reader_names: List[str]
180+
_analog_names: List[str]
168181
_digital_interrupt_names: List[str]
169182

170183
def __init__(self, name: str, channel: Channel):
171184
self.channel = channel
172185
self.client = BoardServiceStub(channel)
173-
self._analog_reader_names = []
186+
self._analog_names = []
174187
self._digital_interrupt_names = []
175188
super().__init__(name)
176189

177-
async def analog_reader_by_name(self, name: str) -> Board.AnalogReader:
178-
self._analog_reader_names.append(name)
179-
return AnalogReaderClient(name, self)
190+
async def analog_by_name(self, name: str) -> Board.Analog:
191+
self._analog_names.append(name)
192+
return AnalogClient(name, self)
180193

181194
async def digital_interrupt_by_name(self, name: str) -> Board.DigitalInterrupt:
182195
self._digital_interrupt_names.append(name)
@@ -185,10 +198,10 @@ async def digital_interrupt_by_name(self, name: str) -> Board.DigitalInterrupt:
185198
async def gpio_pin_by_name(self, name: str) -> Board.GPIOPin:
186199
return GPIOPinClient(name, self)
187200

188-
async def analog_reader_names(self) -> List[str]:
189-
if self._analog_reader_names is None:
201+
async def analog_names(self) -> List[str]:
202+
if self._analog_names is None:
190203
return []
191-
return self._analog_reader_names
204+
return self._analog_names
192205

193206
async def digital_interrupt_names(self) -> List[str]:
194207
if self._digital_interrupt_names is None:

src/viam/components/board/service.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ async def ReadAnalogReader(self, stream: Stream[ReadAnalogReaderRequest, ReadAna
134134
name = request.board_name
135135
board = self.get_resource(name)
136136
try:
137-
analog_reader = await board.analog_reader_by_name(request.analog_reader_name)
137+
analog_reader = await board.analog_by_name(request.analog_reader_name)
138138
except ResourceNotFoundError as e:
139139
raise e.grpc_error
140140
timeout = stream.deadline.time_remaining() if stream.deadline else None
@@ -176,8 +176,12 @@ async def WriteAnalog(self, stream: Stream[WriteAnalogRequest, WriteAnalogRespon
176176
assert request is not None
177177
name = request.name
178178
board = self.get_resource(name)
179+
try:
180+
analog_writer = await board.analog_by_name(request.pin)
181+
except ResourceNotFoundError as e:
182+
raise e.grpc_error
179183
timeout = stream.deadline.time_remaining() if stream.deadline else None
180-
await board.write_analog(pin=request.pin, value=request.value, timeout=timeout, metadata=stream.metadata)
184+
await analog_writer.write(value=request.value, timeout=timeout, metadata=stream.metadata, extra=struct_to_dict(request.extra))
181185
response = WriteAnalogResponse()
182186
await stream.send_message(response)
183187

0 commit comments

Comments
 (0)