Source code for bluetooth_clocks.devices.current_time_service

"""Bluetooth clock support for devices implementing the Current Time Service.

This includes the PineTime with InfiniTime firmware.
"""
from __future__ import annotations

import struct
from datetime import datetime
from typing import TYPE_CHECKING
from uuid import UUID

if TYPE_CHECKING:
    from bleak.backends.device import BLEDevice
    from bleak.backends.scanner import AdvertisementData

from bluetooth_clocks import MICROSECONDS, BluetoothClock
from bluetooth_clocks.exceptions import InvalidTimeBytesError


[docs] class CurrentTimeService(BluetoothClock): """Bluetooth clock support for devices implementing the Current Time Service. This implements the standardized Bluetooth service Current Time Service (https://www.bluetooth.com/specifications/specs/current-time-service-1-1/). """ DEVICE_TYPE = "Current Time Service" SERVICE_UUID = UUID("00001805-0000-1000-8000-00805f9b34fb") CHAR_UUID = UUID("00002a2b-0000-1000-8000-00805f9b34fb") TIME_GET_FORMAT = "<HBBBBBBB" """The format string to convert bytes read from the Current Time Service to a time. This starts with an unsigned short in little-endian format, followed by seven bytes. """ TIME_SET_FORMAT = "<HBBBBBBBB" """The format string to convert a time to bytes written to the device. This starts with an unsigned short in little-endian format, followed by eight bytes. """ WRITE_WITH_RESPONSE = True """Writing the time to the Current Time Service needs write with response."""
[docs] @classmethod def recognize( cls, device: BLEDevice, advertisement_data: AdvertisementData, ) -> bool: """Recognize the Current Time Service from advertisement data. This checks whether the Current Time Service's service UUID is in the list of advertised service UUIDs. Args: device (~bleak.backends.device.BLEDevice): The Bluetooth device. advertisement_data (AdvertisementData): The advertisement data. Returns: bool: ``True`` if the device is recognized as a Current Time Service, ``False`` otherwise. """ return cls.recognize_from_service_uuids(advertisement_data.service_uuids)
[docs] def get_time_from_bytes(self, time_bytes: bytes) -> float: """Convert bytes read from the Current Time Service to a timestamp. Args: time_bytes (bytes): The raw bytes read from the device. Raises: InvalidTimeBytesError: If `time_bytes` don't have the right format. Returns: float: The time encoded as a Unix timestamp. """ try: ( year, month, day, hour, minute, second, _, # weekday fractions256, ) = struct.unpack(self.TIME_GET_FORMAT, time_bytes) date_time = datetime( year=year, month=month, day=day, hour=hour, minute=minute, second=second, microsecond=int(fractions256 / 256 * MICROSECONDS), ) except struct.error as exception: raise InvalidTimeBytesError(time_bytes) from exception return date_time.timestamp()
[docs] def get_bytes_from_time( self, timestamp: float, ampm: bool = False, ) -> bytes: """Generate the bytes to set the time on the Current Time Service. Args: timestamp (float): The time encoded as a Unix timestamp. ampm (bool): ``True`` if the device should show the time with AM/PM, ``False`` if it should use 24-hour format. The Current Time Service ignores this argument, as it doesn't support this option. Returns: bytes: The bytes needed to set the time of the device to `timestamp`. """ date_time = datetime.fromtimestamp(timestamp) return struct.pack( self.TIME_SET_FORMAT, date_time.year, date_time.month, date_time.day, date_time.hour, date_time.minute, date_time.second, date_time.weekday() + 1, # Monday-Sunday -> 0-6 int(date_time.microsecond / MICROSECONDS * 256), 0, # Manual update )
[docs] class InfiniTime(CurrentTimeService): """Bluetooth clock support for the PineTime with InfiniTime firmware.""" DEVICE_TYPE = "InfiniTime" LOCAL_NAME = "InfiniTime" """The local name used to recognize this type of device.""" LOCAL_NAME_STARTS_WITH = False """The local name should exactly match `LOCAL_NAME`."""
[docs] @classmethod def recognize( cls, device: BLEDevice, advertisement_data: AdvertisementData, ) -> bool: """Recognize the PineTime with InfiniTime firmware from advertisement data. This checks whether the advertisement data has a local name that is equal to or starts with `LOCAL_NAME`. Args: device (~bleak.backends.device.BLEDevice): The Bluetooth device. advertisement_data (AdvertisementData): The advertisement data. Returns: bool: ``True`` if the device is recognized as a PineTime with InfiniTime firmware, ``False`` otherwise. """ return cls.recognize_from_local_name(advertisement_data.local_name)