Coverage for src / bluetooth_sig / types / registry / common.py: 97%

78 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-11 20:14 +0000

1"""Core common types for Bluetooth SIG registry data structures.""" 

2 

3from __future__ import annotations 

4 

5import msgspec 

6 

7from bluetooth_sig.types.registry.gss_characteristic import FieldSpec 

8from bluetooth_sig.types.uuid import BluetoothUUID 

9 

10 

11class FieldInfo(msgspec.Struct, frozen=True, kw_only=True): 

12 """Field-related metadata from YAML.""" 

13 

14 name: str | None = None 

15 data_type: str | None = None 

16 field_size: str | None = None 

17 description: str | None = None 

18 

19 

20class UnitMetadata(msgspec.Struct, frozen=True, kw_only=True): 

21 """Unit-related metadata from characteristic YAML specifications. 

22 

23 This is embedded metadata within characteristic specs, distinct from 

24 the Units registry which uses UUID-based entries. 

25 """ 

26 

27 unit_id: str | None = None 

28 unit_symbol: str | None = None 

29 base_unit: str | None = None 

30 resolution_text: str | None = None 

31 

32 

33class CharacteristicSpec(msgspec.Struct, kw_only=True): 

34 """Characteristic specification from cross-file YAML references.""" 

35 

36 uuid: BluetoothUUID 

37 name: str 

38 field_info: FieldInfo = msgspec.field(default_factory=FieldInfo) 

39 unit_info: UnitMetadata = msgspec.field(default_factory=UnitMetadata) 

40 description: str | None = None 

41 structure: list[FieldSpec] = msgspec.field(default_factory=list) 

42 

43 @property 

44 def data_type(self) -> str | None: 

45 """Get data type from field info.""" 

46 return self.field_info.data_type if self.field_info else None 

47 

48 @property 

49 def field_size(self) -> str | None: 

50 """Get field size from field info.""" 

51 return self.field_info.field_size if self.field_info else None 

52 

53 @property 

54 def unit_id(self) -> str | None: 

55 """Get unit ID from unit info.""" 

56 return self.unit_info.unit_id if self.unit_info else None 

57 

58 @property 

59 def unit_symbol(self) -> str | None: 

60 """Get unit symbol from unit info.""" 

61 return self.unit_info.unit_symbol if self.unit_info else None 

62 

63 @property 

64 def base_unit(self) -> str | None: 

65 """Get base unit from unit info.""" 

66 return self.unit_info.base_unit if self.unit_info else None 

67 

68 @property 

69 def resolution_text(self) -> str | None: 

70 """Get resolution text from unit info.""" 

71 return self.unit_info.resolution_text if self.unit_info else None 

72 

73 

74class BaseUuidInfo(msgspec.Struct, frozen=True, kw_only=True): 

75 """Minimal base info for all UUID-based registry entries. 

76 

77 Child classes should add an id field as needed (required or optional). 

78 """ 

79 

80 uuid: BluetoothUUID 

81 name: str 

82 

83 

84def generate_basic_aliases(info: BaseUuidInfo) -> set[str]: 

85 """Generate a small set of common alias keys for a BaseUuidInfo. 

86 

87 Domain-specific heuristics remain the responsibility of the registry. 

88 """ 

89 aliases: set[str] = set() 

90 if info.name: 

91 aliases.add(info.name.lower()) 

92 aliases.add(info.name.replace("_", " ").replace("-", " ").title()) 

93 id_val = getattr(info, "id", None) 

94 if id_val: 

95 aliases.add(id_val) 

96 return {a for a in aliases if a and a.strip()} 

97 

98 

99# Generic reusable Info classes for common YAML patterns 

100 

101 

102class UuidIdInfo(BaseUuidInfo, frozen=True, kw_only=True): 

103 """Standard registry info for simple UUID-based registries with org.bluetooth ID. 

104 

105 Extends BaseUuidInfo (uuid, name) with an id field for org.bluetooth identifiers. 

106 Used by registries with uuid, name, id fields: 

107 - browse_group_identifiers (uuid, name, id) 

108 - declarations (uuid, name, id) 

109 ... 

110 """ 

111 

112 id: str 

113 

114 

115class ValueNameInfo(msgspec.Struct, frozen=True, kw_only=True): 

116 """Generic info for registries with value and name fields. 

117 

118 Used by: coding_format, core_version, diacs, mws_channel_type, 

119 namespace, namespaces, pcm_data_format, transport_layers, uri_schemes, 

120 company_identifiers, and many others. 

121 """ 

122 

123 value: int 

124 name: str 

125 

126 @property 

127 def bit(self) -> int: 

128 """Alias for value when used as bit position.""" 

129 return self.value 

130 

131 

132class ValueNameReferenceInfo(msgspec.Struct, frozen=True, kw_only=True): 

133 """Generic info for registries with value, name, and reference fields. 

134 

135 Used by: ad_types and similar registries with specification references. 

136 """ 

137 

138 value: int 

139 name: str 

140 reference: str 

141 

142 

143class NameValueInfo(msgspec.Struct, frozen=True, kw_only=True): 

144 """Generic info for registries with name and value fields (reversed order). 

145 

146 Used by: psm and similar registries where name comes before numeric value. 

147 """ 

148 

149 name: str 

150 value: int 

151 

152 @property 

153 def psm(self) -> int: 

154 """Alias for value when used as PSM.""" 

155 return self.value 

156 

157 

158class KeyNameInfo(msgspec.Struct, frozen=True, kw_only=True): 

159 """Generic info for registries with key and name fields. 

160 

161 Used by: security_keyIDs and similar registries with non-numeric keys. 

162 """ 

163 

164 key: str 

165 name: str 

166 

167 

168class NameUuidTypeInfo(BaseUuidInfo, frozen=True, kw_only=True): 

169 """Generic info for registries with name, uuid, and type fields. 

170 

171 Used by: mesh model UUIDs and similar registries with type classification. 

172 Extends BaseUuidInfo to inherit uuid and name fields. 

173 """ 

174 

175 type: str 

176 

177 

178class NameOpcodeTypeInfo(msgspec.Struct, frozen=True, kw_only=True): 

179 """Generic info for registries with name, opcode, and type fields. 

180 

181 Used by: mesh opcodes and similar registries with opcode classification. 

182 """ 

183 

184 name: str 

185 opcode: int 

186 type: str