From 8bb808922dc58b517c2a2edc67d4649731b7b3b9 Mon Sep 17 00:00:00 2001 From: Yuval Peress Date: Fri, 30 Aug 2024 23:47:56 +0000 Subject: [PATCH] pw_sensor: Add supported bus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Commit-Queue: Auto-Submit Pigweed-Auto-Submit: Yuval Peress Reviewed-by: Taylor Cramer Lint: Lint 🤖 --- pw_sensor/py/docs.rst | 13 ++++ pw_sensor/py/pw_sensor/constants_generator.py | 23 ++++++- pw_sensor/py/pw_sensor/metadata_schema.json | 14 +++- pw_sensor/py/pw_sensor/resolved_schema.json | 11 +++ pw_sensor/py/pw_sensor/validator.py | 4 ++ pw_sensor/py/validator_test.py | 69 ++++++++++++++++--- pw_sensor/sensor.yaml | 2 + 7 files changed, 125 insertions(+), 11 deletions(-) diff --git a/pw_sensor/py/docs.rst b/pw_sensor/py/docs.rst index 7999a270f7..8c141e88d4 100644 --- a/pw_sensor/py/docs.rst +++ b/pw_sensor/py/docs.rst @@ -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: [] @@ -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 @@ -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 @@ -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: [] diff --git a/pw_sensor/py/pw_sensor/constants_generator.py b/pw_sensor/py/pw_sensor/constants_generator.py index 796e5d747b..a774415423 100644 --- a/pw_sensor/py/pw_sensor/constants_generator.py +++ b/pw_sensor/py/pw_sensor/constants_generator.py @@ -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] @@ -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.""" @@ -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 diff --git a/pw_sensor/py/pw_sensor/metadata_schema.json b/pw_sensor/py/pw_sensor/metadata_schema.json index 675b0f9897..ea363bd874 100644 --- a/pw_sensor/py/pw_sensor/metadata_schema.json +++ b/pw_sensor/py/pw_sensor/metadata_schema.json @@ -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", @@ -100,6 +111,7 @@ }, "additionalProperties": false, "required": [ - "compatible" + "compatible", + "supported-buses" ] } diff --git a/pw_sensor/py/pw_sensor/resolved_schema.json b/pw_sensor/py/pw_sensor/resolved_schema.json index ce270c2ea4..89cf404ba6 100644 --- a/pw_sensor/py/pw_sensor/resolved_schema.json +++ b/pw_sensor/py/pw_sensor/resolved_schema.json @@ -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", diff --git a/pw_sensor/py/pw_sensor/validator.py b/pw_sensor/py/pw_sensor/validator.py index 40350b0876..1d60597731 100644 --- a/pw_sensor/py/pw_sensor/validator.py +++ b/pw_sensor/py/pw_sensor/validator.py @@ -78,6 +78,8 @@ def validate(self, metadata: dict) -> dict: compatible: org: "Bosch" part: "BMA4xx + supported-buses: + - i2c channels: acceleration: [] die_temperature: [] @@ -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, diff --git a/pw_sensor/py/validator_test.py b/pw_sensor/py/validator_test.py index 10230f9a15..27ac272326 100644 --- a/pw_sensor/py/validator_test.py +++ b/pw_sensor/py/validator_test.py @@ -36,9 +36,10 @@ 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", @@ -46,29 +47,70 @@ def test_invalid_compatible_type(self) -> None: ) 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 @@ -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": [], @@ -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) @@ -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:", @@ -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" @@ -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": [ { @@ -269,6 +319,7 @@ def test_channel_info_from_deps(self) -> None: "org": "google", "part": "foo", }, + "supported-buses": ["i2c"], "attributes": [ { "attribute": "sample_rate", diff --git a/pw_sensor/sensor.yaml b/pw_sensor/sensor.yaml index 7ff79eca3c..083abda256 100644 --- a/pw_sensor/sensor.yaml +++ b/pw_sensor/sensor.yaml @@ -20,3 +20,5 @@ deps: compatible: org: "Google" part: "pigweed" +supported-buses: + - "i2c"