Coverage for src / bluetooth_sig / gatt / characteristics / new_alert.py: 97%
35 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 20:14 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 20:14 +0000
1"""New Alert characteristic (0x2A46) implementation.
3Represents a new alert with category, count, and optional text information.
4Used by Alert Notification Service (0x1811).
6Based on Bluetooth SIG GATT Specification:
7- New Alert: Variable length (Category ID + Number of New Alert + Text String)
8"""
10from __future__ import annotations
12import msgspec
14from ...types import ALERT_TEXT_MAX_LENGTH, AlertCategoryID
15from ..context import CharacteristicContext
16from .base import BaseCharacteristic
17from .utils import DataParser
20class NewAlertData(msgspec.Struct):
21 """New Alert characteristic data structure."""
23 category_id: AlertCategoryID
24 number_of_new_alert: int # 0-255
25 text_string_information: str # 0-18 characters
28class NewAlertCharacteristic(BaseCharacteristic[NewAlertData]):
29 """New Alert characteristic (0x2A46).
31 Represents the category, count, and brief text for a new alert.
33 Structure (variable length):
34 - Category ID: uint8 (0=Simple Alert, 1=Email, etc.)
35 - Number of New Alert: uint8 (0-255, count of new alerts)
36 - Text String Information: utf8s (0-18 characters, optional brief text)
38 Used by Alert Notification Service (0x1811).
39 """
41 min_length: int = 2 # Category ID(1) + Number of New Alert(1)
42 allow_variable_length: bool = True # Optional text string
44 def _decode_value(self, data: bytearray, ctx: CharacteristicContext | None = None) -> NewAlertData:
45 """Decode New Alert data from bytes.
47 Args:
48 data: Raw characteristic data (minimum 2 bytes)
49 ctx: Optional characteristic context
51 Returns:
52 NewAlertData with all fields
54 Raises:
55 ValueError: If data contains invalid values
57 """
58 # Parse Category ID (1 byte)
59 category_id_raw = DataParser.parse_int8(data, 0, signed=False)
60 category_id = AlertCategoryID(category_id_raw)
62 # Parse Number of New Alert (1 byte)
63 number_of_new_alert = DataParser.parse_int8(data, 1, signed=False)
65 # Parse Text String Information (remaining bytes, max ALERT_TEXT_MAX_LENGTH characters)
66 text_string_information = ""
67 if len(data) > 2:
68 text_bytes = data[2:]
69 if len(text_bytes) > ALERT_TEXT_MAX_LENGTH:
70 raise ValueError(f"Text string too long: {len(text_bytes)} bytes (max {ALERT_TEXT_MAX_LENGTH})")
71 text_string_information = text_bytes.decode("utf-8", errors="replace")
73 return NewAlertData(
74 category_id=category_id,
75 number_of_new_alert=number_of_new_alert,
76 text_string_information=text_string_information,
77 )
79 def _encode_value(self, data: NewAlertData) -> bytearray:
80 """Encode New Alert data to bytes.
82 Args:
83 data: NewAlertData to encode
85 Returns:
86 Encoded new alert (variable length)
88 Raises:
89 ValueError: If data contains invalid values
91 """
92 result = bytearray()
94 # Encode Category ID (1 byte)
95 category_id_value = int(data.category_id)
96 result.append(category_id_value)
98 # Encode Number of New Alert (1 byte)
99 result.append(data.number_of_new_alert)
101 # Encode Text String Information (utf-8)
102 if data.text_string_information:
103 text_bytes = data.text_string_information.encode("utf-8")
104 if len(text_bytes) > ALERT_TEXT_MAX_LENGTH:
105 raise ValueError(f"Text string too long: {len(text_bytes)} bytes (max {ALERT_TEXT_MAX_LENGTH})")
106 result.extend(text_bytes)
108 return result