예제 #1
0
def main():

    # initialize I2C
    i2c = board.I2C()

    # initialize sensors
    bmp280 = adafruit_bmp280.Adafruit_BMP280_I2C(i2c)
    sht31d = adafruit_sht31d.SHT31D(i2c)

    # initialize  BLE
    ble = BLERadio()

    # create custom advertisement object
    advertisement = IOTGAdvertisement()
    # set device name
    ble.name = "IOTG1"
    # set initial value
    # will use only first 5 chars of name
    advertisement.md_field = ble.name[:5] + "0000"
    # BLRE advertising interval in seconds
    BLE_ADV_INT = 0.2
    # start BLE advertising
    ble.start_advertising(advertisement, interval=BLE_ADV_INT)

    # main loop
    while True:
        # print values - this will be available on serial
        print("Temperature: {:.1f} C".format(bmp280.temperature))
        print("Humidity: {:.1f} %".format(sht31d.relative_humidity))
        # get sensor data
        T = int(bmp280.temperature)
        H = int(sht31d.relative_humidity)
        # stop advertsing
        ble.stop_advertising()
        # update advertisement data
        advertisement.md_field = ble.name[:5] + chr(T) + chr(H) + "00"
        # start advertising
        ble.start_advertising(advertisement, interval=BLE_ADV_INT)
        # sleep for 2 seconds
        time.sleep(2)
예제 #2
0
class NametagsApp:
    def __init__(self, init_ui=True):
        self.ble = BLERadio()
        ui.display_qr(self.addr_suffix)
        self.nameservice = NameService()
        self.advertisement = ProvideServicesAdvertisement(self.nameservice)
        self.scan_response = Advertisement()
        self.scan_response.complete_name = "BADGE-{}".format(self.addr_suffix)
        self.ble.start_advertising(self.advertisement, self.scan_response)

    @property
    def addr_suffix(self):
        addr = self.ble.address_bytes
        return "{:02X}{:02X}{:02X}".format(addr[3], addr[4], addr[5])

    def update(self):
        if self.ble.connected:
            self.nameservice.update()
        ui.display_qr(self.addr_suffix)

    def process_input(self):
        buttons = badge.gamepad.get_pressed()
        if buttons & badge.BTN_ACTION:
            self.cleanup()
            return True
        return False

    def run(self):
        self.running = True
        while self.running:
            self.process_input()
            self.update()

    def cleanup(self):
        self.ble.stop_advertising()
        self.running = False
ble.name = "Sense"

# The Bluefruit Playground app looks in the manufacturer data
# in the advertisement. That data uses the USB PID as a unique ID.
# Feather Bluefruit Sense USB PID:
# This board is not yet support on the app.
# Arduino: 0x8087,  CircuitPython: 0x8088
adv = AdafruitServerAdvertisement()
adv.pid = 0x8088

while True:
    # Advertise when not connected.
    ble.start_advertising(adv)
    while not ble.connected:
        pass
    ble.stop_advertising()

    while ble.connected:
        now_msecs = time.monotonic_ns() // 1000000  # pylint: disable=no-member

        if now_msecs - accel_last_update >= accel_svc.measurement_period:
            accel_svc.acceleration = lsm6ds33.acceleration
            accel_last_update = now_msecs

        if now_msecs - baro_last_update >= baro_svc.measurement_period:
            baro_svc.pressure = bmp280.pressure
            baro_last_update = now_msecs

        button_svc.set_pressed(False, not button.value, False)

        if now_msecs - humidity_last_update >= humidity_svc.measurement_period:
예제 #4
0
class Radio:
    """
    Represents a connection through which one can send or receive strings
    and bytes. The radio can be tuned to a specific channel upon initialisation
    or via the `configure` method.
    """
    def __init__(self, **args):
        """
        Takes the same configuration arguments as the `configure` method.
        """
        # For BLE related operations.
        self.ble = BLERadio()
        # The uid for outgoing message. Incremented by one on each send, up to
        # 255 when it's reset to 0.
        self.uid = 0
        # Contains timestamped message metadata to mitigate report of
        # receiving of duplicate messages within AD_DURATION time frame.
        self.msg_pool = set()
        # Handle user related configuration.
        self.configure(**args)

    def configure(self, channel=42):
        """
        Set configuration values for the radio.

        :param int channel: The channel (0-255) the radio is listening /
            broadcasting on.
        """
        if -1 < channel < 256:
            self._channel = channel
        else:
            raise ValueError("Channel must be in range 0-255")

    def send(self, message):
        """
        Send a message string on the channel to which the radio is
        broadcasting.

        :param str message: The message string to broadcast.
        """
        return self.send_bytes(message.encode("utf-8"))

    def send_bytes(self, message):
        """
        Send bytes on the channel to which the radio is broadcasting.

        :param bytes message: The bytes to broadcast.
        """
        # Ensure length of message.
        if len(message) > MAX_LENGTH:
            raise ValueError(
                "Message too long (max length = {})".format(MAX_LENGTH))
        advertisement = _RadioAdvertisement()
        # Concatenate the bytes that make up the advertised message.
        advertisement.msg = struct.pack("<BB", self._channel,
                                        self.uid) + message

        self.uid = (self.uid + 1) % 255
        # Advertise (block) for AD_DURATION period of time.
        self.ble.start_advertising(advertisement)
        time.sleep(AD_DURATION)
        self.ble.stop_advertising()

    def receive(self):
        """
        Returns a message received on the channel on which the radio is
        listening.

        :return: A string representation of the received message, or else None.
        """
        msg = self.receive_full()
        if msg:
            return msg[0].decode("utf-8").replace("\x00", "")
        return None

    def receive_full(self):
        """
        Returns a tuple containing three values representing a message received
        on the channel on which the radio is listening. If no message was
        received then `None` is returned.

        The three values in the tuple represent:

        * the bytes received.
        * the RSSI (signal strength: 0 = max, -255 = min).
        * a microsecond timestamp: the value returned by time.monotonic() when
          the message was received.

        :return: A tuple representation of the received message, or else None.
        """
        try:
            for entry in self.ble.start_scan(_RadioAdvertisement,
                                             minimum_rssi=-255,
                                             timeout=1,
                                             extended=True):
                # Extract channel and unique message ID bytes.
                chan, uid = struct.unpack("<BB", entry.msg[:2])
                if chan == self._channel:
                    now = time.monotonic()
                    addr = entry.address.address_bytes
                    # Ensure this message isn't a duplicate. Message metadata
                    # is a tuple of (now, chan, uid, addr), to (mostly)
                    # uniquely identify a specific message in a certain time
                    # window.
                    expired_metadata = set()
                    duplicate = False
                    for msg_metadata in self.msg_pool:
                        if msg_metadata[0] < now - AD_DURATION:
                            # Ignore expired entries and mark for removal.
                            expired_metadata.add(msg_metadata)
                        elif (chan, uid, addr) == msg_metadata[1:]:
                            # Ignore matched messages to avoid duplication.
                            duplicate = True
                    # Remove expired entries.
                    self.msg_pool = self.msg_pool - expired_metadata
                    if not duplicate:
                        # Add new message's metadata to the msg_pool and
                        # return it as a result.
                        self.msg_pool.add((now, chan, uid, addr))
                        msg = entry.msg[2:]
                        return (msg, entry.rssi, now)
        finally:
            self.ble.stop_scan()
        return None
예제 #5
0
class BLEHID(AbstractHID):
    def post_init(self, ble_name='KMK Keyboard', **kwargs):
        self.conn_id = -1

        self.ble = BLERadio()
        self.ble.name = ble_name
        self.hid = HIDService()
        self.hid.protocol_mode = 0  # Boot protocol

        # Security-wise this is not right. While you're away someone turns
        # on your keyboard and they can pair with it nice and clean and then
        # listen to keystrokes.
        # On the other hand we don't have LESC so it's like shouting your
        # keystrokes in the air
        if not self.ble.connected or not self.hid.devices:
            self.start_advertising()

        self.conn_id = 0

    @property
    def devices(self):
        """Search through the provided list of devices to find the ones with the
        send_report attribute."""
        if not self.ble.connected:
            return []

        result = []
        # Security issue:
        # This introduces a race condition. Let's say you have 2 active
        # connections: Alice and Bob - Alice is connection 1 and Bob 2.
        # Now Chuck who has already paired with the device in the past
        # (this assumption is needed only in the case of LESC)
        # wants to gather the keystrokes you send to Alice. You have
        # selected right now to talk to Alice (1) and you're typing a secret.
        # If Chuck kicks Alice off and is quick enough to connect to you,
        # which means quicker than the running interval of this function,
        # he'll be earlier in the `self.hid.devices` so will take over the
        # selected 1 position in the resulted array.
        # If no LESC is in place, Chuck can sniff the keystrokes anyway
        for device in self.hid.devices:
            if hasattr(device, "send_report"):
                result.append(device)

        return result

    def _check_connection(self):
        devices = self.devices
        if not devices:
            return False

        if self.conn_id >= len(devices):
            self.conn_id = len(devices) - 1

        if self.conn_id < 0:
            return False

        if not devices[self.conn_id]:
            return False

        return True

    def hid_send(self, evt):
        if not self._check_connection():
            return

        device = self.devices[self.conn_id]

        while len(evt) < len(device._characteristic.value) + 1:
            evt.append(0)

        return device.send_report(evt[1:])

    def clear_bonds(self):
        import _bleio

        _bleio.adapter.erase_bonding()

    def next_connection(self):
        self.conn_id = (self.conn_id + 1) % len(self.devices)

    def previous_connection(self):
        self.conn_id = (self.conn_id - 1) % len(self.devices)

    def start_advertising(self):
        advertisement = ProvideServicesAdvertisement(self.hid)
        advertisement.appearance = BLE_APPEARANCE_HID_KEYBOARD

        self.ble.start_advertising(advertisement)

    def stop_advertising(self):
        self.ble.stop_advertising()
예제 #6
0
class Ble():
    def __init__(self, onConnectionStateChanged, onAdvertising=None, onWrite=None):
        self._ble = BLERadio()
        self._uart_server = UARTService()
        self._onAdvertising = onAdvertising
        self._onWrite = onWrite
        self._advertisement = ProvideServicesAdvertisement(self._uart_server)
        self._isAdvertising = False
        self._onAdvertising = onAdvertising
        self._onWrite = onWrite
        self.__oldConnectionState = False
        self._onConnectionStateChanged = onConnectionStateChanged
        self._enabled = False

    def write(self, data):
        if self._ble.connected:
            self._uart_server.write(data)
            if self._onWrite:
                self._onWrite(data)

    def startAdvertising(self):
        if not self._ble.connected:
            self.stopAdvertising()
        self._ble.start_advertising(self._advertisement)
        display("Start Phone App and connect")
        display("Started Advertising to BLE devices")
        if self._onAdvertising:
            self._onAdvertising()
        self._isAdvertising = True

    def stopAdvertising(self):
        # if self._ble.connected:
        display("Stopped Advertising")
        self._ble.stop_advertising()
        self._isAdvertising = False

    def read(self):

        if self._ble.connected != self.__oldConnectionState:
            self._onConnectionStateChanged()

        self.__oldConnectionState = self._ble.connected

    def enable(self):
        self.startAdvertising()
        self._enabled = True

    def disable(self):
        self.stopAdvertising()
        self._enabled = False

    def toggle(self):
        if self._enabled:
            self.disable()
        else:
            self.enable()

    @property
    def isAdvertising(self):
        return self._isAdvertising

    @property
    def connected(self):
        return self._ble.connected

    @property
    def enabled(self):
        return self._enabled
######################################################
#   Main Code
######################################################
while True:
    print("WAITING for BlueFruit device...")
    
    # Advertise when not connected.
    ble.start_advertising(advertisement)                # Tell other devices that Clue has a Bluetooth UART connection.
    while not ble.connected:                            # Check to see if another device has connected with the Clue via Bluetooth.
        if clue.button_a:
            break
        pass                                            # Do nothing this loop.

    # Connected
    ble.stop_advertising()                              # Stop telling other devices about the Clue's Bluetooth UART Connection.
    print("CONNECTED")

    # Loop and read packets
    while ble.connected:                                # Check to see if we are still connected.

        if clue.button_a:
            break

        # Keeping trying until a good packet is received
        try:
            packet = Packet.from_stream(uart_server)    # Try to get new messages from connected device.
        except ValueError:
            continue

        # Only handle button packets
예제 #8
0
def main():
    DEBUG = True
    BOARD = True  #flag indicating whether attached to a board.

    try:
        import board
    except NotImplementedError:
        # no board attached so mock sensors, services etc
        import mock as mk
        print('No valid board. Using mock sensors and services')
        BOARD = False

    # sensors
    import adafruit_lsm6ds.lsm6ds33  # motion
    import adafruit_lis3mdl  # magnetometer
    import adafruit_apds9960.apds9960  # EMR

    import time

    dummy_sensor = DummySensor()

    if BOARD:  # valid board present use real sensors
        import analogio

        battery = analogio.AnalogIn(board.VOLTAGE_MONITOR)
        motion = adafruit_lsm6ds.lsm6ds33.LSM6DS33(board.I2C())
        magnet = adafruit_lis3mdl.LIS3MDL(board.I2C())
        emr = adafruit_apds9960.apds9960.APDS9960(board.I2C())
        # emr.enable_proximity = True
        emr.enable_color = True

        # Create and initialize the available services.
        ble = BLERadio()
        battery_svc = BatteryService()
        motion_svc = MotionService()
        magnet_svc = MagnetService()
        emr_svc = EMRService()
        dummy_svc = DummyService()
        adv = PhysBrykServerAdvertisement()

    else:  #use mock sensors and services

        # Accelerometer and gyro
        motion = mk.Sensor()
        magnet = mk.Sensor()
        emr = mk.Sensor()
        battery = mk.Sensor()

        ble = mk.Service()
        battery_svc = mk.Service()
        motion_svc = mk.Service()
        magnet_svc = mk.Service()
        emr_svc = mk.Service()
        dummy_svc = mk.Service()
        adv = mk.Service()

    ble.name = "PhysBryk_Alpha"

    last_update = 0

    while True:
        # Advertise when not connected.
        ble.start_advertising(adv)
        if DEBUG: print('Connecting...')
        while not ble.connected:
            pass
        ble.stop_advertising()
        if DEBUG:
            print('Connected!')

        while ble.connected:
            now_msecs = time.monotonic_ns() // 1000000  # pylint: disable=no-member

            if now_msecs - last_update >= MEASUREMENT_PERIOD:
                battery_svc.voltage = battery_svc.get_voltage(battery)
                motion_svc.acceleration = motion.acceleration  # m/s/s
                motion_svc.gyro = motion.gyro  # rad/s
                magnet_svc.magnetic = magnet.magnetic  # microT

                emr_svc.intensity = emr_svc.get_lux(emr.color_data)
                emr_svc.spectrum = emr.color_data
                emr_svc.proximity = emr.proximity
                dummy_svc.value = 42
                dummy_sensor.update()
                last_update = now_msecs

                if DEBUG:
                    print(f'motion acceleration: {motion_svc.acceleration}')
                    print(f'motion gyro: {motion_svc.gyro}')
                    print(f'magnet magnet: {magnet_svc.magnetic}')
                    print(f'emr intensity: {emr_svc.intensity}')
                    print(f'emr spectrum: {emr_svc.spectrum}')
                    print(f'emr proximity: {emr_svc.proximity}')
                    print(f'battery: {battery_svc.voltage}')
                    print(f'dummy: {dummy_svc.value}')
                if not BOARD:
                    for s in mk.sensors:
                        s.update()
예제 #9
0
class Split(Module):
    '''Enables splitting keyboards wirelessly, or wired'''

    def __init__(
        self,
        split_flip=True,
        split_side=None,
        split_type=SplitType.UART,
        split_target_left=True,
        uart_interval=20,
        data_pin=None,
        data_pin2=None,
        target_left=True,
        uart_flip=True,
        debug_enabled=False,
    ):
        self._is_target = True
        self._uart_buffer = []
        self.split_flip = split_flip
        self.split_side = split_side
        self.split_type = split_type
        self.split_target_left = split_target_left
        self.split_offset = None
        self.data_pin = data_pin
        self.data_pin2 = data_pin2
        self.target_left = target_left
        self.uart_flip = uart_flip
        self._is_target = True
        self._uart = None
        self._uart_interval = uart_interval
        self._debug_enabled = debug_enabled
        if self.split_type == SplitType.BLE:
            try:
                from adafruit_ble import BLERadio
                from adafruit_ble.advertising.standard import (
                    ProvideServicesAdvertisement,
                )
                from adafruit_ble.services.nordic import UARTService

                self.ProvideServicesAdvertisement = ProvideServicesAdvertisement
                self.UARTService = UARTService
            except ImportError:
                print('BLE Import error')
                return  # BLE isn't supported on this platform
            self._ble = BLERadio()
            self._ble_last_scan = ticks_ms() - 5000
            self._connection_count = 0
            self._uart_connection = None
            self._advertisment = None
            self._advertising = False
            self._psave_enable = False

    def during_bootup(self, keyboard):
        # Set up name for target side detection and BLE advertisment
        name = str(getmount('/').label)
        if self.split_type == SplitType.BLE:
            self._ble.name = name
        else:
            # Try to guess data pins if not supplied
            if not self.data_pin:
                self.data_pin = keyboard.data_pin

        # Detect split side from name
        if self.split_side is None:
            if name.endswith('L'):
                # If name ends in 'L' assume left and strip from name
                self._is_target = bool(self.split_target_left)
                self.split_side = SplitSide.LEFT
            elif name.endswith('R'):
                # If name ends in 'R' assume right and strip from name
                self._is_target = not bool(self.split_target_left)
                self.split_side = SplitSide.RIGHT

        # if split side was given, find master from split_side.
        elif self.split_side == SplitSide.LEFT:
            self._is_target = bool(self.split_target_left)
        elif self.split_side == SplitSide.RIGHT:
            self._is_target = not bool(self.split_target_left)

        # Flips the col pins if PCB is the same but flipped on right
        if self.split_flip and self.split_side == SplitSide.RIGHT:
            keyboard.col_pins = list(reversed(keyboard.col_pins))

        self.split_offset = len(keyboard.col_pins)

        if self.split_type == SplitType.UART and self.data_pin is not None:
            if self._is_target:
                self._uart = busio.UART(
                    tx=self.data_pin2, rx=self.data_pin, timeout=self._uart_interval
                )
            else:
                self._uart = busio.UART(
                    tx=self.data_pin, rx=self.data_pin2, timeout=self._uart_interval
                )

        # Attempt to sanely guess a coord_mapping if one is not provided.
        if not keyboard.coord_mapping:
            keyboard.coord_mapping = []

            rows_to_calc = len(keyboard.row_pins) * 2
            cols_to_calc = len(keyboard.col_pins) * 2

            for ridx in range(rows_to_calc):
                for cidx in range(cols_to_calc):
                    keyboard.coord_mapping.append(intify_coordinate(ridx, cidx))

    def before_matrix_scan(self, keyboard):
        if self.split_type == SplitType.BLE:
            self._check_all_connections()
            self._receive_ble(keyboard)
        elif self.split_type == SplitType.UART:
            if self._is_target or self.data_pin2:
                self._receive_uart(keyboard)
        elif self.split_type == SplitType.ONEWIRE:
            pass  # Protocol needs written
        return

    def after_matrix_scan(self, keyboard):
        if keyboard.matrix_update:
            if self.split_type == SplitType.UART and self._is_target:
                pass  # explicit pass just for dev sanity...
            elif self.split_type == SplitType.UART and (
                self.data_pin2 or not self._is_target
            ):
                self._send_uart(keyboard.matrix_update)
            elif self.split_type == SplitType.BLE:
                self._send_ble(keyboard.matrix_update)
            elif self.split_type == SplitType.ONEWIRE:
                pass  # Protocol needs written
            else:
                print('Unexpected case in after_matrix_scan')

        return

    def before_hid_send(self, keyboard):
        if not self._is_target:
            keyboard.hid_pending = False

        return

    def after_hid_send(self, keyboard):
        return

    def on_powersave_enable(self, keyboard):
        if self.split_type == SplitType.BLE:
            if self._uart_connection and not self._psave_enable:
                self._uart_connection.connection_interval = self._uart_interval
                self._psave_enable = True

    def on_powersave_disable(self, keyboard):
        if self.split_type == SplitType.BLE:
            if self._uart_connection and self._psave_enable:
                self._uart_connection.connection_interval = 11.25
                self._psave_enable = False

    def _check_all_connections(self):
        '''Validates the correct number of BLE connections'''
        self._connection_count = len(self._ble.connections)
        if self._is_target and self._connection_count < 2:
            self._target_advertise()
        elif not self._is_target and self._connection_count < 1:
            self._initiator_scan()

    def _initiator_scan(self):
        '''Scans for target device'''
        self._uart = None
        self._uart_connection = None
        # See if any existing connections are providing UARTService.
        self._connection_count = len(self._ble.connections)
        if self._connection_count > 0 and not self._uart:
            for connection in self._ble.connections:
                if self.UARTService in connection:
                    self._uart_connection = connection
                    self._uart_connection.connection_interval = 11.25
                    self._uart = self._uart_connection[self.UARTService]
                    break

        if not self._uart:
            if self._debug_enabled:
                print('Scanning')
            self._ble.stop_scan()
            for adv in self._ble.start_scan(
                self.ProvideServicesAdvertisement, timeout=20
            ):
                if self._debug_enabled:
                    print('Scanning')
                if self.UARTService in adv.services and adv.rssi > -70:
                    self._uart_connection = self._ble.connect(adv)
                    self._uart_connection.connection_interval = 11.25
                    self._uart = self._uart_connection[self.UARTService]
                    self._ble.stop_scan()
                    if self._debug_enabled:
                        print('Scan complete')
                    break
        self._ble.stop_scan()

    def _target_advertise(self):
        '''Advertises the target for the initiator to find'''
        self._ble.stop_advertising()
        if self._debug_enabled:
            print('Advertising')
        # Uart must not change on this connection if reconnecting
        if not self._uart:
            self._uart = self.UARTService()
        advertisement = self.ProvideServicesAdvertisement(self._uart)

        self._ble.start_advertising(advertisement)

        self.ble_time_reset()
        while not self.ble_rescan_timer():
            self._connection_count = len(self._ble.connections)
            if self._connection_count > 1:
                self.ble_time_reset()
                if self._debug_enabled:
                    print('Advertising complete')
                break
        self._ble.stop_advertising()

    def ble_rescan_timer(self):
        '''If true, the rescan timer is up'''
        return bool(ticks_diff(ticks_ms(), self._ble_last_scan) > 5000)

    def ble_time_reset(self):
        '''Resets the rescan timer'''
        self._ble_last_scan = ticks_ms()

    def _send_ble(self, update):
        if self._uart:
            try:
                if not self._is_target:
                    update[1] += self.split_offset
                self._uart.write(update)
            except OSError:
                try:
                    self._uart.disconnect()
                except:  # noqa: E722
                    if self._debug_enabled:
                        print('UART disconnect failed')

                if self._debug_enabled:
                    print('Connection error')
                self._uart_connection = None
                self._uart = None

    def _receive_ble(self, keyboard):
        if self._uart is not None and self._uart.in_waiting > 0 or self._uart_buffer:
            while self._uart.in_waiting >= 3:
                self._uart_buffer.append(self._uart.read(3))
            if self._uart_buffer:
                keyboard.secondary_matrix_update = bytearray(self._uart_buffer.pop(0))
                return

    def _send_uart(self, update):
        # Change offsets depending on where the data is going to match the correct
        # matrix location of the receiever
        if self._is_target:
            if self.split_target_left:
                update[1] += self.split_offset
            else:
                update[1] -= self.split_offset
        else:
            if self.split_target_left:
                update[1] += self.split_offset
            else:
                update[1] -= self.split_offset

        if self._uart is not None:
            self._uart.write(update)

    def _receive_uart(self, keyboard):
        if self._uart is not None and self._uart.in_waiting > 0 or self._uart_buffer:
            if self._uart.in_waiting >= 60:
                # This is a dirty hack to prevent crashes in unrealistic cases
                import microcontroller

                microcontroller.reset()

            while self._uart.in_waiting >= 3:
                self._uart_buffer.append(self._uart.read(3))
            if self._uart_buffer:
                keyboard.secondary_matrix_update = bytearray(self._uart_buffer.pop(0))

                return
예제 #10
0
class BLEHID(AbstractHID):
    BLE_APPEARANCE_HID_KEYBOARD = const(961)
    # Hardcoded in CPy
    MAX_CONNECTIONS = const(2)

    def __init__(self, ble_name=str(getmount('/').label), **kwargs):
        self.ble_name = ble_name
        super().__init__()

    def post_init(self):
        self.ble = BLERadio()
        self.ble.name = self.ble_name
        self.hid = HIDService()
        self.hid.protocol_mode = 0  # Boot protocol

        # Security-wise this is not right. While you're away someone turns
        # on your keyboard and they can pair with it nice and clean and then
        # listen to keystrokes.
        # On the other hand we don't have LESC so it's like shouting your
        # keystrokes in the air
        if not self.ble.connected or not self.hid.devices:
            self.start_advertising()

    @property
    def devices(self):
        '''Search through the provided list of devices to find the ones with the
        send_report attribute.'''
        if not self.ble.connected:
            return {}

        result = {}

        for device in self.hid.devices:
            if not hasattr(device, 'send_report'):
                continue
            us = device.usage
            up = device.usage_page

            if up == HIDUsagePage.CONSUMER and us == HIDUsage.CONSUMER:
                result[HIDReportTypes.CONSUMER] = device
                continue

            if up == HIDUsagePage.KEYBOARD and us == HIDUsage.KEYBOARD:
                result[HIDReportTypes.KEYBOARD] = device
                continue

            if up == HIDUsagePage.MOUSE and us == HIDUsage.MOUSE:
                result[HIDReportTypes.MOUSE] = device
                continue

            if up == HIDUsagePage.SYSCONTROL and us == HIDUsage.SYSCONTROL:
                result[HIDReportTypes.SYSCONTROL] = device
                continue

        return result

    def hid_send(self, evt):
        if not self.ble.connected:
            return

        # int, can be looked up in HIDReportTypes
        reporting_device_const = evt[0]

        device = self.devices[reporting_device_const]

        report_size = len(device._characteristic.value)
        while len(evt) < report_size + 1:
            evt.append(0)

        return device.send_report(evt[1:report_size + 1])

    def clear_bonds(self):
        import _bleio

        _bleio.adapter.erase_bonding()

    def start_advertising(self):
        if not self.ble.advertising:
            advertisement = ProvideServicesAdvertisement(self.hid)
            advertisement.appearance = self.BLE_APPEARANCE_HID_KEYBOARD

            self.ble.start_advertising(advertisement)

    def stop_advertising(self):
        self.ble.stop_advertising()
예제 #11
0
class Room:
    def __init__(self, use_debug=False):
        self.use_debug = use_debug
        self.subscription_ids = {}
        self.subscription_messages_on_reconnect = []
        self.ble = BLERadio()
        self.uart_server = UARTService()
        self.advertisement = ProvideServicesAdvertisement(self.uart_server)
        self.chunk_size = 20  # adafruit_ble can only write in 20 byte chunks
        self.recv_msg_cache = ""
    
    def debug(self, msg):
        if self.use_debug:
            print(msg)

    def cleanup(self):
        self.uart_server.write('~:\n'.encode("utf-8"))

    def claim(self, claim_str):
        # Cleanup claim = cleanup all previous claims
        self.uart_server.write('N:{}\n'.format(claim_str).encode("utf-8"))
    
    def when(self, query_strings, callback):
        x = str(random.randint(0, 9999))
        subscription_id = '0'*(4-len(x)) + x  # like 0568
        self.subscription_ids[subscription_id] = callback
        # S:0568:$ $ value is $x::$ $ $x is open
        x = 'S:{}:{}\n'.format(subscription_id, '::'.join(query_strings)).encode("utf-8")
        self.subscription_messages_on_reconnect.append(x)
    
    def parse_results(self, val):
        self.debug("parsing results: {}".format(val))
        result_vals = val[1:-1].split("},{")
        results = []
        for result_val in result_vals:
            result = {}
            rvs = result_val.split(",")
            for rv in rvs:
                kv = rv.strip().split(":")
                result[kv[0].replace('"', '').replace("'", '')] = kv[1].strip()
            results.append(result)
        return results
    
    def check_read_msg_cache_and_callbacks(self):
        lines = self.recv_msg_cache.split("\n")
        self.debug("lines: {}".format(lines))
        if len(lines) > 1:
            # trim off the last line (either '' if \n is the last char or a partial string)
            # because it is not part of a string that ends in a \n
            full_lines = lines[:-1]
            for line_msg in full_lines:
                self.debug("Proccesing new sub update: {}".format(line_msg))
                # 1234[{x:"5",y:"1"},{"x":1,"y":2}]
                sub_id = line_msg[:4] # first four characters of message are sub id
                val = line_msg[4:]
                if sub_id not in self.subscription_ids:
                    print("Unknown sub id {}".format(sub_id))
                    continue
                callback = self.subscription_ids[sub_id]
                callback(self.parse_results(val))
            self.recv_msg_cache = lines[-1]

    def listen_and_update_subscriptions(self):
        # self.debug("listening~~~~~")
        if self.uart_server.in_waiting:
            read_msg = self.uart_server.read(self.uart_server.in_waiting)
            self.recv_msg_cache += read_msg.decode("utf-8")
            self.check_read_msg_cache_and_callbacks()
            self.debug("sub update: {}".format(self.recv_msg_cache))

    def connected(self):
        if not self.ble.connected:
            # Advertise when not connected.
            print("BLE not connected, advertising...")
            self.ble.start_advertising(self.advertisement)
            while not self.ble.connected:
                pass
            self.ble.stop_advertising()
            print("BLE now connected")
            time.sleep(1.0)  # Give BLE connector time setup before sending data
            for sub_msg in self.subscription_messages_on_reconnect:
                self.debug("Sending sub message: {}".format(sub_msg))
                self.uart_server.write(sub_msg)
        self.listen_and_update_subscriptions()
        return True