Пример #1
0
class MyLightClient(Service):
    uuid = VendorUUID("6BFD8F3F-A704-4111-8DCE-F571BA26B40B")
    _control = Characteristic(
        uuid=VendorUUID("6BFD8F3E-A704-4111-8DCE-F571BA26B40B"), max_length=7)
    _light_level = Uint8Characteristic(
        uuid=VendorUUID("6BFD8F3D-A704-4111-8DCE-F571BA26B40B"),
        initial_value=100,
        properties=(Characteristic.READ | Characteristic.WRITE
                    | Characteristic.WRITE_NO_RESPONSE))
    print("my light init")
Пример #2
0
    def __init__(self, service=None):
        _server_tx = StreamOut(
            uuid=VendorUUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"),
            timeout=5.0,
            buffer_size=512,
        )
        _server_rx = StreamIn(
            uuid=VendorUUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"),
            timeout=5.0,
            buffer_size=512,
        )

        super().__init__(service=service)
class LYWSD03MMCService(Service):
    """Service for reading from an LYWSD03MMC sensor."""
    def __init__(self, service=None):
        super().__init__(service=service)
        # Defer creating buffers until needed, since MTU is not known yet.
        self._settings_result_buf = None
        self._readings_buf = None

    uuid = VendorUUID("ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6")

    readings = _Readings()

    @property
    def temperature_humidity(self):
        """Return a tuple of (temperature, humidity)."""
        if self._readings_buf is None:
            self._readings_buf = bytearray(self.readings.packet_size  # pylint: disable=no-member
                                           )
        data = self._readings_buf
        length = self.readings.readinto(data)  # pylint: disable=no-member
        if length > 0:
            low_temp, high_temp, hum = struct.unpack_from("<BBB", data)
            sign = high_temp & 0x80
            temp = ((high_temp & 0x7F) << 8) | low_temp
            if sign:
                temp = temp - 32767
            temp = temp / 100
            return (temp, hum)
        # No data.
        return None
Пример #4
0
class _EntityAttribute(Characteristic):
    """UTF-8 Encoded string characteristic."""
    uuid = VendorUUID("C6B2F38C-23AB-46D8-A6AB-A3A870BBD5D7")

    def __init__(self):
        super().__init__(properties=Characteristic.WRITE | Characteristic.READ,
                         read_perm=Attribute.OPEN,
                         write_perm=Attribute.OPEN,
                         fixed_length=False)
Пример #5
0
class CustomUART(Service):

    uuid = VendorUUID("8ba86973-935c-447c-91ad-bdcbad575f31")
    _server_rx = StreamIn(
        uuid=VendorUUID("8ba86974-935c-447c-91ad-bdcbad575f31"),
        timeout=1.0,
        buffer_size=64,
    )

    def __init__(self, service=None):
        # just steal the uuid code from MIDISerivce

        super().__init__(service=service)
        self.connectable = True
        self._rx = self._server_rx

    def read(self, nbytes=None):
        return self._rx.read(nbytes)
Пример #6
0
class MagicLightService(Service):
    """Service for controlling a Magic Light RGB bulb."""

    # These UUIDs actually use the standard base UUID even though they aren't standard.
    uuid = VendorUUID("0000ffe5-0000-1000-8000-00805f9b34fb")

    _control = Characteristic(
        uuid=VendorUUID("0000ffe9-0000-1000-8000-00805f9b34fb"), max_length=7
    )

    def __init__(self, service=None):
        super().__init__(service=service)
        self._color = 0xFFFFFF
        self._buf = bytearray(7)
        self._buf[0] = 0x56
        self._buf[6] = 0xAA
        self._brightness = 1.0

    def __getitem__(self, index):
        if index > 0:
            raise IndexError()
        return self._color

    def __setitem__(self, index, value):
        if index > 0:
            raise IndexError()
        if isinstance(value, int):
            r = (value >> 16) & 0xFF
            g = (value >> 8) & 0xFF
            b = value & 0xFF
        else:
            r, g, b = value
        self._buf[1] = r
        self._buf[2] = g
        self._buf[3] = b
        self._buf[4] = 0x00
        self._buf[5] = 0xF0
        self._control = self._buf
        self._color = value

    def __len__(self):
        return 1
class _Readings(ComplexCharacteristic):
    """Notify-only characteristic of temperature/humidity"""

    uuid = VendorUUID("ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6")

    def __init__(self):
        super().__init__(properties=Characteristic.NOTIFY)

    def bind(self, service):
        """Bind to an LYWSD03MMCService."""
        bound_characteristic = super().bind(service)
        bound_characteristic.set_cccd(notify=True)
        # Use a PacketBuffer that can store one packet to receive the data.
        return _bleio.PacketBuffer(bound_characteristic, buffer_size=1)
Пример #8
0
class LEDService(Service):

    uuid = VendorUUID(LED_SERVICE_UUID)

    redChar = Uint8Characteristic(uuid=VendorUUID(RED_LED_UUID), properties=Characteristic.WRITE_NO_RESPONSE)
    greenChar = Uint8Characteristic(uuid=VendorUUID(GREEN_LED_UUID), properties=Characteristic.WRITE_NO_RESPONSE)

    phase = 0

    def phaseChange(self):

        self.phase = self.phase + 1

        if self.phase > 3:
            self.phase = 0

        if 0 == self.phase:
            self.charsWrite(1, 0)
        elif 1 == self.phase:
            self.charsWrite(1, 1)
        elif 2 == self.phase:
            self.charsWrite(0, 1)
        elif 3 == self.phase:
            self.charsWrite(0, 0)

    def charsWrite(self, pRed, pGreen):

        self.redCharWrite(pRed)
        self.greenCharWrite(pGreen)

    def redCharWrite(self, pValue):

        self.redChar = pValue

    def greenCharWrite(self, pValue):

        self.greenChar = pValue
Пример #9
0
class _EntityUpdate(ComplexCharacteristic):
    """UTF-8 Encoded string characteristic."""
    uuid = VendorUUID("2F7CABCE-808D-411F-9A0C-BB92BA96C102")

    def __init__(self):
        super().__init__(properties=Characteristic.WRITE
                         | Characteristic.NOTIFY,
                         read_perm=Attribute.OPEN,
                         write_perm=Attribute.OPEN,
                         max_length=128,
                         fixed_length=False)

    def bind(self, service):
        """Binds the characteristic to the given Service."""
        bound_characteristic = super().bind(service)
        return _bleio.PacketBuffer(bound_characteristic, buffer_size=8)
Пример #10
0
class _RemoteCommand(ComplexCharacteristic):
    """Endpoint for sending commands to a media player. The value read will list all available

       commands."""
    uuid = VendorUUID("9B3C81D8-57B1-4A8A-B8DF-0E56F7CA51C2")

    def __init__(self):
        super().__init__(properties=Characteristic.WRITE_NO_RESPONSE
                         | Characteristic.NOTIFY,
                         read_perm=Attribute.OPEN,
                         write_perm=Attribute.OPEN,
                         max_length=13,
                         fixed_length=False)

    def bind(self, service):
        """Binds the characteristic to the given Service."""
        bound_characteristic = super().bind(service)
        return _bleio.PacketBuffer(bound_characteristic, buffer_size=1)
Пример #11
0
class _MidiCharacteristic(ComplexCharacteristic):
    """Endpoint for sending commands to a media player. The value read will list all available

       commands."""
    uuid = VendorUUID("7772E5DB-3868-4112-A1A9-F2669D106BF3")

    def __init__(self):
        super().__init__(properties=Characteristic.WRITE_NO_RESPONSE
                         | Characteristic.READ | Characteristic.NOTIFY,
                         read_perm=Attribute.OPEN,
                         write_perm=Attribute.OPEN,
                         max_length=512,
                         fixed_length=False)

    def bind(self, service):
        """Binds the characteristic to the given Service."""
        bound_characteristic = super().bind(service)
        return _bleio.PacketBuffer(bound_characteristic, buffer_size=4)
Пример #12
0
 def adafruit_service_uuid(n):
     """Generate a VendorUUID which fills in a 16-bit value in the standard
     Adafruit Service UUID: ADAFnnnn-C332-42A8-93BD-25E905756CB8.
     """
     return VendorUUID("ADAF{:04x}-C332-42A8-93BD-25E905756CB8".format(n))
Пример #13
0
class FreeGlowService(UARTService):
    uuid = VendorUUID("622b6b5e-b514-4be9-81d4-e13ba87ba54f")
Пример #14
0
class AppleMediaService(Service):
    """View and control currently playing media.

    Exact functionality varies with different media apps. For example, Spotify will include the
    album name and artist name in `title` when controlling playback on a remote device.
    `artist` includes a description of the remote playback.

    """

    uuid = VendorUUID("89D3502B-0F36-433A-8EF4-C502AD55F8DC")

    _remote_command = _RemoteCommand()
    _entity_update = _EntityUpdate()
    _entity_attribute = _EntityAttribute()

    player_name = _MediaAttribute(0, 0)
    """Name of the media player app"""
    _playback_info = _MediaAttribute(0, 1)
    paused = _MediaAttributePlaybackState(0)
    """True when playback is paused. False otherwise."""
    playing = _MediaAttributePlaybackState(1)
    """True when playback is playing. False otherwise."""
    rewinding = _MediaAttributePlaybackState(2)
    """True when playback is rewinding. False otherwise."""
    fast_forwarding = _MediaAttributePlaybackState(3)
    """True when playback is fast-forwarding. False otherwise."""
    playback_rate = _MediaAttributePlaybackInfo(1)
    """Playback rate as a decimal of normal speed."""
    elapsed_time = _MediaAttributePlaybackInfo(2)
    """Time elapsed in the current track. Not updated as the track plays. Use (the amount of time
       since read elapsed time) * `playback_rate` to estimate the current `elapsed_time`."""
    volume = _MediaAttribute(0, 2)
    """Current volume"""

    queue_index = _MediaAttribute(1, 0)
    """Current track's index in the queue."""
    queue_length = _MediaAttribute(1, 1)
    """Count of tracks in the queue."""
    shuffle_mode = _MediaAttribute(1, 2)
    """Current shuffle mode as an integer. Off (0), One (1), and All (2)"""
    repeat_mode = _MediaAttribute(1, 3)
    """Current repeat mode as an integer. Off (0), One (1), and All (2)"""

    artist = _MediaAttribute(2, 0)
    """Current track's artist name."""
    album = _MediaAttribute(2, 1)
    """Current track's album name."""
    title = _MediaAttribute(2, 2)
    """Current track's title."""
    duration = _MediaAttribute(2, 3)
    """Current track's duration as a string."""
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._buffer = None
        self._cmd = None
        self._register_buffer = None
        self._attribute_cache = {}
        self._supported_commands = []
        self._command_buffer = None

    def _send_command(self, command_id):
        if not self._command_buffer:
            self._command_buffer = bytearray(13)
        i = self._remote_command.readinto(  # pylint: disable=no-member
            self._command_buffer)
        if i > 0:
            self._supported_commands = list(self._command_buffer[:i])
        if command_id not in self._supported_commands:
            if not self._supported_commands:
                return
            raise UnsupportedCommand()
        if not self._cmd:
            self._cmd = bytearray(1)
        self._cmd[0] = command_id
        self._remote_command.write(self._cmd)  # pylint: disable=no-member

    def play(self):
        """Plays the current track. Does nothing if already playing."""
        self._send_command(0)

    def pause(self):
        """Pauses the current track. Does nothing if already paused."""
        self._send_command(1)

    def toggle_play_pause(self):
        """Plays the current track if it is paused. Otherwise it pauses the track."""
        self._send_command(2)

    def next_track(self):
        """Stops playing the current track and plays the next one."""
        self._send_command(3)

    def previous_track(self):
        """Stops playing the current track and plays the previous track."""
        self._send_command(4)

    def volume_up(self):
        """Increases the playback volume."""
        self._send_command(5)

    def volume_down(self):
        """Decreases the playback volume."""
        self._send_command(6)

    def advance_repeat_mode(self):
        """Advances the repeat mode. Modes are: Off, One and All"""
        self._send_command(7)

    def advance_shuffle_mode(self):
        """Advances the shuffle mode. Modes are: Off, One and All"""
        self._send_command(8)

    def skip_forward(self):
        """Skips forwards in the current track"""
        self._send_command(9)

    def skip_backward(self):
        """Skips backwards in the current track"""
        self._send_command(10)

    def like_track(self):
        """Likes the current track"""
        self._send_command(11)

    def dislike_track(self):
        """Dislikes the current track"""
        self._send_command(12)

    def bookmark_track(self):
        """Bookmarks the current track"""
        self._send_command(13)
Пример #15
0
 def physbryk_service_uuid(n):
     """Generate a VendorUUID which fills in a 16-bit value in the standard
     PhysBryk Service UUID: a0d1839c-0eaa-5b52-nnnn-818888dc7dc5.
     """
     # return VendorUUID("ADAF{:04x}-C332-42A8-93BD-25E905756CB8".format(n))
     return VendorUUID('a0d1{:04x}-0eaa-5b52-bc84-818888dc7dc5'.format(n))
Пример #16
0
class SubkeypadService(UARTService):
    uuid = VendorUUID('39A00001-C17A-7AE2-EA02-A50E24DCCA9E')
    left_state = Uint8Characteristic(
        uuid=VendorUUID('39A00003-C1AB-74A2-EFA2-E50EA4DCCA9E'))
    right_state = Uint8Characteristic(
        uuid=VendorUUID('39A00004-C1AB-74A2-EFA2-E50EA4DCCA9E'))
Пример #17
0
class AppleMediaService(Service):
    """View and control currently playing media. Unimplemented."""
    uuid = VendorUUID("89D3502B-0F36-433A-8EF4-C502AD55F8DC")

    _remote_command = _RemoteCommand()
    _entity_update = _EntityUpdate()
    _entity_attribute = _EntityAttribute()

    player_name = _MediaAttribute(0, 0)
    _playback_info = _MediaAttribute(0, 1)
    paused = _MediaAttributePlaybackState(0)
    playing = _MediaAttributePlaybackState(1)
    rewinding = _MediaAttributePlaybackState(2)
    fast_forwarding = _MediaAttributePlaybackState(3)
    playback_rate = _MediaAttributePlaybackInfo(1)
    elapsed_time = _MediaAttributePlaybackInfo(2)
    volume = _MediaAttribute(0, 2)

    queue_index = _MediaAttribute(1, 0)
    queue_length = _MediaAttribute(1, 1)
    shuffle_mode = _MediaAttribute(1, 2)
    repeat_mode = _MediaAttribute(1, 3)

    artist = _MediaAttribute(2, 0)
    album = _MediaAttribute(2, 1)
    title = _MediaAttribute(2, 2)
    duration = _MediaAttribute(2, 3)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._buffer = None
        self._cmd = None
        self._register_buffer = None
        self._attribute_cache = {}
        self._supported_commands = []
        self._command_buffer = None

    def _send_command(self, command_id):
        if not self._command_buffer:
            self._command_buffer = bytearray(13)
        i = self._remote_command.readinto(self._command_buffer)
        if i > 0:
            self._supported_commands = list(self._command_buffer[:i])
        if command_id not in self._supported_commands:
            if not self._supported_commands:
                return
            raise UnsupportedCommand()
        if not self._cmd:
            self._cmd = bytearray(1)
        self._cmd[0] = command_id
        self._remote_command.write(self._cmd)

    def play(self):
        self._send_command(0)

    def pause(self):
        self._send_command(1)

    def toggle_play_pause(self):
        self._send_command(2)

    def next_track(self):
        self._send_command(3)

    def previous_track(self):
        self._send_command(4)

    def volume_up(self):
        self._send_command(5)

    def volume_down(self):
        self._send_command(6)

    def advance_repeat_mode(self):
        self._send_command(7)

    def advance_shuffle_mode(self):
        self._send_command(8)

    def skip_forward(self):
        self._send_command(9)

    def skip_backward(self):
        self._send_command(10)

    def like_track(self):
        self._send_command(11)

    def dislike_track(self):
        self._send_command(12)

    def bookmark_track(self):
        self._send_command(13)
Пример #18
0
class MIDIService(Service):
    uuid = VendorUUID("03B80E5A-EDE8-4B33-A751-6CE34EC4C700")
    _raw = _MidiCharacteristic()

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._in_buffer = bytearray(self._raw.packet_length)
        self._out_buffer = None
        shared_buffer = memoryview(bytearray(4))
        self._buffers = [
            None, shared_buffer[:1], shared_buffer[:2], shared_buffer[:3],
            shared_buffer[:4]
        ]
        self._header = bytearray(1)
        self._in_sysex = False
        self._message_target_length = None
        self._message_length = 0
        self._pending_realtime = None

    def read(self, length):
        self._raw.read(self._in_buffer)
        return None

    def write(self, buf, length):
        timestamp_ms = time.monotonic_ns() // 1000000
        self._header[0] = (timestamp_ms >> 7 & 0x3f) | 0x80
        i = 0
        while i < length:
            data = buf[i]
            command = data & 0x80 != 0
            if self._in_sysex:
                if command:  # End of sysex or real time
                    b = self._buffers[2]
                    b[0] = 0x80 | (timestamp_ms & 0x7f)
                    b[1] = 0xf7
                    self._raw.write(b, self._header)
                    self._in_sysex = data == 0xf7
                else:
                    b = self._buffers[1]
                    b[0] = data
                    self._raw.write(b, self._header)
            elif command:
                self._in_sysex = data == 0xf0
                b = self._buffers[2]
                b[0] = 0x80 | (timestamp_ms & 0x7f)
                b[1] = data
                if 0xf6 <= data <= 0xff or self._in_sysex:  # Real time, command only or start sysex
                    if self._message_target_length:
                        self._pending_realtime = b
                    else:
                        self._raw.write(b, self._header)
                else:
                    if 0x80 <= data <= 0xbf or 0xe0 <= data <= 0xef or data == 0xf2:  # Two following bytes
                        self._message_target_length = 4
                    else:
                        self._message_target_length = 3
                    b = self._buffers[self._message_target_length]
                    # All of the buffers share memory so the timestamp and data have already been set.
                    self._message_length = 2
                    self._out_buffer = b
            else:
                self._out_buffer[self._message_length] = data
                self._message_length += 1
                if self._message_target_length == self._message_length:
                    self._raw.write(self._out_buffer, self._header)
                    if _pending_realtime:
                        self._raw.write(self._pending_realtime, self._header)
                        self._pending_realtime = None
                    self._message_target_length = None
Пример #19
0
from adafruit_ble.uuid import VendorUUID
from adafruit_ble.characteristics import Characteristic, ComplexCharacteristic

import adafruit_ble
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.standard.device_info import DeviceInfoService
from adafruit_ble_cycling_speed_and_cadence import CyclingSpeedAndCadenceService

from PySide2.QtWidgets import (QAction, QApplication, QLabel, QPushButton,
                               QVBoxLayout, QWidget, QMainWindow, QSizePolicy)
from PySide2.QtCore import QObject, Signal, Slot, Qt, QRect, QPoint
from PySide2.QtGui import QFont, QPaintEvent, QPainterPath, QPainter, QPen, QBrush, QKeySequence

Buf = Union[bytes, bytearray, memoryview]

UUID_SB20_SERVICE = VendorUUID("0c46beaf-9c22-48ff-ae0e-c6eae1a2f4e5")
UUID_SB20_STATUS = VendorUUID("0c46beb0-9c22-48ff-ae0e-c6eae1a2f4e5")
UUID_SB20_COMMAND = VendorUUID("0c46beb1-9c22-48ff-ae0e-c6eae1a2f4e5")


class StatusQueue:
    """Accumulates a Characteristic's incoming values in a FIFO buffer."""
    def __init__(self,
                 characteristic: Characteristic,
                 *,
                 timeout: float = -1,
                 buffer_size: int = 64):
        """Monitor the given Characteristic. Each time a new value is written to the Characteristic
        add the newly-written bytes to a FIFO buffer.

        :param Characteristic characteristic: The Characteristic to monitor.
Пример #20
0
class TransparentUARTService(Service):
    """
    Provide UART-like functionality via MicroChip

    :param int timeout:  the timeout in seconds to wait
      for the first character and between subsequent characters.
    :param int buffer_size: buffer up to this many bytes.
      If more bytes are received, older bytes will be discarded.
    """

    # pylint: disable=no-member
    uuid = VendorUUID("49535343-FE7D-4AE5-8FA9-9FAFD205E455")
    _server_tx = StreamOut(
        uuid=VendorUUID("49535343-1E4D-4BD9-BA61-23C647249616"),
        timeout=1.0,
        buffer_size=64,
    )
    _server_rx = StreamIn(
        uuid=VendorUUID("49535343-8841-43F4-A8D4-ECBE34729BB3"),
        timeout=1.0,
        buffer_size=64,
    )

    def __init__(self, service=None):
        super().__init__(service=service)
        self.connectable = True
        if not service:
            self._rx = self._server_rx
            self._tx = self._server_tx
        else:
            # If we're a client then swap the characteristics we use.
            self._tx = self._server_rx
            self._rx = self._server_tx

    def read(self, nbytes=None):
        """
        Read characters. If ``nbytes`` is specified then read at most that many bytes.
        Otherwise, read everything that arrives until the connection times out.
        Providing the number of bytes expected is highly recommended because it will be faster.

        :return: Data read
        :rtype: bytes or None
        """
        return self._rx.read(nbytes)

    def readinto(self, buf, nbytes=None):
        """
        Read bytes into the ``buf``. If ``nbytes`` is specified then read at most
        that many bytes. Otherwise, read at most ``len(buf)`` bytes.

        :return: number of bytes read and stored into ``buf``
        :rtype: int or None (on a non-blocking error)
        """
        return self._rx.readinto(buf, nbytes)

    def readline(self):
        """
        Read a line, ending in a newline character.

        :return: the line read
        :rtype: bytes or None
        """
        return self._rx.readline()

    @property
    def in_waiting(self):
        """The number of bytes in the input buffer, available to be read."""
        return self._rx.in_waiting

    def reset_input_buffer(self):
        """Discard any unread characters in the input buffer."""
        self._rx.reset_input_buffer()

    def write(self, buf):
        """Write a buffer of bytes."""
        self._tx.write(buf)
class AppleNotificationCenterService(Service):
    """Notification service.

    Documented by Apple here:
      https://developer.apple.com/library/archive/documentation/CoreBluetooth/Reference/AppleNotificationCenterServiceSpecification/Specification/Specification.html

    """

    uuid = VendorUUID("7905F431-B5CE-4E99-A40F-4B1E122D00D0")

    control_point = StreamIn(
        uuid=VendorUUID("69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9"))
    data_source = StreamOut(
        uuid=VendorUUID("22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB"),
        buffer_size=1024)
    notification_source = StreamOut(
        uuid=VendorUUID("9FBF120D-6301-42D9-8C58-25E699A21DBD"),
        buffer_size=8 * 100)

    def __init__(self, service=None):
        super().__init__(service=service)
        self._active_notifications = {}

    def _update(self):
        # Pylint is incorrectly inferring the type of self.notification_source so disable no-member.
        while self.notification_source.in_waiting > 7:  # pylint: disable=no-member
            buffer = self.notification_source.read(8)  # pylint: disable=no-member
            event_id, event_flags, category_id, category_count, nid = struct.unpack(
                "<BBBBI", buffer)
            if event_id == 0:
                self._active_notifications[nid] = Notification(
                    nid,
                    event_flags,
                    category_id,
                    category_count,
                    control_point=self.control_point,
                    data_source=self.data_source,
                )
                yield self._active_notifications[nid]
            elif event_id == 1:
                self._active_notifications[nid].update(event_flags,
                                                       category_id,
                                                       category_count)
                yield None
            elif event_id == 2:
                self._active_notifications[nid].removed = True
                del self._active_notifications[nid]
                yield None

    def wait_for_new_notifications(self, timeout=None):
        """Waits for new notifications and yields them. Returns on timeout, update, disconnect or
           clear."""
        start_time = time.monotonic()
        while timeout is None or timeout > time.monotonic() - start_time:
            try:
                new_notification = next(self._update())
            except StopIteration:
                return
            if new_notification:
                yield new_notification

    @property
    def active_notifications(self):
        """A dictionary of active notifications keyed by id."""
        for _ in self._update():
            pass
        return self._active_notifications
Пример #22
0
class MIDIService(Service):
    """BLE MIDI service. It acts just like a USB MIDI PortIn and PortOut and can be used as a drop
       in replacement.

       BLE MIDI's protocol includes timestamps for MIDI messages. This class automatically adds them
       to MIDI data written out and strips them from MIDI data read in."""

    uuid = VendorUUID("03B80E5A-EDE8-4B33-A751-6CE34EC4C700")
    _raw = _MidiCharacteristic()

    # _raw gets shadowed for each MIDIService instance by a PacketBuffer. PyLint doesn't know this
    # so it complains about missing members.
    # pylint: disable=no-member

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # Defer creating _in_buffer until we're definitely connected.
        self._in_buffer = None
        self._out_buffer = None
        shared_buffer = memoryview(bytearray(4))
        self._buffers = [
            None,
            shared_buffer[:1],
            shared_buffer[:2],
            shared_buffer[:3],
            shared_buffer[:4],
        ]
        self._header = bytearray(1)
        self._in_sysex = False
        self._message_target_length = None
        self._message_length = 0
        self._pending_realtime = None
        self._in_length = 0
        self._in_index = 1
        self._last_data = True

    def readinto(self, buf, length):
        """Reads up to ``length`` bytes into ``buf`` starting at index 0.

           Returns the number of bytes written into ``buf``."""
        if self._in_buffer is None:
            self._in_buffer = bytearray(self._raw.packet_size)
        i = 0
        while i < length:
            if self._in_index < self._in_length:
                byte = self._in_buffer[self._in_index]
                if self._last_data and byte & 0x80 != 0:
                    # Maybe manage timing here. Not done now because we're likely slower than we
                    # need to be already.
                    # low_ms = byte & 0x7f
                    # print("low", low_ms)
                    self._in_index += 1
                    self._last_data = False
                    continue
                self._in_index += 1
                self._last_data = True
                buf[i] = byte
                i += 1
            else:
                self._in_length = self._raw.readinto(self._in_buffer)
                if self._in_length == 0:
                    break
                # high_ms = self._in_buffer[0] & 0x3f
                # print("high", high_ms)
                self._in_index = 1
                self._last_data = True

        return i

    def read(self, length):
        """Reads up to ``length`` bytes and returns them."""
        result = bytearray(length)
        i = self.readinto(result, length)
        return result[:i]

    def write(self, buf, length):
        """Writes ``length`` bytes out."""
        # pylint: disable=too-many-branches
        timestamp_ms = time.monotonic_ns() // 1000000
        self._header[0] = (timestamp_ms >> 7 & 0x3F) | 0x80
        i = 0
        while i < length:
            data = buf[i]
            command = data & 0x80 != 0
            if self._in_sysex:
                if command:  # End of sysex or real time
                    b = self._buffers[2]
                    b[0] = 0x80 | (timestamp_ms & 0x7F)
                    b[1] = 0xF7
                    self._raw.write(b, header=self._header)
                    self._in_sysex = data == 0xF7
                else:
                    b = self._buffers[1]
                    b[0] = data
                    self._raw.write(b, header=self._header)
            elif command:
                self._in_sysex = data == 0xF0
                b = self._buffers[2]
                b[0] = 0x80 | (timestamp_ms & 0x7F)
                b[1] = data
                if (0xF6 <= data <= 0xFF or self._in_sysex
                    ):  # Real time, command only or start sysex
                    if self._message_target_length:
                        self._pending_realtime = b
                    else:
                        self._raw.write(b, header=self._header)
                else:
                    if (0x80 <= data <= 0xBF or 0xE0 <= data <= 0xEF
                            or data == 0xF2):  # Two following bytes
                        self._message_target_length = 4
                    else:
                        self._message_target_length = 3
                    b = self._buffers[self._message_target_length]
                    # All of the buffers share memory so the timestamp and data have already been
                    # set.
                    self._message_length = 2
                    self._out_buffer = b
            else:
                self._out_buffer[self._message_length] = data
                self._message_length += 1
                if self._message_target_length == self._message_length:
                    self._raw.write(self._out_buffer, header=self._header)
                    if self._pending_realtime:
                        self._raw.write(self._pending_realtime,
                                        header=self._header)
                        self._pending_realtime = None
                    self._message_target_length = None
            i += 1