Skip to content

Commit 096a508

Browse files
authored
feat(components): add switch implementation (#862)
1 parent 580bdcd commit 096a508

File tree

7 files changed

+448
-0
lines changed

7 files changed

+448
-0
lines changed

examples/apis.json

+4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
"func": "get_position",
3030
"packagePath": "viam.components"
3131
},
32+
"switch": {
33+
"func": "get_position",
34+
"packagePath": "viam.components"
35+
},
3236
"arm": {
3337
"func": "get_end_position",
3438
"packagePath": "viam.components"
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import viam.gen.component.switch.v1.switch_pb2
2+
from viam.resource.registry import Registry, ResourceRegistration
3+
4+
from .client import SwitchClient
5+
from .service import SwitchRPCService
6+
from .switch import Switch
7+
8+
__all__ = ["Switch"]
9+
10+
Registry.register_api(
11+
ResourceRegistration(
12+
Switch, SwitchRPCService, lambda name, channel: SwitchClient(name, channel)
13+
)
14+
)

src/viam/components/switch/client.py

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from typing import Any, Mapping, Optional
2+
3+
from grpclib.client import Channel
4+
5+
from viam.proto.common import (
6+
DoCommandRequest,
7+
DoCommandResponse,
8+
)
9+
from viam.proto.component.switch import SwitchServiceStub
10+
from viam.gen.component.switch.v1.switch_pb2 import (
11+
GetPositionRequest,
12+
GetPositionResponse,
13+
SetPositionRequest,
14+
GetNumberOfPositionsRequest,
15+
GetNumberOfPositionsResponse,
16+
)
17+
from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase
18+
from viam.utils import (
19+
ValueTypes,
20+
dict_to_struct,
21+
struct_to_dict,
22+
)
23+
24+
from .switch import Switch
25+
26+
27+
class SwitchClient(Switch, ReconfigurableResourceRPCClientBase):
28+
"""
29+
gRPC client for Switch component
30+
"""
31+
32+
def __init__(self, name: str, channel: Channel):
33+
self.channel = channel
34+
self.client = SwitchServiceStub(channel)
35+
super().__init__(name)
36+
37+
async def get_position(
38+
self,
39+
*,
40+
extra: Optional[Mapping[str, Any]] = None,
41+
timeout: Optional[float] = None,
42+
**kwargs,
43+
) -> int:
44+
md = kwargs.get("metadata", self.Metadata()).proto
45+
request = GetPositionRequest(name=self.name, extra=dict_to_struct(extra))
46+
response: GetPositionResponse = await self.client.GetPosition(request, timeout=timeout, metadata=md)
47+
return response.position
48+
49+
async def set_position(
50+
self,
51+
position: int,
52+
*,
53+
extra: Optional[Mapping[str, Any]] = None,
54+
timeout: Optional[float] = None,
55+
**kwargs,
56+
) -> None:
57+
md = kwargs.get("metadata", self.Metadata()).proto
58+
request = SetPositionRequest(name=self.name, position=position, extra=dict_to_struct(extra))
59+
await self.client.SetPosition(request, timeout=timeout, metadata=md)
60+
61+
async def get_number_of_positions(
62+
self,
63+
*,
64+
extra: Optional[Mapping[str, Any]] = None,
65+
timeout: Optional[float] = None,
66+
**kwargs,
67+
) -> int:
68+
md = kwargs.get("metadata", self.Metadata()).proto
69+
request = GetNumberOfPositionsRequest(name=self.name, extra=dict_to_struct(extra))
70+
response: GetNumberOfPositionsResponse = await self.client.GetNumberOfPositions(request, timeout=timeout, metadata=md)
71+
return response.number_of_positions
72+
73+
async def do_command(
74+
self,
75+
command: Mapping[str, ValueTypes],
76+
*,
77+
timeout: Optional[float] = None,
78+
**kwargs,
79+
) -> Mapping[str, ValueTypes]:
80+
md = kwargs.get("metadata", self.Metadata()).proto
81+
request = DoCommandRequest(name=self.name, command=dict_to_struct(command))
82+
response: DoCommandResponse = await self.client.DoCommand(request, timeout=timeout, metadata=md)
83+
return struct_to_dict(response.result)

src/viam/components/switch/service.py

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from grpclib.server import Stream
2+
3+
from viam.proto.common import (
4+
DoCommandRequest,
5+
DoCommandResponse,
6+
)
7+
from viam.gen.component.switch.v1.switch_pb2 import (
8+
GetPositionRequest,
9+
GetPositionResponse,
10+
SetPositionRequest,
11+
SetPositionResponse,
12+
GetNumberOfPositionsRequest,
13+
GetNumberOfPositionsResponse,
14+
)
15+
from viam.proto.component.switch import SwitchServiceBase
16+
from viam.resource.rpc_service_base import ResourceRPCServiceBase
17+
from viam.utils import dict_to_struct, struct_to_dict
18+
19+
from .switch import Switch
20+
21+
22+
class SwitchRPCService(SwitchServiceBase, ResourceRPCServiceBase[Switch]):
23+
"""
24+
gRPC Service for a generic Switch
25+
"""
26+
27+
RESOURCE_TYPE = Switch
28+
29+
async def GetPosition(self, stream: Stream[GetPositionRequest, GetPositionResponse]) -> None:
30+
request = await stream.recv_message()
31+
assert request is not None
32+
name = request.name
33+
switch = self.get_resource(name)
34+
timeout = stream.deadline.time_remaining() if stream.deadline else None
35+
position = await switch.get_position(extra=struct_to_dict(request.extra), timeout=timeout, metadata=stream.metadata)
36+
response = GetPositionResponse(position=position)
37+
await stream.send_message(response)
38+
39+
async def SetPosition(self, stream: Stream[SetPositionRequest, SetPositionResponse]) -> None:
40+
request = await stream.recv_message()
41+
assert request is not None
42+
name = request.name
43+
switch = self.get_resource(name)
44+
timeout = stream.deadline.time_remaining() if stream.deadline else None
45+
await switch.set_position(position=request.position, extra=struct_to_dict(request.extra), timeout=timeout, metadata=stream.metadata)
46+
await stream.send_message(SetPositionResponse())
47+
48+
async def GetNumberOfPositions(self, stream: Stream[GetNumberOfPositionsRequest, GetNumberOfPositionsResponse]) -> None:
49+
request = await stream.recv_message()
50+
assert request is not None
51+
name = request.name
52+
switch = self.get_resource(name)
53+
timeout = stream.deadline.time_remaining() if stream.deadline else None
54+
number_of_positions = await switch.get_number_of_positions(
55+
extra=struct_to_dict(request.extra), timeout=timeout, metadata=stream.metadata
56+
)
57+
response = GetNumberOfPositionsResponse(number_of_positions=number_of_positions)
58+
await stream.send_message(response)
59+
60+
async def DoCommand(self, stream: Stream[DoCommandRequest, DoCommandResponse]) -> None:
61+
request = await stream.recv_message()
62+
assert request is not None
63+
name = request.name
64+
switch = self.get_resource(name)
65+
timeout = stream.deadline.time_remaining() if stream.deadline else None
66+
result = await switch.do_command(
67+
command=struct_to_dict(request.command),
68+
timeout=timeout,
69+
metadata=stream.metadata,
70+
)
71+
response = DoCommandResponse(result=dict_to_struct(result))
72+
await stream.send_message(response)

src/viam/components/switch/switch.py

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import abc
2+
from typing import Any, Final, Mapping, Optional
3+
4+
from viam.resource.types import API, RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT
5+
from viam.components.component_base import ComponentBase
6+
7+
8+
class Switch(ComponentBase):
9+
"""
10+
Switch represents a device with two or more finite states (or positions) than can be set and retrieved.
11+
12+
This acts as an abstract base class for any drivers representing specific
13+
switch implementations. This cannot be used on its own. If the ``__init__()`` function is
14+
overridden, it must call the ``super().__init__()`` function.
15+
16+
::
17+
18+
from viam.components.switch import Switch
19+
20+
For more information, see `Switch component <https://docs.viam.com/dev/reference/apis/components/switch/>`_.
21+
"""
22+
23+
API: Final = API( # pyright: ignore [reportIncompatibleVariableOverride]
24+
RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, "switch"
25+
)
26+
27+
@abc.abstractmethod
28+
async def get_position(self, *, extra: Optional[Mapping[str, Any]] = None, timeout: Optional[float] = None, **kwargs) -> int:
29+
"""
30+
Get the current position of the switch
31+
32+
::
33+
34+
my_switch = Switch.from_robot(robot=machine, name="my_switch")
35+
36+
# Update the switch from its current position to the desired position of 1.
37+
await my_switch.set_position(1)
38+
39+
# Get the current set position of the switch.
40+
pos1 = await my_switch.get_position()
41+
42+
# Update the switch from its current position to the desired position.
43+
await my_switch.set_position(0)
44+
45+
# Get the current set position of the switch.
46+
pos2 = await my_switch.get_position()
47+
48+
Returns:
49+
int: The current position of the switch within the range of available positions.
50+
51+
For more information, see `Switch component <https://docs.viam.com/dev/reference/apis/components/Switch/#getposition>`_.
52+
"""
53+
...
54+
55+
@abc.abstractmethod
56+
async def set_position(
57+
self, position: int, *, extra: Optional[Mapping[str, Any]] = None, timeout: Optional[float] = None, **kwargs
58+
) -> None:
59+
"""
60+
Sets the current position of the switch.
61+
62+
::
63+
64+
my_switch = Switch.from_robot(robot=machine, name="my_switch")
65+
66+
# Update the switch from its current position to the desired position of 1.
67+
await my_switch.set_position(1)
68+
69+
# Update the switch from its current position to the desired position of 0.
70+
await my_switch.set_position(0)
71+
72+
Args:
73+
position (int): The position of the switch within the range of available positions.
74+
75+
For more information, see `Switch component <https://docs.viam.com/dev/reference/apis/components/switch/#setposition>`_.
76+
"""
77+
...
78+
79+
@abc.abstractmethod
80+
async def get_number_of_positions(self, *, extra: Optional[Mapping[str, Any]] = None, timeout: Optional[float] = None, **kwargs) -> int:
81+
"""
82+
Get the number of available positions on the switch.
83+
84+
::
85+
86+
my_switch = Switch.from_robot(robot=machine, name="my_switch")
87+
88+
print(await my_switch.get_number_of_positions())
89+
90+
Returns:
91+
int: The number of available positions.
92+
93+
For more information, see `Switch component <https://docs.viam.com/dev/reference/apis/components/switch/#getnumberofpositions>`_.
94+
"""
95+
...

tests/mocks/components.py

+30
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from viam.components.power_sensor import PowerSensor
2929
from viam.components.sensor import Sensor
3030
from viam.components.servo import Servo
31+
from viam.components.switch import Switch
3132
from viam.errors import ResourceNotFoundError
3233
from viam.media.audio import Audio, AudioStream
3334
from viam.media.video import CameraMimeType, NamedImage, ViamImage
@@ -1013,3 +1014,32 @@ async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeou
10131014

10141015
async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]:
10151016
return {"command": command}
1017+
1018+
1019+
class MockSwitch(Switch):
1020+
def __init__(self, name: str, number_of_positions: int = 3, position: int = 0):
1021+
self.number_of_positions = number_of_positions
1022+
self.position = position
1023+
self.timeout: Optional[float] = None
1024+
self.extra: Optional[Mapping[str, Any]] = None
1025+
super().__init__(name)
1026+
1027+
async def get_position(self, *, extra: Optional[Mapping[str, Any]] = None, timeout: Optional[float] = None, **kwargs) -> int:
1028+
self.extra = extra
1029+
self.timeout = timeout
1030+
return self.position
1031+
1032+
async def get_number_of_positions(self, *, extra: Optional[Mapping[str, Any]] = None, timeout: Optional[float] = None, **kwargs) -> int:
1033+
self.extra = extra
1034+
self.timeout = timeout
1035+
return self.number_of_positions
1036+
1037+
async def set_position(
1038+
self, position: int, *, extra: Optional[Mapping[str, Any]] = None, timeout: Optional[float] = None, **kwargs
1039+
) -> None:
1040+
self.extra = extra
1041+
self.timeout = timeout
1042+
self.position = position
1043+
1044+
async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]:
1045+
return {"command": command}

0 commit comments

Comments
 (0)