Skip to content

Commit

Permalink
pw_sensor: Add supported bus
Browse files Browse the repository at this point in the history
Add an enum for supported buses. For now only include i2c
and spi. We might want to add others and a way for
downstream to add their own. But this CL focuses on piping
the basic information into the generator.

Bug: b/293466822
Change-Id: Id3982725e9a8c31efb7e71000f2f3a645f1f8fd6
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/232798
Presubmit-Verified: CQ Bot Account <[email protected]>
Commit-Queue: Auto-Submit <[email protected]>
Pigweed-Auto-Submit: Yuval Peress <[email protected]>
Reviewed-by: Taylor Cramer <[email protected]>
Lint: Lint 🤖 <[email protected]>
  • Loading branch information
yperess authored and CQ Bot Account committed Aug 30, 2024
1 parent 38c6f7c commit 8bb8089
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 11 deletions.
13 changes: 13 additions & 0 deletions pw_sensor/py/docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ sensor using the `metadata_schema.json`_ format, e.g.:
compatible:
org: "Bosch"
part: "BMA4xx"
supported-buses:
- i2c
- spi
channels:
acceleration: []
die_temperature: []
Expand All @@ -54,6 +57,7 @@ When describing a sensor from the user's perspective, there are 3 primary points
of interaction:

#. compatible descriptor
#. supported buses
#. channels
#. attributes
#. triggers
Expand All @@ -73,6 +77,12 @@ Both *channels* and *attributes* covered in :ref:`seed-0120`, while the
Next, we need a way to describe a sensor in a platform and language agnostic
way.

What are supported buses?
=========================
Currently, pw_sensor supports 2 types of sensor buses: i2c and spi. Each sensor
must list at least 1 supported bus. Additional buses may be added as well as
support for custom bus descriptors in downstream projects.

What are channels?
==================
A channel is something that we can measure from a sensor. It's reasonable to ask
Expand Down Expand Up @@ -214,6 +224,9 @@ implementing this channel would provide a definition file:
compatible:
org: "myorg"
part: "cakevision"
supported-buses:
- i2c
- spi
channels:
cakes: []
cakes_small: []
Expand Down
23 changes: 22 additions & 1 deletion pw_sensor/py/pw_sensor/constants_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ class SensorSpec:

description: str
compatible: CompatibleSpec
supported_buses: List[str]
attributes: List[SensorAttributeSpec]
channels: dict[str, List[ChannelSpec]]
triggers: List[Any]
Expand Down Expand Up @@ -731,6 +732,18 @@ def is_list_type(t) -> bool:
return origin is list or (origin is list and typing.get_args(t) == ())


def is_primitive(value):
"""Checks if the given value is of a primitive type.
Args:
value: The value to check.
Returns:
True if the value is of a primitive type, False otherwise.
"""
return isinstance(value, (int, float, complex, str, bool))


def create_dataclass_from_dict(cls, data, indent: int = 0):
"""Recursively creates a dataclass instance from a nested dictionary."""

Expand All @@ -746,13 +759,21 @@ def create_dataclass_from_dict(cls, data, indent: int = 0):
)
return result

if is_primitive(data):
return data

for field in fields(cls):
field_value = data[field.name]
field_value = data.get(field.name)
if field_value is None:
field_value = data.get(field.name.replace('_', '-'))

assert field_value is not None

# We need to check if the field is a List, dictionary, or another
# dataclass. If it is, recurse.
if is_list_type(field.type):
item_type = typing.get_args(field.type)[0]
print((" " * indent) + str(item_type), file=sys.stderr)
field_value = [
create_dataclass_from_dict(item_type, item, indent + 2)
for item in field_value
Expand Down
14 changes: 13 additions & 1 deletion pw_sensor/py/pw_sensor/metadata_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@
"type": "string",
"description": "A description of the sensor"
},
"supported-buses": {
"type": "array",
"description": "One or more buses which this sensor driver supports",
"minItems": 1,
"items": {
"enum": [
"i2c",
"spi"
]
}
},
"attributes": {
"type": "array",
"description": "A set of attribute/channel pairs supported by this sensor",
Expand Down Expand Up @@ -100,6 +111,7 @@
},
"additionalProperties": false,
"required": [
"compatible"
"compatible",
"supported-buses"
]
}
11 changes: 11 additions & 0 deletions pw_sensor/py/pw_sensor/resolved_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,17 @@
"type": "string",
"description": "A description of the sensor"
},
"supported-buses": {
"type": "array",
"description": "One or more buses which this sensor driver supports",
"minItems": 1,
"items": {
"enum": [
"i2c",
"spi"
]
}
},
"channels": {
"type": "object",
"description": "A map of channels supported by this sensor",
Expand Down
4 changes: 4 additions & 0 deletions pw_sensor/py/pw_sensor/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ def validate(self, metadata: dict) -> dict:
compatible:
org: "Bosch"
part: "BMA4xx
supported-buses:
- i2c
channels:
acceleration: []
die_temperature: []
Expand Down Expand Up @@ -130,11 +132,13 @@ def validate(self, metadata: dict) -> dict:
self._resolve_triggers(metadata=metadata, out=result)

compatible = metadata.pop("compatible")
supported_buses = metadata.pop("supported-buses")
channels = metadata.pop("channels")
attributes = metadata.pop("attributes")
triggers = metadata.pop("triggers")
result["sensors"][f"{compatible['org']},{compatible['part']}"] = {
"compatible": compatible,
"supported-buses": supported_buses,
"channels": channels,
"attributes": attributes,
"triggers": triggers,
Expand Down
69 changes: 60 additions & 9 deletions pw_sensor/py/validator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,39 +36,81 @@ def test_missing_compatible(self) -> None:
def test_invalid_compatible_type(self) -> None:
"""Check that incorrect type of 'compatible' throws exception"""
self._check_with_exception(
metadata={"compatible": {}},
metadata={"compatible": {}, "supported-buses": ["i2c"]},
exception_string=(
"ERROR: Malformed sensor metadata YAML:\ncompatible: {}"
"ERROR: Malformed sensor metadata YAML:\ncompatible: {}\n"
+ "supported-buses:\n- i2c"
),
cause_substrings=[
"'org' is a required property",
],
)

self._check_with_exception(
metadata={"compatible": []},
metadata={"compatible": [], "supported-buses": ["i2c"]},
exception_string=(
"ERROR: Malformed sensor metadata YAML:\ncompatible: []"
"ERROR: Malformed sensor metadata YAML:\ncompatible: []\n"
+ "supported-buses:\n- i2c"
),
cause_substrings=["[] is not of type 'object'"],
)

self._check_with_exception(
metadata={"compatible": 1},
metadata={"compatible": 1, "supported-buses": ["i2c"]},
exception_string=(
"ERROR: Malformed sensor metadata YAML:\ncompatible: 1"
"ERROR: Malformed sensor metadata YAML:\ncompatible: 1\n"
+ "supported-buses:\n- i2c"
),
cause_substrings=["1 is not of type 'object'"],
)

self._check_with_exception(
metadata={"compatible": ""},
metadata={"compatible": "", "supported-buses": ["i2c"]},
exception_string=(
"ERROR: Malformed sensor metadata YAML:\ncompatible: ''"
"ERROR: Malformed sensor metadata YAML:\ncompatible: ''\n"
+ "supported-buses:\n- i2c"
),
cause_substrings=[" is not of type 'object'"],
)

def test_invalid_supported_buses(self) -> None:
"""
Check that invalid or missing supported-buses cause an error
"""
self._check_with_exception(
metadata={"compatible": {"org": "Google", "part": "Pigweed"}},
exception_string=(
"ERROR: Malformed sensor metadata YAML:\ncompatible:\n"
+ " org: Google\n part: Pigweed"
),
cause_substrings=[],
)

self._check_with_exception(
metadata={
"compatible": {"org": "Google", "part": "Pigweed"},
"supported-buses": [],
},
exception_string=(
"ERROR: Malformed sensor metadata YAML:\ncompatible:\n"
+ " org: Google\n part: Pigweed\nsupported-buses: []"
),
cause_substrings=[],
)

self._check_with_exception(
metadata={
"compatible": {"org": "Google", "part": "Pigweed"},
"supported-buses": ["not-a-bus"],
},
exception_string=(
"ERROR: Malformed sensor metadata YAML:\ncompatible:\n"
+ " org: Google\n part: Pigweed\nsupported-buses:\n"
+ "- not-a-bus"
),
cause_substrings=[],
)

def test_empty_dependency_list(self) -> None:
"""
Check that an empty or missing 'deps' resolves to one with an empty
Expand All @@ -78,6 +120,7 @@ def test_empty_dependency_list(self) -> None:
"sensors": {
"google,foo": {
"compatible": {"org": "google", "part": "foo"},
"supported-buses": ["i2c"],
"description": "",
"channels": {},
"attributes": [],
Expand All @@ -91,12 +134,16 @@ def test_empty_dependency_list(self) -> None:
}
metadata = {
"compatible": {"org": "google", "part": "foo"},
"supported-buses": ["i2c"],
"deps": [],
}
result = Validator().validate(metadata=metadata)
self.assertEqual(result, expected)

metadata = {"compatible": {"org": "google", "part": "foo"}}
metadata = {
"compatible": {"org": "google", "part": "foo"},
"supported-buses": ["i2c"],
}
result = Validator().validate(metadata=metadata)
self.assertEqual(result, expected)

Expand All @@ -109,6 +156,7 @@ def test_invalid_dependency_file(self) -> None:
self._check_with_exception(
metadata={
"compatible": {"org": "google", "part": "foo"},
"supported-buses": ["i2c"],
"deps": ["test.yaml"],
},
exception_string="Failed to find test.yaml using search paths:",
Expand All @@ -123,6 +171,7 @@ def test_invalid_channel_name_raises_exception(self) -> None:
self._check_with_exception(
metadata={
"compatible": {"org": "google", "part": "foo"},
"supported-buses": ["i2c"],
"channels": {"bar": []},
},
exception_string="Failed to find a definition for 'bar', did"
Expand Down Expand Up @@ -185,6 +234,7 @@ def test_channel_info_from_deps(self) -> None:
metadata = Validator(include_paths=[dep_filename.parent]).validate(
metadata={
"compatible": {"org": "google", "part": "foo"},
"supported-buses": ["i2c"],
"deps": [dep_filename.name],
"attributes": [
{
Expand Down Expand Up @@ -269,6 +319,7 @@ def test_channel_info_from_deps(self) -> None:
"org": "google",
"part": "foo",
},
"supported-buses": ["i2c"],
"attributes": [
{
"attribute": "sample_rate",
Expand Down
2 changes: 2 additions & 0 deletions pw_sensor/sensor.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ deps:
compatible:
org: "Google"
part: "pigweed"
supported-buses:
- "i2c"

0 comments on commit 8bb8089

Please sign in to comment.