Coverage for src / bluetooth_sig / types / registry / common.py: 96%
82 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 11:17 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 11:17 +0000
1"""Core common types for Bluetooth SIG registry data structures."""
3from __future__ import annotations
5import msgspec
7from bluetooth_sig.types.registry.gss_characteristic import FieldSpec
8from bluetooth_sig.types.uuid import BluetoothUUID
11class FieldInfo(msgspec.Struct, frozen=True, kw_only=True):
12 """Field-related metadata from YAML."""
14 name: str | None = None
15 data_type: str | None = None
16 field_size: str | None = None
17 description: str | None = None
20class UnitMetadata(msgspec.Struct, frozen=True, kw_only=True):
21 """Unit-related metadata from characteristic YAML specifications.
23 This is embedded metadata within characteristic specs, distinct from
24 the Units registry which uses UUID-based entries.
26 Attributes:
27 unit_id: Full SIG unit identifier (e.g. ``org.bluetooth.unit.period.beats_per_minute``).
28 unit_symbol: Short SI symbol (e.g. ``'bpm'``, ``'°C'``).
29 unit_name: Human-readable long-form name (e.g. ``'beats per minute'``).
30 base_unit: Base unit identifier.
31 resolution_text: Resolution description from the GSS spec.
33 """
35 unit_id: str | None = None
36 unit_symbol: str | None = None
37 unit_name: str | None = None
38 base_unit: str | None = None
39 resolution_text: str | None = None
42class CharacteristicSpec(msgspec.Struct, kw_only=True):
43 """Characteristic specification from cross-file YAML references."""
45 uuid: BluetoothUUID
46 name: str
47 field_info: FieldInfo = msgspec.field(default_factory=FieldInfo)
48 unit_info: UnitMetadata = msgspec.field(default_factory=UnitMetadata)
49 description: str | None = None
50 structure: list[FieldSpec] = msgspec.field(default_factory=list)
52 @property
53 def data_type(self) -> str | None:
54 """Get data type from field info."""
55 return self.field_info.data_type if self.field_info else None
57 @property
58 def field_size(self) -> str | None:
59 """Get field size from field info."""
60 return self.field_info.field_size if self.field_info else None
62 @property
63 def unit_id(self) -> str | None:
64 """Get unit ID from unit info."""
65 return self.unit_info.unit_id if self.unit_info else None
67 @property
68 def unit_symbol(self) -> str | None:
69 """Get unit symbol from unit info."""
70 return self.unit_info.unit_symbol if self.unit_info else None
72 @property
73 def unit_name(self) -> str | None:
74 """Get human-readable unit name from unit info."""
75 return self.unit_info.unit_name if self.unit_info else None
77 @property
78 def base_unit(self) -> str | None:
79 """Get base unit from unit info."""
80 return self.unit_info.base_unit if self.unit_info else None
82 @property
83 def resolution_text(self) -> str | None:
84 """Get resolution text from unit info."""
85 return self.unit_info.resolution_text if self.unit_info else None
88class BaseUuidInfo(msgspec.Struct, frozen=True, kw_only=True):
89 """Minimal base info for all UUID-based registry entries.
91 Child classes should add an id field as needed (required or optional).
92 """
94 uuid: BluetoothUUID
95 name: str
98def generate_basic_aliases(info: BaseUuidInfo) -> set[str]:
99 """Generate a small set of common alias keys for a BaseUuidInfo.
101 Domain-specific heuristics remain the responsibility of the registry.
102 """
103 aliases: set[str] = set()
104 if info.name:
105 aliases.add(info.name.lower())
106 aliases.add(info.name.replace("_", " ").replace("-", " ").title())
107 id_val = getattr(info, "id", None)
108 if id_val:
109 aliases.add(id_val)
110 return {a for a in aliases if a and a.strip()}
113# Generic reusable Info classes for common YAML patterns
116class UuidIdInfo(BaseUuidInfo, frozen=True, kw_only=True):
117 """Standard registry info for simple UUID-based registries with org.bluetooth ID.
119 Extends BaseUuidInfo (uuid, name) with an id field for org.bluetooth identifiers.
120 Used by registries with uuid, name, id fields:
121 - browse_group_identifiers (uuid, name, id)
122 - declarations (uuid, name, id)
123 ...
124 """
126 id: str
129class ValueNameInfo(msgspec.Struct, frozen=True, kw_only=True):
130 """Generic info for registries with value and name fields.
132 Used by: coding_format, core_version, diacs, mws_channel_type,
133 namespace, namespaces, pcm_data_format, transport_layers, uri_schemes,
134 company_identifiers, and many others.
135 """
137 value: int
138 name: str
140 @property
141 def bit(self) -> int:
142 """Alias for value when used as bit position."""
143 return self.value
146class ValueNameReferenceInfo(msgspec.Struct, frozen=True, kw_only=True):
147 """Generic info for registries with value, name, and reference fields.
149 Used by: ad_types and similar registries with specification references.
150 """
152 value: int
153 name: str
154 reference: str
157class NameValueInfo(msgspec.Struct, frozen=True, kw_only=True):
158 """Generic info for registries with name and value fields (reversed order).
160 Used by: psm and similar registries where name comes before numeric value.
161 """
163 name: str
164 value: int
166 @property
167 def psm(self) -> int:
168 """Alias for value when used as PSM."""
169 return self.value
172class KeyNameInfo(msgspec.Struct, frozen=True, kw_only=True):
173 """Generic info for registries with key and name fields.
175 Used by: security_keyIDs and similar registries with non-numeric keys.
176 """
178 key: str
179 name: str
182class NameUuidTypeInfo(BaseUuidInfo, frozen=True, kw_only=True):
183 """Generic info for registries with name, uuid, and type fields.
185 Used by: mesh model UUIDs and similar registries with type classification.
186 Extends BaseUuidInfo to inherit uuid and name fields.
187 """
189 type: str
192class NameOpcodeTypeInfo(msgspec.Struct, frozen=True, kw_only=True):
193 """Generic info for registries with name, opcode, and type fields.
195 Used by: mesh opcodes and similar registries with opcode classification.
196 """
198 name: str
199 opcode: int
200 type: str