Coverage for src / bluetooth_sig / gatt / characteristics / templates / numeric.py: 75%

133 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-18 11:17 +0000

1"""Basic integer templates for unsigned and signed integer parsing. 

2 

3Covers Uint8, Sint8, Uint16, Sint16, Uint24, Uint32, Uint48 templates. 

4""" 

5 

6from __future__ import annotations 

7 

8from ...constants import ( 

9 SINT8_MAX, 

10 SINT8_MIN, 

11 SINT16_MAX, 

12 SINT16_MIN, 

13 UINT8_MAX, 

14 UINT16_MAX, 

15 UINT24_MAX, 

16 UINT32_MAX, 

17 UINT48_MAX, 

18) 

19from ...context import CharacteristicContext 

20from ...exceptions import InsufficientDataError 

21from ..utils.extractors import ( 

22 SINT8, 

23 SINT16, 

24 UINT8, 

25 UINT16, 

26 UINT24, 

27 UINT32, 

28 UINT48, 

29 RawExtractor, 

30) 

31from ..utils.translators import ( 

32 IDENTITY, 

33 ValueTranslator, 

34) 

35from .base import CodingTemplate 

36 

37 

38class Uint8Template(CodingTemplate[int]): 

39 """Template for 8-bit unsigned integer parsing (0-255).""" 

40 

41 @property 

42 def data_size(self) -> int: 

43 """Size: 1 byte.""" 

44 return 1 

45 

46 @property 

47 def extractor(self) -> RawExtractor: 

48 """Get uint8 extractor.""" 

49 return UINT8 

50 

51 @property 

52 def translator(self) -> ValueTranslator[int]: 

53 """Return identity translator for no scaling.""" 

54 return IDENTITY 

55 

56 def decode_value( 

57 self, data: bytearray, offset: int = 0, ctx: CharacteristicContext | None = None, *, validate: bool = True 

58 ) -> int: 

59 """Parse 8-bit unsigned integer.""" 

60 if validate and len(data) < offset + 1: 

61 raise InsufficientDataError("uint8", data[offset:], 1) 

62 return self.extractor.extract(data, offset) 

63 

64 def encode_value(self, value: int, *, validate: bool = True) -> bytearray: 

65 """Encode uint8 value to bytes.""" 

66 if validate and not 0 <= value <= UINT8_MAX: 

67 raise ValueError(f"Value {value} out of range for uint8 (0-{UINT8_MAX})") 

68 return self.extractor.pack(value) 

69 

70 

71class Sint8Template(CodingTemplate[int]): 

72 """Template for 8-bit signed integer parsing (-128 to 127).""" 

73 

74 @property 

75 def data_size(self) -> int: 

76 """Size: 1 byte.""" 

77 return 1 

78 

79 @property 

80 def extractor(self) -> RawExtractor: 

81 """Get sint8 extractor.""" 

82 return SINT8 

83 

84 @property 

85 def translator(self) -> ValueTranslator[int]: 

86 """Return identity translator for no scaling.""" 

87 return IDENTITY 

88 

89 def decode_value( 

90 self, data: bytearray, offset: int = 0, ctx: CharacteristicContext | None = None, *, validate: bool = True 

91 ) -> int: 

92 """Parse 8-bit signed integer.""" 

93 if validate and len(data) < offset + 1: 

94 raise InsufficientDataError("sint8", data[offset:], 1) 

95 return self.extractor.extract(data, offset) 

96 

97 def encode_value(self, value: int, *, validate: bool = True) -> bytearray: 

98 """Encode sint8 value to bytes.""" 

99 if validate and not SINT8_MIN <= value <= SINT8_MAX: 

100 raise ValueError(f"Value {value} out of range for sint8 ({SINT8_MIN} to {SINT8_MAX})") 

101 return self.extractor.pack(value) 

102 

103 

104class Uint16Template(CodingTemplate[int]): 

105 """Template for 16-bit unsigned integer parsing (0-65535).""" 

106 

107 @property 

108 def data_size(self) -> int: 

109 """Size: 2 bytes.""" 

110 return 2 

111 

112 @property 

113 def extractor(self) -> RawExtractor: 

114 """Get uint16 extractor.""" 

115 return UINT16 

116 

117 @property 

118 def translator(self) -> ValueTranslator[int]: 

119 """Return identity translator for no scaling.""" 

120 return IDENTITY 

121 

122 def decode_value( 

123 self, data: bytearray, offset: int = 0, ctx: CharacteristicContext | None = None, *, validate: bool = True 

124 ) -> int: 

125 """Parse 16-bit unsigned integer.""" 

126 if validate and len(data) < offset + 2: 

127 raise InsufficientDataError("uint16", data[offset:], 2) 

128 return self.extractor.extract(data, offset) 

129 

130 def encode_value(self, value: int, *, validate: bool = True) -> bytearray: 

131 """Encode uint16 value to bytes.""" 

132 if validate and not 0 <= value <= UINT16_MAX: 

133 raise ValueError(f"Value {value} out of range for uint16 (0-{UINT16_MAX})") 

134 return self.extractor.pack(value) 

135 

136 

137class Sint16Template(CodingTemplate[int]): 

138 """Template for 16-bit signed integer parsing (-32768 to 32767).""" 

139 

140 @property 

141 def data_size(self) -> int: 

142 """Size: 2 bytes.""" 

143 return 2 

144 

145 @property 

146 def extractor(self) -> RawExtractor: 

147 """Get sint16 extractor.""" 

148 return SINT16 

149 

150 @property 

151 def translator(self) -> ValueTranslator[int]: 

152 """Return identity translator for no scaling.""" 

153 return IDENTITY 

154 

155 def decode_value( 

156 self, data: bytearray, offset: int = 0, ctx: CharacteristicContext | None = None, *, validate: bool = True 

157 ) -> int: 

158 """Parse 16-bit signed integer.""" 

159 if validate and len(data) < offset + 2: 

160 raise InsufficientDataError("sint16", data[offset:], 2) 

161 return self.extractor.extract(data, offset) 

162 

163 def encode_value(self, value: int, *, validate: bool = True) -> bytearray: 

164 """Encode sint16 value to bytes.""" 

165 if validate and not SINT16_MIN <= value <= SINT16_MAX: 

166 raise ValueError(f"Value {value} out of range for sint16 ({SINT16_MIN} to {SINT16_MAX})") 

167 return self.extractor.pack(value) 

168 

169 

170class Uint24Template(CodingTemplate[int]): 

171 """Template for 24-bit unsigned integer parsing (0-16777215).""" 

172 

173 @property 

174 def data_size(self) -> int: 

175 """Size: 3 bytes.""" 

176 return 3 

177 

178 @property 

179 def extractor(self) -> RawExtractor: 

180 """Get uint24 extractor.""" 

181 return UINT24 

182 

183 @property 

184 def translator(self) -> ValueTranslator[int]: 

185 """Return identity translator for no scaling.""" 

186 return IDENTITY 

187 

188 def decode_value( 

189 self, data: bytearray, offset: int = 0, ctx: CharacteristicContext | None = None, *, validate: bool = True 

190 ) -> int: 

191 """Parse 24-bit unsigned integer.""" 

192 if validate and len(data) < offset + 3: 

193 raise InsufficientDataError("uint24", data[offset:], 3) 

194 return self.extractor.extract(data, offset) 

195 

196 def encode_value(self, value: int, *, validate: bool = True) -> bytearray: 

197 """Encode uint24 value to bytes.""" 

198 if validate and not 0 <= value <= UINT24_MAX: 

199 raise ValueError(f"Value {value} out of range for uint24 (0-{UINT24_MAX})") 

200 return self.extractor.pack(value) 

201 

202 

203class Uint32Template(CodingTemplate[int]): 

204 """Template for 32-bit unsigned integer parsing.""" 

205 

206 @property 

207 def data_size(self) -> int: 

208 """Size: 4 bytes.""" 

209 return 4 

210 

211 @property 

212 def extractor(self) -> RawExtractor: 

213 """Get uint32 extractor.""" 

214 return UINT32 

215 

216 @property 

217 def translator(self) -> ValueTranslator[int]: 

218 """Return identity translator for no scaling.""" 

219 return IDENTITY 

220 

221 def decode_value( 

222 self, data: bytearray, offset: int = 0, ctx: CharacteristicContext | None = None, *, validate: bool = True 

223 ) -> int: 

224 """Parse 32-bit unsigned integer.""" 

225 if validate and len(data) < offset + 4: 

226 raise InsufficientDataError("uint32", data[offset:], 4) 

227 return self.extractor.extract(data, offset) 

228 

229 def encode_value(self, value: int, *, validate: bool = True) -> bytearray: 

230 """Encode uint32 value to bytes.""" 

231 if validate and not 0 <= value <= UINT32_MAX: 

232 raise ValueError(f"Value {value} out of range for uint32 (0-{UINT32_MAX})") 

233 return self.extractor.pack(value) 

234 

235 

236class Uint48Template(CodingTemplate[int]): 

237 """Template for 48-bit unsigned integer parsing (0-281474976710655).""" 

238 

239 @property 

240 def data_size(self) -> int: 

241 """Size: 6 bytes.""" 

242 return 6 

243 

244 @property 

245 def extractor(self) -> RawExtractor: 

246 """Get uint48 extractor.""" 

247 return UINT48 

248 

249 @property 

250 def translator(self) -> ValueTranslator[int]: 

251 """Return identity translator for no scaling.""" 

252 return IDENTITY 

253 

254 def decode_value( 

255 self, data: bytearray, offset: int = 0, ctx: CharacteristicContext | None = None, *, validate: bool = True 

256 ) -> int: 

257 """Parse 48-bit unsigned integer.""" 

258 if validate and len(data) < offset + 6: 

259 raise InsufficientDataError("uint48", data[offset:], 6) 

260 return self.extractor.extract(data, offset) 

261 

262 def encode_value(self, value: int, *, validate: bool = True) -> bytearray: 

263 """Encode uint48 value to bytes.""" 

264 if validate and not 0 <= value <= UINT48_MAX: 

265 raise ValueError(f"Value {value} out of range for uint48 (0-{UINT48_MAX})") 

266 return self.extractor.pack(value)