src.bluetooth_sig.gatt.characteristics.base

Base class for GATT characteristics.

This module implements the core characteristic parsing and encoding system for Bluetooth GATT characteristics, following official Bluetooth SIG specifications.

Architecture

The implementation uses a multi-stage pipeline for parsing and encoding:

Parsing Pipeline (parse_value):
  1. Length validation (pre-decode)

  2. Raw integer extraction (little-endian per Bluetooth spec)

  3. Special value detection (sentinel values like 0x8000)

  4. Value decoding (via template or subclass override)

  5. Range validation (post-decode)

  6. Type validation

Encoding Pipeline (build_value):
  1. Type validation

  2. Range validation

  3. Value encoding (via template or subclass override)

  4. Length validation (post-encode)

YAML Metadata Resolution

Characteristic metadata is automatically resolved from Bluetooth SIG YAML specifications:

  • UUID, name, value type from assigned numbers registry

  • Units, resolution, and scaling factors (M × 10^d + b formula)

  • Special sentinel values (e.g., 0x8000 = “value is not known”)

  • Validation ranges and length constraints

Manual overrides (_manual_unit, _special_values, etc.) should only be used for: - Fixing incomplete or incorrect SIG specifications - Custom characteristics not in official registry - Performance optimizations

Template Composition

Characteristics use templates for reusable parsing logic via composition:

class TemperatureCharacteristic(BaseCharacteristic):

_template = Sint16Template(resolution=0.01, unit=”°C”) # No need to override decode_value() - template handles it

Subclasses only override decode_value() for custom logic that templates cannot handle. Templates take priority over YAML-derived extractors.

Validation Sources (Priority Order)

  1. Descriptor Valid Range - Device-reported constraints (highest priority)

  2. Class-level Attributes - Characteristic spec defaults (min_value, max_value)

  3. YAML-derived Ranges - Bluetooth SIG specification ranges (fallback)

Special Values

Sentinel values (like 0x8000 for “unknown”) bypass range and type validation since they represent non-numeric states. The gss_special_values property handles both unsigned (0x8000) and signed (-32768) interpretations for compatibility with different parsing contexts.

Byte Order

All multi-byte values use little-endian encoding per Bluetooth Core Specification.

Attributes

Name

Description

T

Classes

Name

Description

BaseCharacteristic

Base class for all GATT characteristics.

CharacteristicMeta

Metaclass to automatically handle template flags for characteristics.

SIGCharacteristicResolver

Resolves SIG characteristic information from YAML and registry.

ValidationConfig

Configuration for characteristic validation constraints.

Module Contents

class src.bluetooth_sig.gatt.characteristics.base.BaseCharacteristic(info: src.bluetooth_sig.types.CharacteristicInfo | None = None, validation: ValidationConfig | None = None, properties: list[src.bluetooth_sig.types.gatt_enums.GattProperty] | None = None)

Bases: abc.ABC, Generic[T]

Base class for all GATT characteristics.

Generic over T, the return type of _decode_value().

Automatically resolves UUID, unit, and value_type from Bluetooth SIG YAML specifications. Supports manual overrides via _manual_unit and _manual_value_type attributes.

Note: This class intentionally has >20 public methods as it provides the complete characteristic API including parsing, validation, UUID resolution, registry interaction, and metadata access. The methods are well-organized by functionality.

Validation Attributes (optional class-level declarations):

min_value: Minimum allowed value for parsed data max_value: Maximum allowed value for parsed data expected_length: Exact expected data length in bytes min_length: Minimum required data length in bytes max_length: Maximum allowed data length in bytes allow_variable_length: Whether variable length data is acceptable expected_type: Expected Python type for parsed values

Example usage in subclasses:
class ExampleCharacteristic(BaseCharacteristic):

‘’’Example showing validation attributes usage.’’’

# Declare validation constraints as class attributes expected_length = 2 min_value = 0 max_value = 65535 # UINT16_MAX expected_type = int

def _decode_value(self, data: bytearray) -> int:

# Just parse - validation happens automatically in parse_value return DataParser.parse_int16(data, 0, signed=False)

# Before: BatteryLevelCharacteristic with hardcoded validation # class BatteryLevelCharacteristic(BaseCharacteristic): # def _decode_value(self, data: bytearray) -> int: # if not data: # raise ValueError(“Battery level data must be at least 1 byte”) # level = data[0] # if not 0 <= level <= PERCENTAGE_MAX: # raise ValueError(f”Battery level must be 0-100, got {level}”) # return level

# After: BatteryLevelCharacteristic with declarative validation # class BatteryLevelCharacteristic(BaseCharacteristic): # expected_length = 1 # min_value = 0 # max_value = 100 # PERCENTAGE_MAX # expected_type = int # # def _decode_value(self, data: bytearray) -> int: # return data[0] # Validation happens automatically

add_descriptor(descriptor: src.bluetooth_sig.gatt.descriptors.BaseDescriptor) None

Add a descriptor to this characteristic.

Parameters:

descriptor – The descriptor instance to add

build_value(data: T | src.bluetooth_sig.types.SpecialValueResult, validate: bool = True) bytearray

Encode value or special value to characteristic bytes.

Parameters:
  • data – Value to encode (type T) or special value to encode

  • validate – Enable validation (type, range, length checks) Note: Special values bypass validation

Returns:

Encoded bytes ready for BLE write

Raises:

CharacteristicEncodeError – If encoding or validation fails

Examples

# Normal value data = char.build_value(37.5) # Returns: bytearray([0xAA, 0x0E])

# Special value (for testing/simulation) from bluetooth_sig.types import SpecialValueResult, SpecialValueType special = SpecialValueResult(

raw_value=0x8000, meaning=”value is not known”, value_type=SpecialValueType.NOT_KNOWN

) data = char.build_value(special) # Returns: bytearray([0x00, 0x80])

# With validation disabled (for debugging) data = char.build_value(200.0, validate=False) # Allows out-of-range

# Error handling try:

data = char.build_value(value)

except CharacteristicEncodeError as e:

print(f”Encode failed: {e}”)

can_notify() bool

Check if this characteristic supports notifications.

Returns:

True if the characteristic has a CCCD descriptor, False otherwise

encode_special(value_type: src.bluetooth_sig.types.SpecialValueType) bytearray

Encode a special value type to bytes (reverse lookup).

Raises ValueError if no raw value of that type is defined for this characteristic.

encode_special_by_meaning(meaning: str) bytearray

Encode a special value by a partial meaning string match.

Raises ValueError if no matching special value is found.

enhance_error_message_with_descriptors(base_message: str, ctx: src.bluetooth_sig.gatt.context.CharacteristicContext | None = None) str

Enhance error message with descriptor information for better debugging.

Parameters:
  • base_message – Original error message

  • ctx – Characteristic context containing descriptors

Returns:

Enhanced error message with descriptor context

classmethod get_allows_sig_override() bool

Check if this characteristic class allows overriding SIG characteristics.

Custom characteristics that need to override official Bluetooth SIG characteristics must set _allows_sig_override = True as a class attribute.

Returns:

True if SIG override is allowed, False otherwise.

get_byte_order_hint() str

Get byte order hint (Bluetooth SIG uses little-endian by convention).

get_cccd() src.bluetooth_sig.gatt.descriptors.BaseDescriptor | None

Get the Client Characteristic Configuration Descriptor (CCCD).

Returns:

CCCD descriptor instance if present, None otherwise

classmethod get_class_uuid() src.bluetooth_sig.types.uuid.BluetoothUUID | None

Get the characteristic UUID for this class without creating an instance.

This is the public API for registry and other modules to resolve UUIDs.

Returns:

BluetoothUUID if the class has a resolvable UUID, None otherwise.

classmethod get_configured_info() src.bluetooth_sig.types.CharacteristicInfo | None

Get the class-level configured CharacteristicInfo.

This provides public access to the _configured_info attribute that is set by __init_subclass__ for custom characteristics.

Returns:

CharacteristicInfo if configured, None otherwise

get_context_characteristic(ctx: src.bluetooth_sig.gatt.context.CharacteristicContext | None, characteristic_name: src.bluetooth_sig.types.gatt_enums.CharacteristicName | str | type[BaseCharacteristic[Any]]) Any

Find a characteristic in a context by name or class.

Note

Returns Any because the characteristic type is determined at runtime by characteristic_name. For type-safe access, use direct characteristic class instantiation instead of this lookup method.

Parameters:
  • ctx – Context containing other characteristics.

  • characteristic_name – Enum, string name, or characteristic class.

Returns:

Parsed characteristic value if found, None otherwise.

get_descriptor(uuid: str | src.bluetooth_sig.types.uuid.BluetoothUUID) src.bluetooth_sig.gatt.descriptors.BaseDescriptor | None

Get a descriptor by UUID.

Parameters:

uuid – Descriptor UUID (string or BluetoothUUID)

Returns:

Descriptor instance if found, None otherwise

get_descriptor_from_context(ctx: src.bluetooth_sig.gatt.context.CharacteristicContext | None, descriptor_class: type[src.bluetooth_sig.gatt.descriptors.BaseDescriptor]) src.bluetooth_sig.types.registry.descriptor_types.DescriptorData | None

Get a descriptor of the specified type from the context.

Parameters:
  • ctx – Characteristic context containing descriptors

  • descriptor_class – The descriptor class to look for (e.g., ValidRangeDescriptor)

Returns:

DescriptorData if found, None otherwise

get_descriptors() dict[str, src.bluetooth_sig.gatt.descriptors.BaseDescriptor]

Get all descriptors for this characteristic.

Returns:

Dict mapping descriptor UUID strings to descriptor instances

get_presentation_format_from_context(ctx: src.bluetooth_sig.gatt.context.CharacteristicContext | None = None) src.bluetooth_sig.gatt.descriptors.characteristic_presentation_format.CharacteristicPresentationFormatData | None

Get presentation format from descriptor context if available.

Parameters:

ctx – Characteristic context containing descriptors

Returns:

CharacteristicPresentationFormatData if present, None otherwise

get_special_value_meaning(raw_value: int) str | None

Get the human-readable meaning of a special value.

Parameters:

raw_value – The raw integer value to look up.

Returns:

The meaning string (e.g., “value is not known”), or None if not special.

get_special_value_type(raw_value: int) src.bluetooth_sig.types.SpecialValueType | None

Get the category of a special value.

Parameters:

raw_value – The raw integer value to classify.

Returns:

The SpecialValueType category, or None if not a special value.

get_user_description_from_context(ctx: src.bluetooth_sig.gatt.context.CharacteristicContext | None = None) str | None

Get user description from descriptor context if available.

Parameters:

ctx – Characteristic context containing descriptors

Returns:

User description string if present, None otherwise

get_valid_range_from_context(ctx: src.bluetooth_sig.gatt.context.CharacteristicContext | None = None) tuple[int | float, int | float] | None

Get valid range from descriptor context if available.

Parameters:

ctx – Characteristic context containing descriptors

Returns:

Tuple of (min, max) values if Valid Range descriptor present, None otherwise

get_yaml_data_type() str | None

Get the data type from YAML automation (e.g., ‘sint16’, ‘uint8’).

get_yaml_field_size() int | None

Get the field size in bytes from YAML automation.

get_yaml_resolution_text() str | None

Get the resolution description text from YAML automation.

get_yaml_unit_id() str | None

Get the Bluetooth SIG unit identifier from YAML automation.

is_signed_from_yaml() bool

Determine if the data type is signed based on YAML automation.

is_special_value(raw_value: int) bool

Check if a raw value is a special sentinel value.

Checks both manual overrides (_special_values class variable) and GSS-derived special values, with manual taking precedence.

Parameters:

raw_value – The raw integer value to check.

Returns:

True if this is a special sentinel value, False otherwise.

classmethod matches_uuid(uuid: str | src.bluetooth_sig.types.uuid.BluetoothUUID) bool

Check if this characteristic matches the given UUID.

parse_value(data: bytes | bytearray, ctx: src.bluetooth_sig.gatt.context.CharacteristicContext | None = None, validate: bool = True) T

Parse characteristic data.

Returns: Parsed value of type T :raises SpecialValueDetected: Special sentinel (0x8000=”unknown”, 0x7FFFFFFF=”NaN”) :raises CharacteristicParseError: Parse/validation failure

validate_value_against_descriptor_range(value: int | float, ctx: src.bluetooth_sig.gatt.context.CharacteristicContext | None = None) bool

Validate a value against descriptor-defined valid range.

Parameters:
  • value – Value to validate

  • ctx – Characteristic context containing descriptors

Returns:

True if value is within valid range or no range defined, False otherwise

allow_variable_length: bool = False
property description: str

Get the characteristic description from GSS specification.

property display_name: str

Get the display name for this characteristic.

Uses explicit _characteristic_name if set, otherwise falls back to class name.

expected_length: int | None = None
expected_type: type | None = None
property gss_special_values: dict[int, str]

Get special values from GSS specification.

Extracts all special value definitions (e.g., 0x8000=”value is not known”) from the GSS YAML specification for this characteristic.

GSS stores values as unsigned hex (e.g., 0x8000). For signed types, this method also includes the signed interpretation so lookups work with both parsed signed values and raw unsigned values.

Returns:

Dictionary mapping raw integer values to their human-readable meanings. Includes both unsigned and signed interpretations for applicable values.

property info: src.bluetooth_sig.types.CharacteristicInfo

Characteristic information.

last_parsed: T | None = None
max_length: int | None = None
max_value: int | float | None = None
min_length: int | None = None
min_value: int | float | None = None
property name: str

Get the characteristic name from _info.

property optional_dependencies: list[str]

Get resolved optional dependency UUID strings.

properties: list[src.bluetooth_sig.types.gatt_enums.GattProperty] = None
property required_dependencies: list[str]

Get resolved required dependency UUID strings.

property size: int | None

Get the size in bytes for this characteristic from YAML specifications.

Returns the field size from YAML automation if available, otherwise None. This is useful for determining the expected data length for parsing and encoding.

property spec: src.bluetooth_sig.types.registry.CharacteristicSpec | None

Get the full GSS specification with description and detailed metadata.

property unit: str

Get the unit of measurement from _info.

Returns empty string for characteristics without units (e.g., bitfields).

property uuid: src.bluetooth_sig.types.uuid.BluetoothUUID

Get the characteristic UUID from _info.

value_type: src.bluetooth_sig.types.gatt_enums.ValueType
property value_type_resolved: src.bluetooth_sig.types.gatt_enums.ValueType

Get the value type from _info.

class src.bluetooth_sig.gatt.characteristics.base.CharacteristicMeta

Bases: abc.ABCMeta

Metaclass to automatically handle template flags for characteristics.

class src.bluetooth_sig.gatt.characteristics.base.SIGCharacteristicResolver

Resolves SIG characteristic information from YAML and registry.

This class handles all SIG characteristic resolution logic, separating concerns from the BaseCharacteristic constructor. Uses shared utilities from the resolver module to avoid code duplication.

static resolve_for_class(char_class: type[BaseCharacteristic[Any]]) src.bluetooth_sig.types.CharacteristicInfo

Resolve CharacteristicInfo for a SIG characteristic class.

Parameters:

char_class – The characteristic class to resolve info for

Returns:

CharacteristicInfo with resolved UUID, name, value_type, unit

Raises:

UUIDResolutionError – If no UUID can be resolved for the class

static resolve_from_registry(char_class: type[BaseCharacteristic[Any]]) src.bluetooth_sig.types.CharacteristicInfo | None

Fallback to registry resolution using shared search strategy.

static resolve_yaml_spec_for_class(char_class: type[BaseCharacteristic[Any]]) src.bluetooth_sig.types.registry.CharacteristicSpec | None

Resolve YAML spec for a characteristic class using shared name variant logic.

camel_case_to_display_name
class src.bluetooth_sig.gatt.characteristics.base.ValidationConfig

Bases: msgspec.Struct

Configuration for characteristic validation constraints.

Groups validation parameters into a single, optional configuration object to simplify BaseCharacteristic constructor signatures.

allow_variable_length: bool = False
expected_length: int | None = None
expected_type: type | None = None
max_length: int | None = None
max_value: int | float | None = None
min_length: int | None = None
min_value: int | float | None = None
src.bluetooth_sig.gatt.characteristics.base.T