Coverage for src/bluetooth_sig/gatt/characteristics/generic_access.py: 77%
39 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-30 00:10 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-30 00:10 +0000
1"""Generic Access Service characteristics."""
3from __future__ import annotations
5from ..context import CharacteristicContext
6from .base import BaseCharacteristic
7from .utils import DataParser
10class DeviceNameCharacteristic(BaseCharacteristic):
11 """Device Name characteristic (0x2A00).
13 org.bluetooth.characteristic.gap.device_name
15 Device Name characteristic.
16 """
18 _characteristic_name: str = "Device Name"
19 _manual_value_type = "string" # Override since decode_value returns str
21 def decode_value(self, data: bytearray, ctx: CharacteristicContext | None = None) -> str:
22 """Parse device name string.
24 Args:
25 data: Raw bytearray from BLE characteristic.
26 ctx: Optional CharacteristicContext providing surrounding context (may be None).
28 Returns:
29 Decoded device name string.
31 """
32 return DataParser.parse_utf8_string(data)
34 def encode_value(self, data: str) -> bytearray:
35 """Encode device name value back to bytes.
37 Args:
38 data: Device name as string
40 Returns:
41 Encoded bytes representing the device name (UTF-8)
43 """
44 # Encode as UTF-8 bytes
45 return bytearray(data.encode("utf-8"))
48class AppearanceCharacteristic(BaseCharacteristic):
49 """Appearance characteristic (0x2A01).
51 org.bluetooth.characteristic.gap.appearance
53 Appearance characteristic.
54 """
56 _characteristic_name: str = "Appearance"
57 _manual_value_type = "int" # Override since decode_value returns int
59 min_length = 2 # Appearance(2) fixed length
60 max_length = 2 # Appearance(2) fixed length
61 allow_variable_length: bool = False # Fixed length
63 def decode_value(self, data: bytearray, ctx: CharacteristicContext | None = None) -> int:
64 """Parse appearance value (uint16).
66 Args:
67 data: Raw bytearray from BLE characteristic.
68 ctx: Optional CharacteristicContext providing surrounding context (may be None).
70 Returns:
71 Parsed appearance as integer.
73 """
74 return DataParser.parse_int16(data, 0, signed=False)
76 def encode_value(self, data: int) -> bytearray:
77 """Encode appearance value back to bytes.
79 Args:
80 data: Appearance value as integer
82 Returns:
83 Encoded bytes representing the appearance
85 """
86 appearance = int(data)
87 return DataParser.encode_int16(appearance, signed=False)
90class ServiceChangedCharacteristic(BaseCharacteristic):
91 """Service Changed characteristic (0x2A05).
93 org.bluetooth.characteristic.gatt.service_changed
95 Service Changed characteristic.
96 """
98 _characteristic_name: str = "Service Changed"
99 _manual_value_type = "bytes" # Raw bytes for handle range
101 min_length = 4 # Start Handle(2) + End Handle(2)
102 max_length = 4 # Fixed length
103 allow_variable_length: bool = False # Fixed length
105 def decode_value(self, data: bytearray, ctx: CharacteristicContext | None = None) -> dict[str, int]:
106 """Parse service changed value.
108 Args:
109 data: Raw bytearray from BLE characteristic.
110 ctx: Optional CharacteristicContext providing surrounding context (may be None).
112 Returns:
113 Dict with start_handle and end_handle.
115 """
116 start_handle = DataParser.parse_int16(data, 0, signed=False)
117 end_handle = DataParser.parse_int16(data, 2, signed=False)
118 return {"start_handle": start_handle, "end_handle": end_handle}
120 def encode_value(self, data: dict[str, int]) -> bytearray:
121 """Encode service changed value back to bytes.
123 Args:
124 data: Dict with start_handle and end_handle
126 Returns:
127 Encoded bytes
129 """
130 start_handle = int(data["start_handle"])
131 end_handle = int(data["end_handle"])
132 result = bytearray()
133 result.extend(DataParser.encode_int16(start_handle, signed=False))
134 result.extend(DataParser.encode_int16(end_handle, signed=False))
135 return result