harp-python 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {harp-python-0.1.0 → harp-python-0.2.0}/.gitignore +1 -1
- {harp-python-0.1.0 → harp-python-0.2.0}/PKG-INFO +33 -16
- harp-python-0.2.0/README.md +58 -0
- harp-python-0.2.0/harp/__init__.py +3 -0
- harp-python-0.2.0/harp/io.py +129 -0
- harp-python-0.2.0/harp/model.py +202 -0
- harp-python-0.2.0/harp/reader.py +257 -0
- harp-python-0.2.0/harp/schema.py +53 -0
- {harp-python-0.1.0 → harp-python-0.2.0}/harp_python.egg-info/PKG-INFO +33 -16
- {harp-python-0.1.0 → harp-python-0.2.0}/harp_python.egg-info/SOURCES.txt +10 -1
- {harp-python-0.1.0 → harp-python-0.2.0}/harp_python.egg-info/requires.txt +1 -0
- {harp-python-0.1.0 → harp-python-0.2.0}/pyproject.toml +1 -1
- harp-python-0.2.0/tests/__init__.py +0 -0
- harp-python-0.2.0/tests/data/device.yml +35 -0
- harp-python-0.2.0/tests/data/device_0.bin +0 -0
- harp-python-0.2.0/tests/data/write_0.bin +0 -0
- harp-python-0.2.0/tests/params.py +41 -0
- harp-python-0.2.0/tests/test_io.py +55 -0
- harp-python-0.2.0/tests/test_reader.py +32 -0
- harp-python-0.2.0/tests/test_schema.py +17 -0
- harp-python-0.1.0/README.md +0 -42
- harp-python-0.1.0/harp/io.py +0 -58
- harp-python-0.1.0/harp/model.py +0 -184
- harp-python-0.1.0/harp/reader.py +0 -155
- harp-python-0.1.0/harp/schema.py +0 -31
- {harp-python-0.1.0 → harp-python-0.2.0}/.gitmodules +0 -0
- {harp-python-0.1.0 → harp-python-0.2.0}/LICENSE +0 -0
- {harp-python-0.1.0 → harp-python-0.2.0}/MANIFEST.in +0 -0
- {harp-python-0.1.0 → harp-python-0.2.0}/harp/common.yml +0 -0
- {harp-python-0.1.0 → harp-python-0.2.0}/harp_python.egg-info/dependency_links.txt +0 -0
- {harp-python-0.1.0 → harp-python-0.2.0}/harp_python.egg-info/top_level.txt +0 -0
- {harp-python-0.1.0 → harp-python-0.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: harp-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A low-level interface for loading binary Harp protocol data
|
|
5
5
|
Author-email: Goncalo Lopes <g.lopes@neurogears.org>
|
|
6
6
|
License: MIT License
|
|
@@ -20,6 +20,7 @@ Requires-Dist: pydantic-yaml
|
|
|
20
20
|
Requires-Dist: pandas
|
|
21
21
|
Provides-Extra: dev
|
|
22
22
|
Requires-Dist: datamodel-code-generator; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest; extra == "dev"
|
|
23
24
|
Requires-Dist: black; extra == "dev"
|
|
24
25
|
Provides-Extra: jupyter
|
|
25
26
|
Requires-Dist: ipykernel; extra == "jupyter"
|
|
@@ -31,35 +32,33 @@ A low-level interface to data collected with the [Harp](https://harp-tech.org/)
|
|
|
31
32
|
|
|
32
33
|
## Data model
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
The interface makes use of a Pydantic data model generated from Harp device schema definitions. The schema data classes are used to automatically generate binary readers for each device.
|
|
36
|
+
|
|
37
|
+
All binary data files from a single device need to be stored in the same folder alongside the device meta-schema, named `device.yml`. Each register file should have the following naming convention `<deviceName>_<registerAddress>.bin`.
|
|
38
|
+
|
|
39
|
+
For example, for a dataset collected with a `Behavior` device, you might have:
|
|
35
40
|
|
|
36
41
|
```
|
|
37
|
-
|
|
42
|
+
📦device.harp
|
|
43
|
+
┣ 📜Behavior_0.bin
|
|
44
|
+
┣ 📜Behavior_1.bin
|
|
45
|
+
...
|
|
46
|
+
┗ 📜device.yml
|
|
38
47
|
```
|
|
39
48
|
|
|
40
|
-
> [!IMPORTANT]
|
|
41
|
-
> 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.
|
|
42
|
-
|
|
43
49
|
## How to use
|
|
44
50
|
|
|
45
|
-
### Read Harp device schema from YML file
|
|
46
|
-
|
|
47
|
-
```python
|
|
48
|
-
from harp.schema import read_schema
|
|
49
|
-
schema = read_schema('device.yml')
|
|
50
|
-
```
|
|
51
|
-
|
|
52
51
|
### Create device reader object from schema
|
|
53
52
|
|
|
54
53
|
```python
|
|
55
|
-
|
|
56
|
-
reader = create_reader(
|
|
54
|
+
import harp
|
|
55
|
+
reader = harp.create_reader("device.harp")
|
|
57
56
|
```
|
|
58
57
|
|
|
59
58
|
### Read data from named register
|
|
60
59
|
|
|
61
60
|
```python
|
|
62
|
-
reader.OperationControl.read(
|
|
61
|
+
reader.OperationControl.read()
|
|
63
62
|
```
|
|
64
63
|
|
|
65
64
|
### Access register metadata
|
|
@@ -67,3 +66,21 @@ reader.OperationControl.read("data/Behavior_10.bin")
|
|
|
67
66
|
```python
|
|
68
67
|
reader.OperationControl.register.address
|
|
69
68
|
```
|
|
69
|
+
|
|
70
|
+
### Create device reader object with UTC datetime format
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
reader = harp.create_reader("device.harp", epoch=harp.REFERENCE_EPOCH)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Read data with message type information
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
reader.OperationControl.read(keep_type=True)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Read data from a specific file
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
reader.OperationControl.read("data/Behavior_10.bin")
|
|
86
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# harp-python
|
|
2
|
+
|
|
3
|
+
A low-level interface to data collected with the [Harp](https://harp-tech.org/) binary protocol.
|
|
4
|
+
|
|
5
|
+
## Data model
|
|
6
|
+
|
|
7
|
+
The interface makes use of a Pydantic data model generated from Harp device schema definitions. The schema data classes are used to automatically generate binary readers for each device.
|
|
8
|
+
|
|
9
|
+
All binary data files from a single device need to be stored in the same folder alongside the device meta-schema, named `device.yml`. Each register file should have the following naming convention `<deviceName>_<registerAddress>.bin`.
|
|
10
|
+
|
|
11
|
+
For example, for a dataset collected with a `Behavior` device, you might have:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
📦device.harp
|
|
15
|
+
┣ 📜Behavior_0.bin
|
|
16
|
+
┣ 📜Behavior_1.bin
|
|
17
|
+
...
|
|
18
|
+
┗ 📜device.yml
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## How to use
|
|
22
|
+
|
|
23
|
+
### Create device reader object from schema
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import harp
|
|
27
|
+
reader = harp.create_reader("device.harp")
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Read data from named register
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
reader.OperationControl.read()
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Access register metadata
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
reader.OperationControl.register.address
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Create device reader object with UTC datetime format
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
reader = harp.create_reader("device.harp", epoch=harp.REFERENCE_EPOCH)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Read data with message type information
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
reader.OperationControl.read(keep_type=True)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Read data from a specific file
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
reader.OperationControl.read("data/Behavior_10.bin")
|
|
58
|
+
```
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from enum import IntEnum
|
|
3
|
+
from os import PathLike
|
|
4
|
+
from typing import Any, BinaryIO, Optional, Union
|
|
5
|
+
from pandas._typing import Axes
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
REFERENCE_EPOCH = datetime(1904, 1, 1)
|
|
10
|
+
"""The reference epoch for UTC harp time."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MessageType(IntEnum):
|
|
14
|
+
"""Specifies the type of a Harp message."""
|
|
15
|
+
|
|
16
|
+
NA = 0
|
|
17
|
+
READ = 1
|
|
18
|
+
WRITE = 2
|
|
19
|
+
EVENT = 3
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_SECONDS_PER_TICK = 32e-6
|
|
23
|
+
_messagetypes = [type.name for type in MessageType]
|
|
24
|
+
_payloadtypes = {
|
|
25
|
+
1: np.dtype(np.uint8),
|
|
26
|
+
2: np.dtype(np.uint16),
|
|
27
|
+
4: np.dtype(np.uint32),
|
|
28
|
+
8: np.dtype(np.uint64),
|
|
29
|
+
129: np.dtype(np.int8),
|
|
30
|
+
130: np.dtype(np.int16),
|
|
31
|
+
132: np.dtype(np.int32),
|
|
32
|
+
136: np.dtype(np.int64),
|
|
33
|
+
68: np.dtype(np.float32),
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def read(
|
|
38
|
+
file: Union[str, bytes, PathLike[Any], BinaryIO],
|
|
39
|
+
address: Optional[int] = None,
|
|
40
|
+
dtype: Optional[np.dtype] = None,
|
|
41
|
+
length: Optional[int] = None,
|
|
42
|
+
columns: Optional[Axes] = None,
|
|
43
|
+
epoch: Optional[datetime] = None,
|
|
44
|
+
keep_type: bool = False,
|
|
45
|
+
):
|
|
46
|
+
"""Read single-register Harp data from the specified file.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
file
|
|
51
|
+
Open file object or filename containing binary data from
|
|
52
|
+
a single device register.
|
|
53
|
+
address
|
|
54
|
+
Expected register address. If specified, the address of
|
|
55
|
+
the first message in the file is used for validation.
|
|
56
|
+
dtype
|
|
57
|
+
Expected data type of the register payload. If specified, the
|
|
58
|
+
payload type of the first message in the file is used for validation.
|
|
59
|
+
length
|
|
60
|
+
Expected number of elements in register payload. If specified, the
|
|
61
|
+
payload length of the first message in the file is used for validation.
|
|
62
|
+
columns
|
|
63
|
+
The optional column labels to use for the data values.
|
|
64
|
+
epoch
|
|
65
|
+
Reference datetime at which time zero begins. If specified,
|
|
66
|
+
the result data frame will have a datetime index.
|
|
67
|
+
keep_type
|
|
68
|
+
Specifies whether to include a column with the message type.
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
A pandas data frame containing message data, sorted by time.
|
|
73
|
+
"""
|
|
74
|
+
data = np.fromfile(file, dtype=np.uint8)
|
|
75
|
+
if len(data) == 0:
|
|
76
|
+
return pd.DataFrame(
|
|
77
|
+
columns=columns, index=pd.Index([], dtype=np.float64, name="Time")
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if address is not None and address != data[2]:
|
|
81
|
+
raise ValueError(f"expected address {address} but got {data[2]}")
|
|
82
|
+
|
|
83
|
+
index = None
|
|
84
|
+
stride = data[1] + 2
|
|
85
|
+
nrows = len(data) // stride
|
|
86
|
+
payloadtype = data[4]
|
|
87
|
+
payloadoffset = 5
|
|
88
|
+
if payloadtype & 0x10 != 0:
|
|
89
|
+
seconds = np.ndarray(
|
|
90
|
+
nrows, dtype=np.uint32, buffer=data, offset=payloadoffset, strides=stride
|
|
91
|
+
)
|
|
92
|
+
payloadoffset += 4
|
|
93
|
+
micros = np.ndarray(
|
|
94
|
+
nrows, dtype=np.uint16, buffer=data, offset=payloadoffset, strides=stride
|
|
95
|
+
)
|
|
96
|
+
payloadoffset += 2
|
|
97
|
+
time = micros * _SECONDS_PER_TICK + seconds
|
|
98
|
+
payloadtype = payloadtype & ~0x10
|
|
99
|
+
if epoch is not None:
|
|
100
|
+
time = epoch + pd.to_timedelta(time, "s") # type: ignore
|
|
101
|
+
index = pd.Series(time)
|
|
102
|
+
index.name = "Time"
|
|
103
|
+
|
|
104
|
+
payloadsize = stride - payloadoffset - 1
|
|
105
|
+
payloadtype = _payloadtypes[payloadtype]
|
|
106
|
+
if dtype is not None and dtype != payloadtype:
|
|
107
|
+
raise ValueError(f"expected payload type {dtype} but got {payloadtype}")
|
|
108
|
+
|
|
109
|
+
elementsize = payloadtype.itemsize
|
|
110
|
+
payloadshape = (nrows, payloadsize // elementsize)
|
|
111
|
+
if length is not None and length != payloadshape[1]:
|
|
112
|
+
raise ValueError(f"expected payload length {length} but got {payloadshape[1]}")
|
|
113
|
+
|
|
114
|
+
payload = np.ndarray(
|
|
115
|
+
payloadshape,
|
|
116
|
+
dtype=payloadtype,
|
|
117
|
+
buffer=data,
|
|
118
|
+
offset=payloadoffset,
|
|
119
|
+
strides=(stride, elementsize),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
result = pd.DataFrame(payload, index=index, columns=columns)
|
|
123
|
+
if keep_type:
|
|
124
|
+
msgtype = np.ndarray(
|
|
125
|
+
nrows, dtype=np.uint8, buffer=data, offset=0, strides=stride
|
|
126
|
+
)
|
|
127
|
+
msgtype = pd.Categorical.from_codes(msgtype, categories=_messagetypes) # type: ignore
|
|
128
|
+
result[MessageType.__name__] = msgtype
|
|
129
|
+
return result
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# generated by datamodel-codegen:
|
|
2
|
+
# filename: device.json
|
|
3
|
+
# timestamp: 2023-10-30T11:46:57+00:00
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from typing import Dict, List, Optional, Union
|
|
9
|
+
from typing_extensions import Annotated
|
|
10
|
+
|
|
11
|
+
from pydantic import (
|
|
12
|
+
BaseModel,
|
|
13
|
+
BeforeValidator,
|
|
14
|
+
ConfigDict,
|
|
15
|
+
Field,
|
|
16
|
+
RootModel,
|
|
17
|
+
field_serializer,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PayloadType(str, Enum):
|
|
22
|
+
U8 = "uint8"
|
|
23
|
+
S8 = "int8"
|
|
24
|
+
U16 = "uint16"
|
|
25
|
+
S16 = "int16"
|
|
26
|
+
U32 = "uint32"
|
|
27
|
+
S32 = "int32"
|
|
28
|
+
U64 = "uint64"
|
|
29
|
+
S64 = "int64"
|
|
30
|
+
Float = "float32"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Access(Enum):
|
|
34
|
+
Read = "Read"
|
|
35
|
+
Write = "Write"
|
|
36
|
+
Event = "Event"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class MaskValueItem(BaseModel):
|
|
40
|
+
model_config = ConfigDict(
|
|
41
|
+
extra="forbid",
|
|
42
|
+
)
|
|
43
|
+
value: int = Field(..., description="Specifies the numerical mask value.")
|
|
44
|
+
description: Optional[str] = Field(
|
|
45
|
+
None, description="Specifies a summary description of the mask value function."
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def __int__(self):
|
|
49
|
+
return self.value
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class MaskValue(RootModel[Union[int, MaskValueItem]]):
|
|
53
|
+
root: Union[int, MaskValueItem]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class BitMask(BaseModel):
|
|
57
|
+
description: Optional[str] = Field(
|
|
58
|
+
None, description="Specifies a summary description of the bit mask function."
|
|
59
|
+
)
|
|
60
|
+
bits: Dict[str, MaskValue]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class GroupMask(BaseModel):
|
|
64
|
+
description: Optional[str] = Field(
|
|
65
|
+
None, description="Specifies a summary description of the group mask function."
|
|
66
|
+
)
|
|
67
|
+
values: Dict[str, MaskValue]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class MaskType(RootModel[str]):
|
|
71
|
+
root: str = Field(
|
|
72
|
+
...,
|
|
73
|
+
description="Specifies the name of the bit mask or group mask used to represent the payload value.",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class InterfaceType(RootModel[str]):
|
|
78
|
+
root: str = Field(
|
|
79
|
+
...,
|
|
80
|
+
description="Specifies the name of the type used to represent the payload value in the high-level interface.",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Converter(Enum):
|
|
85
|
+
None_ = "None"
|
|
86
|
+
Payload = "Payload"
|
|
87
|
+
RawPayload = "RawPayload"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class MinValue(RootModel[float]):
|
|
91
|
+
root: float = Field(
|
|
92
|
+
...,
|
|
93
|
+
description="Specifies the minimum allowable value for the payload or payload member.",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class MaxValue(RootModel[float]):
|
|
98
|
+
root: float = Field(
|
|
99
|
+
...,
|
|
100
|
+
description="Specifies the maximum allowable value for the payload or payload member.",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class DefaultValue(RootModel[float]):
|
|
105
|
+
root: float = Field(
|
|
106
|
+
...,
|
|
107
|
+
description="Specifies the default value for the payload or payload member.",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class PayloadMember(BaseModel):
|
|
112
|
+
mask: Optional[int] = Field(
|
|
113
|
+
None,
|
|
114
|
+
description="Specifies the mask used to read and write this payload member.",
|
|
115
|
+
)
|
|
116
|
+
offset: Optional[int] = Field(
|
|
117
|
+
None,
|
|
118
|
+
description="Specifies the payload array offset where this payload member is stored.",
|
|
119
|
+
)
|
|
120
|
+
description: Optional[str] = Field(
|
|
121
|
+
None, description="Specifies a summary description of the payload member."
|
|
122
|
+
)
|
|
123
|
+
minValue: Optional[MinValue] = None
|
|
124
|
+
maxValue: Optional[MaxValue] = None
|
|
125
|
+
defaultValue: Optional[DefaultValue] = None
|
|
126
|
+
maskType: Optional[MaskType] = None
|
|
127
|
+
interfaceType: Optional[InterfaceType] = None
|
|
128
|
+
converter: Optional[Converter] = None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class Visibility(Enum):
|
|
132
|
+
public = "public"
|
|
133
|
+
private = "private"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class Register(BaseModel):
|
|
137
|
+
address: Annotated[
|
|
138
|
+
int,
|
|
139
|
+
Field(
|
|
140
|
+
le=255, description="Specifies the unique 8-bit address of the register."
|
|
141
|
+
),
|
|
142
|
+
]
|
|
143
|
+
type: Annotated[PayloadType, BeforeValidator(lambda v: PayloadType[v])]
|
|
144
|
+
length: Annotated[
|
|
145
|
+
Optional[int],
|
|
146
|
+
Field(
|
|
147
|
+
ge=1, default=1, description="Specifies the length of the register payload."
|
|
148
|
+
),
|
|
149
|
+
]
|
|
150
|
+
access: Union[Access, List[Access]] = Field(
|
|
151
|
+
..., description="Specifies the expected use of the register."
|
|
152
|
+
)
|
|
153
|
+
description: Optional[str] = Field(
|
|
154
|
+
None, description="Specifies a summary description of the register function."
|
|
155
|
+
)
|
|
156
|
+
minValue: Optional[MinValue] = None
|
|
157
|
+
maxValue: Optional[MaxValue] = None
|
|
158
|
+
defaultValue: Optional[DefaultValue] = None
|
|
159
|
+
maskType: Optional[MaskType] = None
|
|
160
|
+
visibility: Optional[Visibility] = Field(
|
|
161
|
+
None,
|
|
162
|
+
description="Specifies whether the register function is exposed in the high-level interface.",
|
|
163
|
+
)
|
|
164
|
+
volatile: Optional[bool] = Field(
|
|
165
|
+
None,
|
|
166
|
+
description="Specifies whether register values can be saved in non-volatile memory.",
|
|
167
|
+
)
|
|
168
|
+
payloadSpec: Optional[Dict[str, PayloadMember]] = None
|
|
169
|
+
interfaceType: Optional[InterfaceType] = None
|
|
170
|
+
converter: Optional[Converter] = None
|
|
171
|
+
|
|
172
|
+
@field_serializer("type")
|
|
173
|
+
def _serialize_type(self, type: PayloadType):
|
|
174
|
+
return type.name
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class Registers(BaseModel):
|
|
178
|
+
registers: Dict[str, Register] = Field(
|
|
179
|
+
...,
|
|
180
|
+
description="Specifies the collection of registers implementing the device function.",
|
|
181
|
+
)
|
|
182
|
+
bitMasks: Optional[Dict[str, BitMask]] = Field(
|
|
183
|
+
None,
|
|
184
|
+
description="Specifies the collection of masks available to be used with the different registers.",
|
|
185
|
+
)
|
|
186
|
+
groupMasks: Optional[Dict[str, GroupMask]] = Field(
|
|
187
|
+
None,
|
|
188
|
+
description="Specifies the collection of group masks available to be used with the different registers.",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class Model(Registers):
|
|
193
|
+
device: str = Field(..., description="Specifies the name of the device.")
|
|
194
|
+
whoAmI: int = Field(
|
|
195
|
+
..., description="Specifies the unique identifier for this device type."
|
|
196
|
+
)
|
|
197
|
+
firmwareVersion: str = Field(
|
|
198
|
+
..., description="Specifies the semantic version of the device firmware."
|
|
199
|
+
)
|
|
200
|
+
hardwareTargets: str = Field(
|
|
201
|
+
..., description="Specifies the semantic version of the device hardware."
|
|
202
|
+
)
|