From 99ad8c0fd965aa0250f7d4925e9c033ca980fddd Mon Sep 17 00:00:00 2001 From: glopesdev Date: Mon, 30 Oct 2023 11:49:53 +0000 Subject: [PATCH 1/4] Add auto-generated device schema classes --- harp/model.py | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 harp/model.py diff --git a/harp/model.py b/harp/model.py new file mode 100644 index 0000000..5c5bb3f --- /dev/null +++ b/harp/model.py @@ -0,0 +1,180 @@ +# generated by datamodel-codegen: +# filename: device.json +# timestamp: 2023-10-30T11:46:57+00:00 + +from __future__ import annotations + +from enum import Enum +from typing import Any, Dict, List, Optional, Union + +from pydantic import BaseModel, ConfigDict, Field, RootModel, conint + + +class Type(Enum): + U8 = 'U8' + S8 = 'S8' + U16 = 'U16' + S16 = 'S16' + U32 = 'U32' + S32 = 'S32' + U64 = 'U64' + S64 = 'S64' + Float = 'Float' + + +class Access(Enum): + Read = 'Read' + Write = 'Write' + Event = 'Event' + + +class MaskValueItem(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + description: Optional[str] = Field( + None, description='Specifies a summary description of the mask value function.' + ) + + +class MaskValue(RootModel[Union[int, MaskValueItem]]): + root: Union[int, MaskValueItem] + + +class BitMask(BaseModel): + description: Optional[str] = Field( + None, description='Specifies a summary description of the bit mask function.' + ) + bits: Dict[str, MaskValue] + + +class GroupMask(BaseModel): + description: Optional[str] = Field( + None, description='Specifies a summary description of the group mask function.' + ) + values: Dict[str, MaskValue] + + +class MaskType(RootModel[str]): + root: str = Field( + ..., + description='Specifies the name of the bit mask or group mask used to represent the payload value.', + ) + + +class InterfaceType(RootModel[str]): + root: str = Field( + ..., + description='Specifies the name of the type used to represent the payload value in the high-level interface.', + ) + + +class Converter(Enum): + None_ = 'None' + Payload = 'Payload' + RawPayload = 'RawPayload' + + +class MinValue(RootModel[float]): + root: float = Field( + ..., + description='Specifies the minimum allowable value for the payload or payload member.', + ) + + +class MaxValue(RootModel[float]): + root: float = Field( + ..., + description='Specifies the maximum allowable value for the payload or payload member.', + ) + + +class DefaultValue(RootModel[float]): + root: float = Field( + ..., + description='Specifies the default value for the payload or payload member.', + ) + + +class PayloadMember(BaseModel): + mask: Optional[int] = Field( + None, + description='Specifies the mask used to read and write this payload member.', + ) + offset: Optional[int] = Field( + None, + description='Specifies the payload array offset where this payload member is stored.', + ) + description: Optional[str] = Field( + None, description='Specifies a summary description of the payload member.' + ) + minValue: Optional[MinValue] = None + maxValue: Optional[MaxValue] = None + defaultValue: Optional[DefaultValue] = None + maskType: Optional[MaskType] = None + interfaceType: Optional[InterfaceType] = None + converter: Optional[Converter] = None + + +class Visibility(Enum): + public = 'public' + private = 'private' + + +class Register(BaseModel): + address: conint(le=255) = Field( + ..., description='Specifies the unique 8-bit address of the register.' + ) + type: Type + length: Optional[conint(ge=1)] = Field( + None, description='Specifies the length of the register payload.' + ) + access: Union[Access, List[Access]] = Field( + ..., description='Specifies the expected use of the register.' + ) + description: Optional[str] = Field( + None, description='Specifies a summary description of the register function.' + ) + minValue: Optional[MinValue] = None + maxValue: Optional[MaxValue] = None + defaultValue: Optional[DefaultValue] = None + maskType: Optional[MaskType] = None + visibility: Optional[Visibility] = Field( + None, + description='Specifies whether the register function is exposed in the high-level interface.', + ) + volatile: Optional[bool] = Field( + None, + description='Specifies whether register values can be saved in non-volatile memory.', + ) + payloadSpec: Optional[Dict[str, PayloadMember]] = None + interfaceType: Optional[InterfaceType] = None + converter: Optional[Converter] = None + + +class Registers(BaseModel): + registers: Dict[str, Register] = Field( + ..., + description='Specifies the collection of registers implementing the device function.', + ) + bitMasks: Optional[Dict[str, BitMask]] = Field( + None, + description='Specifies the collection of masks available to be used with the different registers.', + ) + groupMasks: Optional[Dict[str, GroupMask]] = Field( + None, + description='Specifies the collection of group masks available to be used with the different registers.', + ) + + +class Model(Registers): + device: str = Field(..., description='Specifies the name of the device.') + whoAmI: int = Field( + ..., description='Specifies the unique identifier for this device type.' + ) + firmwareVersion: str = Field( + ..., description='Specifies the semantic version of the device firmware.' + ) + hardwareTargets: str = Field( + ..., description='Specifies the semantic version of the device hardware.' + ) From c16f140836cb0604b2d30dde5cbe798fa120d9d1 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Mon, 30 Oct 2023 11:53:44 +0000 Subject: [PATCH 2/4] Exclude auto-generated module from black --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 39cf14b..9b1cbdd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,5 +61,6 @@ extend-exclude = ''' ( ^/LICENSE ^/README.md + | harp/model.py ) ''' From 2c98398cae53a2d63a5fef44c9b5010467fdd0dd Mon Sep 17 00:00:00 2001 From: glopesdev Date: Mon, 30 Oct 2023 12:02:55 +0000 Subject: [PATCH 3/4] Add reflex regenerator schemas as submodule --- .gitmodules | 3 +++ reflex-generator | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 reflex-generator diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f289d19 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "reflex-generator"] + path = reflex-generator + url = https://github.com/harp-tech/reflex-generator.git diff --git a/reflex-generator b/reflex-generator new file mode 160000 index 0000000..44f2049 --- /dev/null +++ b/reflex-generator @@ -0,0 +1 @@ +Subproject commit 44f20497b1b5e69602ac3c230f2d4795cbed8e12 From eae770155ec2492c9baa8b0929b83c153f8fc656 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Mon, 30 Oct 2023 12:24:43 +0000 Subject: [PATCH 4/4] Add instructions on generating data models --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index b436e9e..f895eed 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,14 @@ # harp-python A low-level interface to data collected with the [Harp](https://harp-tech.org/) binary protocol. + +## Data model + +To regenerate Pydantic data models from device schema definitions, activate a virtual environment with `dev` dependencies, and run: + +``` +datamodel-codegen --input ./reflex-generator/schema/device.json --output harp/model.py --output-model-type pydantic_v2.BaseModel +``` + +> [!IMPORTANT] +> Currently code generation adds an unwanted field at the very end of the data model definition `registers: Optional[Any] = None`. This declaration needs to be removed for serialization to work properly.