GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Pull Request — master (#2)
by
unknown
01:28
created

bricknil.ble_queue.BLEventQ.connect()   B

Complexity

Conditions 5

Size

Total Lines 34
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 24
nop 2
dl 0
loc 34
rs 8.8373
c 0
b 0
f 0
1
# Copyright 2019 Virantha N. Ekanayake 
2
# 
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
# 
7
# http://www.apache.org/licenses/LICENSE-2.0
8
# 
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14
15
"""Singleton interface to the Adafruit Bluetooth library"""
16
import Adafruit_BluefruitLE
17
from curio import Queue, sleep, CancelledError
18
import sys, functools, uuid
19
20
from .sensor import Button # Hack! only to get the button sensor_id for the fake attach message
21
from .process import Process
22
from .message_dispatch import MessageDispatch
23
from .const import USE_BLEAK
24
25
# Need a class to represent the bluetooth adapter provided
26
# by adafruit that receives messages
27
class BLEventQ(Process):
28
    """All bluetooth comms go through this object
29
30
       Provides interfaces to connect to a device/hub, send_messages to,
31
       and receive_messages from.  Also abstracts away the underlying bluetooth library
32
       that depends on the OS (Adafruit_Bluefruit for Mac, and Bleak for Linux/Win10)
33
34
       All requests to send messages to the BLE device must be inserted into
35
       the :class:`bricknil.BLEventQ.q` Queue object.
36
37
    """
38
39
    def __init__(self, ble):
40
        super().__init__('BLE Event Q')
41
        self.ble = ble
42
        self.q = Queue()
43
        if USE_BLEAK:
44
            self.message('using bleak')
45
            self.adapter = None
46
            # User needs to make sure adapter is powered up and on
47
            #    sudo hciconfig hci0 up
48
        else:
49
            self.message('Clearing BLE cache data')
50
            self.ble.clear_cached_data()
51
            self.adapter = self.ble.get_default_adapter()
52
            self.message(f'Found adapter {self.adapter.name}')
53
            self.message(f'Powering up adapter {self.adapter.name}')
54
            self.adapter.power_on()
55
        self.hubs = {}
56
57
    async def run(self):
58
        try:
59
            while True:
60
                msg = await self.q.get()
61
                msg_type, hub, msg_val = msg
62
                await self.q.task_done()
63
                self.message_debug(f'Got msg: {msg_type} = {msg_val}')
64
                await self.send_message(hub.tx, msg_val)
65
        except CancelledError:
66
            self.message(f'Terminating and disconnecxting')
67
            if USE_BLEAK:
68
                await self.ble.in_queue.put( 'quit' )
69
            else:
70
                self.device.disconnect()
71
72
    async def send_message(self, characteristic, msg):
73
        """Prepends a byte with the length of the msg and writes it to
74
           the characteristic
75
76
           Arguments:
77
              characteristic : An object from bluefruit, or if using Bleak,
78
                  a tuple (device, uuid : str)
79
              msg (bytearray) : Message with header
80
        """
81
        # Message needs to have length prepended
82
        length = len(msg)+1
83
        values = bytearray([length]+msg)
84
        if USE_BLEAK:
85
            device, char_uuid = characteristic
86
            await self.ble.in_queue.put( ('tx', (device, char_uuid, values)) )
87
        else:
88
            characteristic.write_value(values)
89
90
    async def get_messages(self, hub):
91
        """Instance a Message object to parse incoming messages and setup
92
           the callback from the characteristic to call Message.parse on the
93
           incoming data bytes
94
        """
95
        # Message instance to parse and handle messages from this hub
96
        msg_parser = MessageDispatch(hub)
97
98
        # Create a fake attach message on port 255, so that we can attach any instantiated Button listeners if present
99
        msg_parser.parse(bytearray([15, 0x00, 0x04,255, 1, Button._sensor_id, 0x00, 0,0,0,0, 0,0,0,0]))
100
101
        def bleak_received(sender, data):
102
            self.message_debug(f'Bleak Raw data received: {data}')
103
            msg = msg_parser.parse(data)
104
            self.message_debug('{0} Received: {1}'.format(hub.name, msg))
105
        def received(data):
106
            self.message_debug(f'Adafruit_Bluefruit Raw data received: {data}')
107
            msg = msg_parser.parse(data)
108
            self.message_debug('{0} Received: {1}'.format(hub.name, msg))
109
110
        if USE_BLEAK:
111
            device, char_uuid = hub.tx
112
            await self.ble.in_queue.put( ('notify', (device, char_uuid, bleak_received) ))
113
        else:
114
            # Adafruit library does not callback with the sender, only the data
115
            hub.tx.start_notify(received)
116
117
118
    def _check_devices_for(self, devices, name, manufacturer_id, address):
119
        """Check if any of the devices match what we're looking for
120
           
121
           First, check to make sure the manufacturer_id matches.  If the
122
           manufacturer_id is not present in the BLE advertised data from the 
123
           device, then fall back to the name (although this is unreliable because
124
           the name on the device can be changed by the user through the LEGO apps).
125
126
           Then, if address is supplied, only return a device with a matching id/name
127
           if it's BLE MAC address also agrees
128
129
           Returns:
130
              device : Matching device (None if no matches)
131
        """
132
        for device in devices:
133
            self.message(f'checking manufacturer ID for device named {device.name} for {name}')
134
            # Get the device manufacturer id from the advertised data if present
135
            if device.manufacturer_id == manufacturer_id or device.name == name:
136
                if not address:
137
                    return device
138
                else:
139
                    ble_address = device.address
140
                    if address == ble_address:
141
                        return device
142
                    else:
143
                        self.message(f'Address {ble_address} is not a match')
144
            else:
145
                self.message(f'No match for device with advertised data {device.manufacturer_id}')
146
147
        return None
148
149
    async def _ble_connect(self, uart_uuid, ble_name, ble_manufacturer_id, ble_id=None, timeout=60):
150
        """Connect to the underlying BLE device with the needed UART UUID
151
        """
152
        # Set hub.ble_id to a specific hub id if you want it to connect to a
153
        # particular hardware hub instance
154
        if ble_id:
155
            self.message_info(f'Looking for specific hub id {ble_id}')
156
        else:
157
            self.message_info(f'Looking for first matching hub')
158
159
        # Start discovery
160
        if not USE_BLEAK:
161
            self.adapter.start_scan()
162
163
        try:
164
            found = False
165
            while not found and timeout > 0:
166
                if USE_BLEAK:
167
                    await self.ble.in_queue.put('discover')  # Tell bleak to start discovery
168
                    devices = await self.ble.out_queue.get() # Wait for discovered devices
169
                    await self.ble.out_queue.task_done()
170
                    # Filter out no-matching uuid
171
                    devices = [d for d in devices if str(uart_uuid) in d.uuids]
172
                    # NOw, extract the manufacturer_id
173
                    for device in devices:
174
                        assert len(device.manufacturer_data) == 1
175
                        data = next(iter(device.manufacturer_data.values())) # Get the one and only key
176
                        device.manufacturer_id = data[1]
177
                else:
178
                    devices = self.ble.find_devices(service_uuids=[uart_uuid])
179
                    for device in devices:
180
                        if len(device.advertised) > 4:
181
                            device.manufacturer_id = device.advertised[4]
182
                        else:
183
                            device.manufacturer_id = None
184
                        # Remap device.id to device.address to be consistent with bleak
185
                        device.address = device.id
186
187
                device = self._check_devices_for(devices, ble_name, ble_manufacturer_id,  ble_id)
188
                if device:
189
                    self.device = device
190
                    found = True
191
                else:
192
                    self.message(f'Rescanning for {uart_uuid} ({timeout} tries left)')
193
                    timeout -= 1
194
                    self.device = None
195
                    await sleep(1)
196
            if self.device is None:
197
                raise RuntimeError('Failed to find UART device!')
198
        except:
199
            raise
200
        finally:
201
            if not USE_BLEAK:
202
                self.adapter.stop_scan()
203
204
205
    async def connect(self, hub):
206
        # Connect the messaging queue for communication between self and the hub
207
        hub.message_queue = self.q
208
        self.message(f'Starting scan for UART {hub.uart_uuid}')
209
        ble_id = uuid.UUID(hub.ble_id) if hub.ble_id else None
210
211
        await self._ble_connect(hub.uart_uuid, hub.ble_name, hub.manufacturer_id, ble_id)
212
213
        self.message(f"found device {self.device.name}")
214
215
        if USE_BLEAK:
216
            await self.ble.in_queue.put( ('connect', self.device.address) )
217
            device = await self.ble.out_queue.get()
218
            await self.ble.out_queue.task_done()
219
            hub.ble_id = self.device.address
220
            self.message_info(f'Device advertised: {device.characteristics}')
221
            hub.tx = (device, hub.char_uuid)   # Need to store device because the char is not an object in Bleak, unlike Bluefruit library
222
            # Hack to fix device name on Windows
223
            if self.device.name == "Unknown" and hasattr(device._requester, 'Name'):
224
                self.device.name = device._requester.Name
225
        else:
226
            self.device.connect()
227
            hub.ble_id = self.device.id
228
            # discover services
229
            self.device.discover([hub.uart_uuid], [hub.char_uuid])
230
            uart = self.device.find_service(hub.uart_uuid)
231
            hub.tx = uart.find_characteristic(hub.char_uuid) # same for rx
232
            self.message_info(f'Device advertised {self.device.advertised}')
233
234
235
        self.message_info(f"Connected to device {self.device.name}:{hub.ble_id}")
236
        self.hubs[hub.ble_id] = hub
237
238
        await self.get_messages(hub)
239
240
241
242