Skip to content

Commit

Permalink
Fix simple module (#436)
Browse files Browse the repository at this point in the history
  • Loading branch information
cheukt authored Sep 20, 2023
1 parent e34952e commit 191c878
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 46 deletions.
35 changes: 19 additions & 16 deletions docs/examples/example.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -348,30 +348,41 @@
"\n",
"Custom validation can be done by specifiying a validate function. Validate functions can raise errors that will be returned to the parent through gRPC. Validate functions return a sequence of strings representing the implicit dependencies of the resource. If there are none, return an empty sequence `[]`.\n",
"\n",
"For example, let's say `MySensor` had a `multiplier` argument that is used to multiply the returned readings in `get_readings()`. The validation function can check if a multiplier attribute was provided or default the multiplier to 1.\n",
"For example, let's say `MySensor` had a `multiplier` argument that is used to multiply the returned readings in `get_readings()`. The validation function can check if a multiplier attribute was provided and prevent erroneous resource start ups.\n",
"\n",
"Reconfiguration specifies what values can change based on a new configuration. To allow this, add a `reconfigure()` function to the `MySensor` class. A good pattern is to call `reconfigure()` within `new()`, since initialization and reconfiguration are usually very similar.\n",
"\n",
"```python\n",
"# ADD `Sequence` FROM `typing`.\n",
"from typing import Sequence\n",
"\n",
"class MySensor(Sensor):\n",
" # ADD `multiplier` AS A CLASS VARIABLE.\n",
" multiplier: float\n",
"\n",
" # ADD A VALIDATOR FUNCTION \n",
" @classmethod\n",
" def validate_config(cls, config: ComponentConfig) -> Sequence[str]:\n",
" if \"multiplier\" in config.attributes.fields:\n",
" if not isinstance(config.attributes.fields[\"multiplier\"], float):\n",
" raise Exception(\"Multiplier must be a float.\")\n",
" cls.multiplier = config.attributes.fields[\"multiplier\"].number_value\n",
" if cls.multiplier == 0:\n",
" multiplier = config.attributes.fields[\"multiplier\"].number_value\n",
" if multiplier == 0:\n",
" raise Exception(\"Multiplier cannot be 0.\")\n",
" else: \n",
" cls.multiplier = 1.0\n",
" # RETURN AN EMPTY SEQUENCE\n",
" return []\n",
"\n",
" @classmethod\n",
" def new(cls, config: ComponentConfig, dependencies: Mapping[ResourceName, ResourceBase]) -> Self:\n",
" sensor = cls(config.name)\n",
" # CALL RECONFIGURE TO INITIALIZE THE RESOURCE\n",
" sensor.reconfigure(config, dependencies) \n",
" return sensor\n",
"\n",
" def reconfigure(self, config: ComponentConfig, dependencies: Mapping[ResourceName, ResourceBase]):\n",
" if \"multiplier\" in config.attributes.fields:\n",
" multiplier = config.attributes.fields[\"multiplier\"].number_value\n",
" else:\n",
" multiplier = 1.0\n",
" self.multiplier = multiplier\n",
"\n",
" async def get_readings(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> Mapping[str, Any]:\n",
" with open(\"/proc/net/wireless\") as wifi_stats:\n",
" content = wifi_stats.readlines()\n",
Expand All @@ -390,14 +401,6 @@
" async def main():\n",
" Registry.register_resource_creator(Sensor.SUBTYPE, MySensor.MODEL, ResourceCreatorRegistration(MySensor.new, MySensor.validate_config))\n",
"```\n",
"\n",
"Reconfiguration specifies what values can change based on a new configuration. To allow this, add a `reconfigure()` function to the `MySensor` class.\n",
"\n",
"```python\n",
" def reconfigure(self, config: ComponentConfig, dependencies: Mapping[ResourceName, ResourceBase]):\n",
" self.multiplier = config.attributes.fields[\"multiplier\"].number_value\n",
"```\n",
"\n",
"Click [here](https://github.com/viamrobotics/viam-python-sdk/blob/main/docs/examples/module_step2_optional.py) to see the new `wifi_sensor_module.py` with the optional validator and reconfiguration functions."
]
},
Expand Down
9 changes: 3 additions & 6 deletions docs/examples/module_step2.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# wifi-sensor/src/wifi_sensor_module.py
import asyncio
from typing import Any, ClassVar, Dict, List, Mapping, Optional
from typing import Any, ClassVar, Dict, Mapping, Optional
from typing_extensions import Self

from viam.components.sensor import Geometry, Sensor
from viam.components.sensor import Sensor
from viam.proto.app.robot import ComponentConfig
from viam.proto.common import ResourceName
from viam.resource.base import ResourceBase
Expand All @@ -13,7 +13,7 @@

class MySensor(Sensor):
# Subclass the Viam Sensor component and implement the required functions
MODEL: ClassVar[Model] = Model(ModelFamily("acme", "wifi_sensor"), "linux")
MODEL: ClassVar[Model] = Model(ModelFamily("viam", "sensor"), "linux-wifi")

@classmethod
def new(cls, config: ComponentConfig, dependencies: Mapping[ResourceName, ResourceBase]) -> Self:
Expand All @@ -26,9 +26,6 @@ async def get_readings(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -
wifi_signal = [x for x in content[2].split(" ") if x != ""]
return {"link": wifi_signal[2], "level": wifi_signal[3], "noise": wifi_signal[4]}

async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]:
raise NotImplementedError


async def main():
Registry.register_resource_creator(Sensor.SUBTYPE, MySensor.MODEL, ResourceCreatorRegistration(MySensor.new))
Expand Down
23 changes: 11 additions & 12 deletions docs/examples/module_step2_optional.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# wifi-sensor/src/wifi_sensor_module.py
import asyncio
from typing import Any, ClassVar, Dict, List, Mapping, Optional, Sequence
from typing import Any, ClassVar, Dict, Mapping, Optional, Sequence
from typing_extensions import Self

from viam.components.sensor import Geometry, Sensor
from viam.components.sensor import Sensor
from viam.proto.app.robot import ComponentConfig
from viam.proto.common import ResourceName
from viam.resource.base import ResourceBase
Expand All @@ -13,24 +13,22 @@

class MySensor(Sensor):
# Subclass the Viam Sensor component and implement the required functions
MODEL: ClassVar[Model] = Model(ModelFamily("acme", "wifi_sensor"), "linux")
multiplier: float
MODEL: ClassVar[Model] = Model(ModelFamily("viam", "sensor"), "linux-wifi")

@classmethod
def new(cls, config: ComponentConfig, dependencies: Mapping[ResourceName, ResourceBase]) -> Self:
sensor = cls(config.name)
sensor.reconfigure(config, dependencies)
return sensor

@classmethod
def validate_config(cls, config: ComponentConfig) -> Sequence[str]:
if "multiplier" in config.attributes.fields:
if not isinstance(config.attributes.fields["multiplier"], float):
raise Exception("Multiplier must be a float.")
cls.multiplier = config.attributes.fields["multiplier"].number_value
if cls.multiplier == 0:
multiplier = config.attributes.fields["multiplier"].number_value
if multiplier == 0:
raise Exception("Multiplier cannot be 0.")
else:
cls.multiplier = 1.0
return []

async def get_readings(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> Mapping[str, Any]:
Expand All @@ -43,11 +41,12 @@ async def get_readings(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -
wifi_signal.append(int(result[i]) * self.multiplier)
return {"link": wifi_signal[0], "level": wifi_signal[1], "noise": wifi_signal[2]}

async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]:
raise NotImplementedError

def reconfigure(self, config: ComponentConfig, dependencies: Mapping[ResourceName, ResourceBase]):
self.multiplier = config.attributes.fields["multiplier"].number_value
if "multiplier" in config.attributes.fields:
multiplier = config.attributes.fields["multiplier"].number_value
else:
multiplier = 1.0
self.multiplier = multiplier


async def main():
Expand Down
9 changes: 3 additions & 6 deletions docs/examples/module_step3.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# wifi-sensor/src/wifi_sensor_module.py
import asyncio
from typing import Any, ClassVar, Dict, List, Mapping, Optional
from typing import Any, ClassVar, Dict, Mapping, Optional
from typing_extensions import Self

from viam.components.sensor import Geometry, Sensor
from viam.components.sensor import Sensor
from viam.module.module import Module
from viam.proto.app.robot import ComponentConfig
from viam.proto.common import ResourceName
Expand All @@ -14,7 +14,7 @@

class MySensor(Sensor):
# Subclass the Viam Sensor component and implement the required functions
MODEL: ClassVar[Model] = Model(ModelFamily("acme", "wifi_sensor"), "linux")
MODEL: ClassVar[Model] = Model(ModelFamily("viam", "sensor"), "linux-wifi")

@classmethod
def new(cls, config: ComponentConfig, dependencies: Mapping[ResourceName, ResourceBase]) -> Self:
Expand All @@ -27,9 +27,6 @@ async def get_readings(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -
wifi_signal = [x for x in content[2].split(" ") if x != ""]
return {"link": wifi_signal[2], "level": wifi_signal[3], "noise": wifi_signal[4]}

async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None) -> List[Geometry]:
raise NotImplementedError


async def main():
"""
Expand Down
14 changes: 8 additions & 6 deletions examples/simple_module/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,24 @@
class MySensor(Sensor):
# Subclass the Viam Sensor component and implement the required functions
MODEL: ClassVar[Model] = Model(ModelFamily("viam", "sensor"), "mysensor")
multiplier: float

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

@classmethod
def new(cls, config: ComponentConfig, dependencies: Mapping[ResourceName, ResourceBase]) -> Self:
sensor = cls(config.name)
sensor.reconfigure(config, dependencies)
return sensor

@classmethod
def validate_config(cls, config: ComponentConfig) -> Sequence[str]:
if "multiplier" in config.attributes.fields:
if not isinstance(config.attributes.fields["multiplier"], float):
raise Exception("Multiplier must be a float.")
cls.multiplier = config.attributes.fields["multiplier"].number_value
if cls.multiplier == 0:
multiplier = config.attributes.fields["multiplier"].number_value
if multiplier == 0:
raise Exception("Multiplier cannot be 0.")
else:
cls.multiplier = 1.0
return []

async def get_readings(self, extra: Optional[Dict[str, Any]] = None, **kwargs) -> Mapping[str, Any]:
Expand All @@ -45,7 +43,11 @@ async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Option
return command

def reconfigure(self, config: ComponentConfig, dependencies: Mapping[ResourceName, ResourceBase]):
self.multiplier = config.attributes.fields["multiplier"].number_value
if "multiplier" in config.attributes.fields:
multiplier = config.attributes.fields["multiplier"].number_value
else:
multiplier = 1.0
self.multiplier = multiplier


async def main():
Expand Down

0 comments on commit 191c878

Please sign in to comment.