Code Monkey home page Code Monkey logo

bricknil's Introduction

BrickNil - Control LEGO Bluetooth Sensors and Motors with Python

image_pypi image_downloads image_license passing quality Coverage Status

BrickNil [*] provides an easy way to connect to and program LEGO® Bluetooth hubs (including the PoweredUp Passenger Train 60197 and Cargo Train 60198 sets, and the Lego Duplo Steam Train 10874 and Cargo Train 10875 ) using Python on OS X and Linux. This work was inspired by this EuroBricks thread, and the NodeJS Powered-Up library. Unlike the official apps available from LEGO, BrickNil allows you to control multiple hubs with a single program, which lets you easily scale to programming large interactive systems.

BrickNil requires modern Python (designed and tested for 3.7) and uses asynchronous event programming built on top of the asyncio async library. As an aside, the choice of async library is fairly arbitrary; and enabling another library such as asyncio or Trio should be straightforward.

An example BrickNil program for controlling the Train motor speed is shown below:

from asyncio import sleep
from bricknil import attach, start
from bricknil.hub import PoweredUpHub
from bricknil.sensor import TrainMotor

@attach(TrainMotor, name='motor')
class Train(PoweredUpHub):

    async def run(self):
        for i in range(2):  # Repeat this control two times
            await self.motor.ramp_speed(80,5000) # Ramp speed to 80 over 5 seconds
            await sleep(6)
            await self.motor.ramp_speed(0,1000)  # Brake to 0 over 1 second
            await sleep(2)

async def system():
    train = Train('My train')

if __name__ == '__main__':
    start(system)
[*]BrickNil's name comes from the word "Nil" (නිල්) in Sinhala which means Blue (as in Bluetooth)

Features

  • Supports the following LEGO® Bluetooth systems:
  • Supports the following actuators/sensors:
    • Internal motors
    • Train motors
    • Hub LED color
    • Boost vision sensor (color, distance)
    • Boost internal tilt/orientation/accelerometer
    • Boost external motor
    • External light
    • Hub buttons
    • Wedo external motor
    • Wedo tiltand motion sensors
  • Fully supports Python asynchronous keywords and coroutines
    • Allows expressive concurrent programming using async/await syntax
  • Cross-platform
    • Uses the Bleak Bluetooth Low Energy library

Building a simple train controller

Let's build a simple program to control a LEGO® PoweredUp train. The first thing to do is create a class that subclasses the Bluetooth hub that we'll be using:

from bricknil.hub import PoweredUpHub

class Train(PoweredUpHub):
   pass

train = Train("My Train")

async def run():
    ...

The run async function (it's actually a coroutine) will contain the code that will control everything attached to this hub. Speaking of which, because we'll be wanting to control the train motor connected to this hub, we'd better attach it to the code like so:

from bricknil.hub import PoweredUpHub
from bricknil.sensor import TrainMotor

@attach(TrainMotor, name='motor')
class Train(PoweredUpHub):
    pass

train = Train()

async def run():
    ...

Now, we can access the motor functions by calling the object train.motor inside run. For example, let's say that we wanted to set the motor speed to 50 (the allowed range is -100 to 100 where negative numbers are reverse speeds):

from asyncio import sleep
from bricknil.hub import PoweredUpHub
from bricknil.sensor import TrainMotor

@attach(TrainMotor, name='motor')
class Train(PoweredUpHub):
    pass

train = Train("My Train")

async def run():
    await train.motor.set_speed(50)
    await sleep(5)  # Wait 5 seconds before exiting

Notice that we're using the await keyword in front of all the calls, because those are also asynchronous coroutines that will get run in the event loop. At some point, the :meth:`bricknil.peripheral.Motor.set_speed` coroutine will finish executing and control will return back to the statement after it. The next statement is a call to the sleep coroutine from the asyncio library. It's important to use this, instead of the regular function time.sleep because asyncio.sleep is a coroutine that will not block other tasks from running.

Note that we can use arbitrary Python to build our controller; suppose that we wanted to ramp the motor speed gradually to 80 over 5 seconds, and then reduce speed back to a stop in 1 second, and then repeat it over again. We could implement the run logic as:

async def run():
    for i in range(2):
        await train.motor.ramp_speed(80,5000)
        await sleep(5)
        await train.motor.ramp_speed(0,1000)
        await sleep(2)

The :meth:`bricknil.peripheral.Motor.ramp_speed` function will ramp the speed from whatever it is currently to the target speed over the millisecond duration given (internally, it will change the train speed every 100ms). Here, you can see how things are running concurrently: we issue the ramp_speed command, that will take 5 seconds to complete in the background, so we need to make sure our control logic sleeps for 5 seconds too, to ensure the train has enough time to get up to speed, before we issue the braking command. Once the train comes to a stop, it will stay stopped for 1 second, then repeat this sequence of speed changes once more before exiting.

It's also useful to print out what's happening as we run our program. In order to facilitate that, there is some rudimentary logging capability built-in to bricknil via the :class:`bricknil.process.Process` class that all of these concurrent processes are sub-classed from. Here's the run coroutine with logging statements via :meth:`bricknil.process.Process.message_info` enabled:

async def run():
    self.message_info("Running")
    for i in range(2):
        train.message_info('Increasing speed')
        await train.motor.ramp_speed(80,5000)
        await sleep(5)
        train.message_info('Coming to a stop')
        await train.motor.ramp_speed(0,1000)
        await sleep(2)

Of course, just running the above code isn't quite enough to execute the controller. Once we have the controller logic implemented, we can go ahead and run it:

from asyncio import sleep
from bricknil import attach, start
from bricknil.hub import PoweredUpHub
from bricknil.sensor import TrainMotor
from bricknil.process import Process
import logging

@attach(TrainMotor, name='motor')
class Train(PoweredUpHub):
    pass

train = Train("My Train")

async def run():
    train.message_info("Running")
    for i in range(2):
        train.message_info('Increasing speed')
        await train.motor.ramp_speed(80,5000)
        await sleep(5)
        train.message_info('Coming to a stop')
        await train.motor.ramp_speed(0,1000)
        await sleep(2)

if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO)
    start(run)

Running this program will output the following:

BLE Event Q.0: Looking for first matching hub
BLE Event Q.0: Connected to device
BLE Event Q.0: Device name HUB NO.4
BLE Event Q.0: Device id XXXX-XXXX
BLE Event Q.0: Device advertised [UUID('XXXXX')]
My train.2: Waiting for peripheral motor to attach to a port
My train.2: Running
My train.2: Increasing speed
motor.1: Starting ramp of speed: 0 -> 80 (5.0s)
motor.1: Setting speed to 0
motor.1: Setting speed to 1
motor.1: Setting speed to 3
motor.1: Setting speed to 4
...
motor.1: Setting speed to 80
My train.2: Coming to a stop
motor.1: Starting ramp of speed: 76 -> 0 (1.0s)
motor.1: Setting speed to 76
motor.1: Setting speed to 68
motor.1: Setting speed to 60
motor.1: Setting speed to 53
motor.1: Setting speed to 45
motor.1: Setting speed to 38
motor.1: Setting speed to 30
motor.1: Setting speed to 22
motor.1: Setting speed to 15
motor.1: Setting speed to 0
... repeats

Integrating a vision sensor into a simple train controller

Now let's build a controller that sets the speed of the train depending on how close your hand is to a snensor, and will quit the program if you wave your hand more than three times in front of it.

We'll be using the Vision Sensor that comes with the LEGO Boost robotics kit; plug the sensor into the second port of the train's PoweredUP hub. This sensor has a multitude of different sensing abilities including distance and color, but for this example, we're just going to use the sense_count and sense_distance capabilities. The former measures how many times it sees something pass in front of it at a distance of ~2 inches, while the latter measures roughly how many inches something is from the sensor (from 1-10 inches). For a full list of the supported capabilities, please see the API documentation at :class:`bricknil.sensor.VisionSensor`.

The full program is listed at the end of this section, but let's just go through it bit by bit. The first thing we'll do is attach the sensor to the Train class:

@attach(VisionSensor, name='train_sensor', capabilities=['sense_count', 'sense_distance'])
@attach(TrainMotor, name='motor')
class Train(PoweredUpHub):
     ...

Anytime you attach a sensor to the system (motion, tilt, color, etc), you need to define what capabilities you want to enable; each sensor can physically provide different capabilities depending on which sensor you're using. As soon as you attach a sensor, you need to provide a call-back coroutine that will be called whenever the sensor detects a change like so:

@attach(VisionSensor, name='train_sensor', capabilities=['sense_count', 'sense_distance'])
@attach(TrainMotor, name='motor')
class Train(PoweredUpHub):

    async def train_sensor_change(self):
        ...

The values will be provided in dictionary called self.value that is indexed by the capability. Let's look at a practical example, and implement the logic we were discussing above:

@attach(VisionSensor, name='train_sensor', capabilities=['sense_count', 'sense_distance'])
@attach(TrainMotor, name='motor')
class Train(PoweredUpHub):

    async def train_sensor_change(self):
        distance = self.train_sensor.value[VisionSensor.capability.sense_distance]
        count = self.train_sensor.value[VisionSensor.capability.sense_count]

        if count > 3:
            # Wave your hand more than three times in front of the sensor and the program ends
            self.keep_running = False

        # The closer your hand gets to the sensor, the faster the motor runs
        self.motor_speed = (10-distance)*10

        # Flag a change
        self.sensor_change = True

Here, we get the distance and count from the value dict. If the count is greater than 3 (more than 3 hand waves), we set a flag that keeps the system running to False. Next, based on the inverse of the distance, we set a motor_speed instance variable, and then use self.sensor_change to signal to the main run routine that a sensor update has happened. Our run logic can now use these values to implement the controller:

async def run(self):
    self.motor_speed = 0
    self.keep_running = True
    self.sensor_change = False

    while self.keep_running:
        if self.sensor_change:
            await self.motor.ramp_speed(self.motor_speed, 900)  # Ramp to new speed in 0.9 seconds
            self.sensor_change = False
        await sleep(1)

We keep running the train while keep_running flag is True; if a sensor_change is detected, we ramp the train speed to the new target self.motor_speed in 0.9 seconds, and then wait for the next sensor update, whenever that may be, at intervals of 1 second. As soon as you pass your hand more than three times in front of the sensor, the program will exit this while loop and end.

Here's the full code:

from asyncio import sleep
from bricknil import attach, start
from bricknil.hub import PoweredUpHub
from bricknil.sensor import TrainMotor, VisionSensor
from bricknil.process import Process
import logging

@attach(VisionSensor, name='train_sensor', capabilities=['sense_count', 'sense_distance'])
@attach(TrainMotor, name='motor')
class Train(PoweredUpHub):

    async def train_sensor_change(self):
        distance = self.train_sensor.value[VisionSensor.capability.sense_distance]
        count = self.train_sensor.value[VisionSensor.capability.sense_count]

        if count > 3:
            # Wave your hand more than three times in front of the sensor and the program ends
            self.keep_running = False

        # The closer your hand gets to the sensor, the faster the motor runs
        self.motor_speed = (10-distance)*10

        # Flag a change
        self.sensor_change = True

    async def run(self):
        self.motor_speed = 0
        self.keep_running = True
        self.sensor_change = False

        while self.keep_running:
            if self.sensor_change:
                await self.motor.ramp_speed(self.motor_speed, 900)  # Ramp to new speed in 0.9 seconds
                self.sensor_change = False
            await sleep(1)

async def system():
    train = Train('My Train')

if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO)
    start(system)

More examples

Connecting to a specific hub

If you know the BluetoothLE network address of the hub you want to connect to, then you can force a Hub object to only connect to that hub. This can be useful, for example, for connecting to two trains that need to have different code and can be accomplished by passing in the ble_id argument during instantiation of the Hub.

On Windows and Linux, you will use the 6-byte Bluetooth network address:

train1 = Train('train1', ble_id='XX:XX:XX:XX:XX:XX')
train2 = Train('train2', ble_id='YY:YY:YY:YY:YY:YY')

bricknil.run(...)

And on OS X systems, you will use the UUID for the Bluetooth hub like so:

train1 = Train('train1', ble_id='05c5e50e-XXXX-XXXX-XXXX-XXXXXXXXXXXX')
train1 = Train('train2', ble_id='05c5e50e-YYYY-YYYY-YYYY-YYYYYYYYYYYY')

bricknil.run(...)

Hub buttons and LED colors

Here's an example that enables the train motor, vision sensor, hub button, and hub LED. First, we'll wait until the hub button is pressed before we do anything; the LED will blink purple and yellow while it's waiting. Then, we'll change the speed like the previous example based on the vision distance, while at the same time changing the LED color orange if it's responding to a distance change.

.. literalinclude:: ../examples/train_all.py
    :language: python

Controlling Vernie (Boost Hub) with the PoweredUp Remote

Here's a nice example of controlling two hubs (the remote is also a type of hub) and feeding the button presses of the remote to make Vernie move forward, backward, left, and right.

And here's the code that's being used in the video above:

.. literalinclude:: ../examples/vernie_remote.py
    :language: python

Here, we are using two hubs running in parallel, and we use a curio.Queue to send messages from the remote telling the robot(Boost hub) what to do. Notice that each RemoteButtons instance consists of 3 buttons, so there are some helper methods to check if a particular button is pressed.

Using the Duplo Train and Playing Sounds

The Duplo trains 10874 and 10875 have the ability to play a set of 5 predetermined sounds through their built-in speakers (:class:`bricknil.sensor.DuploSpeaker`). In addition, there is a speedometer(:class:`bricknil.sensor.DuploSpeedSensor`) built-in to the front wheels, and a vision sensor(:class:`bricknil.sensor.DuploVisionSensor`) in the undercarriage pointing straight down. This vision sensor is slightly less capable than the stand-alone Boost Vision Sensor discussed above, but it can still recognize colors, distance, and the special blocks that Lego provides in those sets. Here's an example that puts everything together:

.. literalinclude:: ../examples/duplo_train.py
    :language: python


BrickNil Architecture

This section documents the internal architecture of BrickNil and how all the components communicate with each other.

Both, the Bluetooth handling and the user-defined code runs in a signle asyncio loop.

Installation

On all platforms (OS X, Linux, Win10), it should just be a simple:

$ pip install bricknil

On Linux, you might need to install the BlueZ >= 5.43 libraries.

On a Raspberry Pi (and other Linux boxes should be similar), you can follow my automated :doc:`setup instructions <pi_setup>`

Supported Devices

BrickNil supported Lego® devices
Peripheral Sets Support
PoweredUp Hub (88009) :amzn:`60197 <B07CC37F63>`, :amzn:`60198 <B07C39LCZ9>`, :amzn:`76112 <B07BMGR1FN>`
:class:`~bricknil.hub.PoweredUpHub`
PoweredUp Train Motor (88011) :amzn:`60197 <B07CC37F63>`, :amzn:`60198 <B07C39LCZ9>` :class:`~bricknil.sensor.motor.TrainMotor`
PoweredUp Remote (88010) :amzn:`60197 <B07CC37F63>`, :amzn:`60198 <B07C39LCZ9>`
:class:`~bricknil.hub.PoweredUpRemote`
PoweredUp Light (88005) 88005 :class:`~bricknil.sensor.light.Light`
Boost Hub (88006) :amzn:`17101 <B06Y6JCTKH>`
:class:`~bricknil.hub.BoostHub`
Boost Vision Sensor (88007) :amzn:`17101 <B06Y6JCTKH>` :class:`~bricknil.sensor.sensor.VisionSensor`
Boost External Motor (88008) :amzn:`17101 <B06Y6JCTKH>` :class:`~bricknil.sensor.motor.ExternalMotor`
Wedo External Motor (45303) :amzn:`45300 <B01A9A9XLW>` :class:`~bricknil.sensor.motor.WedoMotor`
Wedo Tilt Sensor (45305) :amzn:`45300 <B01A9A9XLW>` :class:`~bricknil.sensor.sensor.ExternalTiltSensor`
Wedo Motion Sensor (45304) :amzn:`45300 <B01A9A9XLW>` :class:`~bricknil.sensor.sensor.ExternalMotionSensor`
Duplo Train Base :amzn:`10874 <B07BK6M2WC>`, :amzn:`10875 <B07BK6KQR6>`
:class:`~bricknil.hub.DuploTrainHub`

Credits

  • Virantha N. Ekanayake :gh_user:`virantha` - lead developer
  • David Lechner :gh_user:`dlech` - contributor
    • Added Windows 10 support
    • Added support for Lego 88005 stand-alone LED peripheral

This project is also greatly indebted to the following persons, as well as their open-sourced libraries, portions of which have been incorporated into BrickNil under the terms of their respective licenses:

Disclaimer

The software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Licensed under ASL 2.0

bricknil's People

Contributors

virantha avatar janvrany avatar justxi avatar dlech avatar

Stargazers

jan kebrdle avatar Andrew Baumann avatar Johannes Ringler avatar  avatar Evan Sklarski avatar

Watchers

James Cloos avatar  avatar  avatar

bricknil's Issues

Duplo Train Hub - peripherial connection to port not happening, intermittently

When trying to connect to a Duplo Train hub I often get a recurring loop where the output in the terminal is just:
Waiting for peripheral motor to attach to a port
Waiting for peripheral motor to attach to a port
Waiting for peripheral motor to attach to a port
Waiting for peripheral motor to attach to a port
Waiting for peripheral motor to attach to a port

That runs until I exit (ctrl+c) power cycle the hub and try again. I'll sometimes need to go through this numerous times to get the hub to connect, but when it does it works fine.

I cannot be sure this is an issue with Bricknil as it could maybe be Bleak, Bluez, the OS or something else. I am using the devel branch in a fresh virtual environment, with only the other dependencies installed, on an up-to-date Ubuntu 20.04 install (Python: 3.8.2). Coding in VScode.

I have tested the hub with the android app and it runs flawlessly, so I don't suspect it as a problem.

This problem has occurred with all combinations of bricknil and bleak I have tried, however this combination ('devel' + 'bleak 0.7.0') is the only one that does not regularly crash somewhere along the process (so you've fixed that problem :-) ).

Any thoughts on where to start troubleshooting would be great as at the moment there are no error messages thrown and the loop seems happy to run indefinitely never connecting, until I try again.

Installation / Multiple hubs?

Hi,

what additional libraries are necessary to run your code?

And is it possible to acces two or more Control+ Hubs at the same time?

Regression in ramp_speed

create_task() got an unexpected keyword argument 'daemon'
  File "[C:\src\bricknil\bricknil\sensor\motor.py]()", line 97, in ramp_speed
    self.ramp_in_progress_task = await spawn(_ramp_speed, daemon = True)
  File "[C:\src\lego\train.py]()", line 87, in start
    await self.motor.ramp_speed(SLOW_SPEED, 300)
  File "[C:\src\lego\train.py]()", line 174, in run
    await train.start()
  File "[C:\src\bricknil\bricknil\bricknil.py]()", line 149, in main
    await program
  File "[C:\src\bricknil\bricknil\bricknil.py]()", line 153, in run
    loop.run_until_complete(main())
  File "[C:\src\lego\train.py]()", line 179, in main
    bricknil.run(run())
  File "[C:\src\lego\train.py]()", line 184, in <module>
    main()

Change of hub button does not give a signal

Hi,

using latest devel branch with the following program does not give a signal when the hub button is pressed:

 
#!/usr/bin/env python3

import logging
from asyncio import sleep
from bricknil import attach, run
from bricknil.hub import CPlusHub
from bricknil.sensor.motor import CPlusXLMotor
from bricknil.sensor.motor import CPlusLargeMotor
from bricknil.sensor import Button
from bricknil.sensor import LED
from bricknil.const import Color

@attach(LED, name='hub_led')
@attach(Button, name='hub_btn', capabilities=['sense_press'])
@attach(CPlusXLMotor, name='drive', port=3)
#@attach(CPlusXLMotor, name='drive', port=3, capabilities=['sense_pos'])
@attach(CPlusLargeMotor, name='steering', port=1)
class Rallycar(CPlusHub):

    async def hub_btn_change(self):
        self.message_info('hub_btn ')
        print('test')

#    async def drive_change(self):
#        self.message_info('drive:')


# Now, intantiate model
vehicle = Rallycar("Rallycar")

async def program():

    await vehicle.hub_led.set_color(Color.green)

    await vehicle.drive.set_speed(30)
    await sleep(3)
    await vehicle.drive.set_speed(0)

    await vehicle.hub_led.set_color(Color.red)

    for idx in range(10):
        vehicle.message_info('wait '+str(idx))
        await sleep(1)


if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO)
    run(program())

Previous version prints out a message.
Did you change something?

Cannot import PoweredUpHubIMUPosition from bricknil.sensor

Hello,

I can import e.g. VoltageSensor and CurrentSensor from bricknil.sensor, but not PoweredUpHubIMUPosition, PoweredUpHubIMUGyro, PoweredUpHubIMUAccelerometer.

Message from Python is:
ImportError: cannot import name "PoweredUpHubIMUPosition" from "bricknil.sensor" ([path to init.py])

I use python 3.7.

Do you have a hint?

Powered Up Hub disconnects before leaving "run"?

@janvrany

Hi,

while your implementation works fine for the "Powered Up Technic Hub", I have a problem with the "Powered Up Hub".

Using this program:

#!/usr/bin/env python3

import logging

from asyncio import sleep

from bricknil import attach, start
from bricknil.hub import PoweredUpHub
from bricknil.sensor import TrainMotor

@attach(TrainMotor, name='motor', port=0)
class Train(PoweredUpHub):

    async def run(self):

        self.message_info('run')

        self.message_info('set_speed 50')
        await self.motor.set_speed(50)

        self.message_info('wait 5s')
        await sleep(5)

        self.message_info('set_speed 0')
        await self.motor.set_speed(0)

        self.message_info('wait 5s')
        await sleep(5)


async def system():
    train = Train('My train')

if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO)
    start(system)

I get:

INFO:BLE Event Q.0:Starting scan for UART 00001623-1212-efde-1623-785feabcd123
INFO:BLE Event Q.0:Looking for first matching hub
Awaiting on bleak discover
INFO:bleak.backends.bluezdbus.discovery:dev_64_38_13_D0_D4_3E, 64:38:13:D0:D4:3E (? dBm), Object Path: /org/bluez/hci0/dev_64_38_13_D0_D4_3E
Done Awaiting on bleak discover
INFO:BLE Event Q.0:Rescanning for 00001623-1212-efde-1623-785feabcd123 (60 tries left)
Awaiting on bleak discover
Done Awaiting on bleak discover
INFO:BLE Event Q.0:Rescanning for 00001623-1212-efde-1623-785feabcd123 (59 tries left)
Awaiting on bleak discover
INFO:bleak.backends.bluezdbus.discovery:dev_64_38_13_D0_D4_3E, 64:38:13:D0:D4:3E (-95 dBm), Object Path: /org/bluez/hci0/dev_64_38_13_D0_D4_3E
INFO:bleak.backends.bluezdbus.discovery:dev_90_84_2B_1B_05_E7, 90:84:2B:1B:05:E7 (-60 dBm), Object Path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7
INFO:bleak.backends.bluezdbus.discovery:dev_90_84_2B_1B_05_E7, 90:84:2B:1B:05:E7 (-60 dBm), Object Path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7
INFO:bleak.backends.bluezdbus.discovery:dev_64_38_13_D0_D4_3E, 64:38:13:D0:D4:3E (-95 dBm), Object Path: /org/bluez/hci0/dev_64_38_13_D0_D4_3E
INFO:bleak.backends.bluezdbus.discovery:dev_90_84_2B_1B_05_E7, 90:84:2B:1B:05:E7 (-60 dBm), Object Path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7
Done Awaiting on bleak discover
INFO:BLE Event Q.0:Rescanning for 00001623-1212-efde-1623-785feabcd123 (58 tries left)
Awaiting on bleak discover
INFO:bleak.backends.bluezdbus.discovery:Smart Hub, 90:84:2B:1B:05:E7 (-60 dBm), Object Path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7
INFO:bleak.backends.bluezdbus.discovery:Smart Hub, 90:84:2B:1B:05:E7 (-59 dBm), Object Path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7
INFO:bleak.backends.bluezdbus.discovery:Smart Hub, 90:84:2B:1B:05:E7 (-61 dBm), Object Path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7
INFO:bleak.backends.bluezdbus.discovery:Smart Hub, 90:84:2B:1B:05:E7 (-59 dBm), Object Path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7
INFO:bleak.backends.bluezdbus.discovery:dev_64_38_13_D0_D4_3E, 64:38:13:D0:D4:3E (? dBm), Object Path: /org/bluez/hci0/dev_64_38_13_D0_D4_3E
INFO:bleak.backends.bluezdbus.discovery:Smart Hub, 90:84:2B:1B:05:E7 (-59 dBm), Object Path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7
Done Awaiting on bleak discover
INFO:BLE Event Q.0:checking manufacturer ID for device named Smart Hub for HUB NO.4
INFO:BLE Event Q.0:found device Smart Hub
INFO:bleak.backends.bluezdbus.discovery:Smart Hub, 90:84:2B:1B:05:E7 (-60 dBm), Object Path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7
INFO:bleak.backends.bluezdbus.discovery:Smart Hub, 90:84:2B:1B:05:E7 (-61 dBm), Object Path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7
INFO:bleak.backends.bluezdbus.discovery:Smart Hub, 90:84:2B:1B:05:E7 (-60 dBm), Object Path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7
INFO:bleak.backends.bluezdbus.discovery:Smart Hub, 90:84:2B:1B:05:E7 (-60 dBm), Object Path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7
INFO:bleak.backends.bluezdbus.discovery:Smart Hub, 90:84:2B:1B:05:E7 (-59 dBm), Object Path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7
INFO:bleak.backends.bluezdbus.discovery:Smart Hub, 90:84:2B:1B:05:E7 (-61 dBm), Object Path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7
INFO:bleak.backends.bluezdbus.discovery:Smart Hub, 90:84:2B:1B:05:E7 (-59 dBm), Object Path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7
INFO:bleak.backends.bluezdbus.discovery:dev_64_38_13_D0_D4_3E, 64:38:13:D0:D4:3E (? dBm), Object Path: /org/bluez/hci0/dev_64_38_13_D0_D4_3E
INFO:bleak.backends.bluezdbus.discovery:Smart Hub, 90:84:2B:1B:05:E7 (-59 dBm), Object Path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7
DEBUG:bleak.backends.bluezdbus.client:Connecting to BLE device @ 90:84:2B:1B:05:E7 with hci0
DEBUG:bleak.backends.bluezdbus.client:Connection successful.
DEBUG:bleak.backends.bluezdbus.client:Get Services...
DEBUG:bleak.backends.bluezdbus.client:
Primary Service
	/org/bluez/hci0/dev_90_84_2B_1B_05_E7/service000c
	00001623-1212-efde-1623-785feabcd123
	Unknown
DEBUG:bleak.backends.bluezdbus.client:
Characteristic
	/org/bluez/hci0/dev_90_84_2B_1B_05_E7/service000c/char000d
	00001624-1212-efde-1623-785feabcd123
	Unknown
DEBUG:bleak.backends.bluezdbus.client:
Descriptor
	/org/bluez/hci0/dev_90_84_2B_1B_05_E7/service000c/char000d/desc000f
	00002902-0000-1000-8000-00805f9b34fb
	Client Characteristic Configuration
DEBUG:bleak.backends.bluezdbus.client:
Primary Service
	/org/bluez/hci0/dev_90_84_2B_1B_05_E7/service0001
	00001801-0000-1000-8000-00805f9b34fb
	Generic Attribute Profile
DEBUG:bleak.backends.bluezdbus.client:
Characteristic
	/org/bluez/hci0/dev_90_84_2B_1B_05_E7/service0001/char0002
	00002a05-0000-1000-8000-00805f9b34fb
	Service Changed
DEBUG:bleak.backends.bluezdbus.client:
Descriptor
	/org/bluez/hci0/dev_90_84_2B_1B_05_E7/service0001/char0002/desc0004
	00002902-0000-1000-8000-00805f9b34fb
	Client Characteristic Configuration
INFO:BLE Event Q.0:Device advertised: {'00001624-1212-efde-1623-785feabcd123': <bleak.backends.bluezdbus.characteristic.BleakGATTCharacteristicBlueZDBus object at 0x7fd8fe17dcd0>, '00002a05-0000-1000-8000-00805f9b34fb': <bleak.backends.bluezdbus.characteristic.BleakGATTCharacteristicBlueZDBus object at 0x7fd8fe17dfd0>}
INFO:BLE Event Q.0:Connected to device Smart Hub:90:84:2B:1B:05:E7
DEBUG:bleak.backends.bluezdbus.client:DBUS: path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7, domain: org.bluez.Device1, body: {'Appearance': 6144}
DEBUG:bleak.backends.bluezdbus.client:DBUS: path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7/service000c/char000d, domain: org.bluez.GattCharacteristic1, body: {'Notifying': True}
INFO:BLE Event Q.0:Waiting for hubs to end
DEBUG:bleak.backends.bluezdbus.client:DBUS: path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7/service000c/char000d, domain: org.bluez.GattCharacteristic1, body: {'Value': [15, 0, 4, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0]}
INFO:bleak.backends.bluezdbus.client:GATT Char Properties Changed: /org/bluez/hci0/dev_90_84_2B_1B_05_E7/service000c/char000d | [{'Value': [15, 0, 4, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0]}, []]
DEBUG:bleak.backends.bluezdbus.client:DBUS: path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7/service000c/char000d, domain: org.bluez.GattCharacteristic1, body: {'Value': [15, 0, 4, 50, 1, 23, 0, 0, 0, 0, 16, 0, 0, 0, 16]}
INFO:bleak.backends.bluezdbus.client:GATT Char Properties Changed: /org/bluez/hci0/dev_90_84_2B_1B_05_E7/service000c/char000d | [{'Value': [15, 0, 4, 50, 1, 23, 0, 0, 0, 0, 16, 0, 0, 0, 16]}, []]
INFO:My train.2:run
INFO:My train.2:set_speed 50
INFO:motor.1:Setting speed to 50
DEBUG:bleak.backends.bluezdbus.client:DBUS: path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7/service000c/char000d, domain: org.bluez.GattCharacteristic1, body: {'Value': [15, 0, 4, 59, 1, 21, 0, 2, 0, 0, 0, 2, 0, 0, 0]}
INFO:bleak.backends.bluezdbus.client:GATT Char Properties Changed: /org/bluez/hci0/dev_90_84_2B_1B_05_E7/service000c/char000d | [{'Value': [15, 0, 4, 59, 1, 21, 0, 2, 0, 0, 0, 2, 0, 0, 0]}, []]
DEBUG:bleak.backends.bluezdbus.client:Write Characteristic 00001624-1212-efde-1623-785feabcd123 | /org/bluez/hci0/dev_90_84_2B_1B_05_E7/service000c/char000d: bytearray(b'\x08\x00\x81\x00\x11Q\x002')
INFO:My train.2:wait 5s
DEBUG:bleak.backends.bluezdbus.client:DBUS: path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7/service000c/char000d, domain: org.bluez.GattCharacteristic1, body: {'Value': [15, 0, 4, 60, 1, 20, 0, 2, 0, 0, 0, 2, 0, 0, 0]}
INFO:bleak.backends.bluezdbus.client:GATT Char Properties Changed: /org/bluez/hci0/dev_90_84_2B_1B_05_E7/service000c/char000d | [{'Value': [15, 0, 4, 60, 1, 20, 0, 2, 0, 0, 0, 2, 0, 0, 0]}, []]
DEBUG:bleak.backends.bluezdbus.client:DBUS: path: /org/bluez/hci0/dev_90_84_2B_1B_05_E7, domain: org.bluez.Device1, body: {'ServicesResolved': False, 'Connected': False}
DEBUG:bleak.backends.bluezdbus.client:Device 90:84:2B:1B:05:E7 disconnected.
DEBUG:bleak.backends.bluezdbus.client:Removing rule PropChanged, ID: 1
INFO:My train.2:set_speed 0
INFO:motor.1:Setting speed to 0
DEBUG:bleak.backends.bluezdbus.client:Disconnecting from BLE device...
INFO:My train.2:Terminating peripheral
DEBUG:bleak.backends.bluezdbus.client:Attempt to stop Twisted reactor failed: Can't stop reactor that isn't running.
Traceback (most recent call last):
  File "citytrain_2.py", line 36, in <module>
    start(system)
  File "/usr/lib/python3.7/site-packages/bricknil/bricknil.py", line 156, in start
    __loop.run_until_complete(main(user_system_setup_func))
  File "/usr/lib/python3.7/asyncio/base_events.py", line 587, in run_until_complete
    return future.result()
  File "/usr/lib/python3.7/site-packages/bricknil/bricknil.py", line 126, in main
    await task
  File "citytrain_2.py", line 25, in run
    await self.motor.set_speed(0)
  File "/usr/lib/python3.7/site-packages/bricknil/sensor/motor.py", line 46, in set_speed
    await self.set_output(0, self._convert_speed_to_val(speed))
  File "/usr/lib/python3.7/site-packages/bricknil/sensor/peripheral.py", line 273, in set_output
    await self.send_message(f'set output port:{self.port} mode: {mode} = {value}', b)
  File "/usr/lib/python3.7/site-packages/bricknil/sensor/peripheral.py", line 240, in send_message
    await self.message_handler(msg, msg_bytes, peripheral=self)
  File "/usr/lib/python3.7/site-packages/bricknil/hub.py", line 110, in send_message
    await self.ble_handler.send_message(self.tx, msg_bytes)
  File "/usr/lib/python3.7/site-packages/bricknil/ble_queue.py", line 63, in send_message
    await device.write_gatt_char(char_uuid, values)
  File "/usr/lib/python3.7/site-packages/bleak/backends/bluezdbus/client.py", line 475, in write_gatt_char
    ).asFuture(self.loop)
txdbus.error.RemoteError: org.bluez.Error.Failed: Not connected

I tried to change the LED color and I can only change the color once.
It seems that the hub disconnects or is disconnected before "run" leaves.
Do you have an idea why this happens?
Or do you have/had a similiar problem?

Assertion failure "Unknown device with id 66" when attaching Boost hub

Whenever I attach to a Boost hub, I hit this assertion:

INFO:BLE Event Q.0:found device LEGO Move Hub
INFO:bleak.backends.winrt.client:Services resolved for BleakClientWinRT (00:16:53:A1:AC:1E)
INFO:BLE Event Q.0:Device advertised: {2: <bleak.backends.winrt.characteristic.BleakGATTCharacteristicWinRT object at 0x00000223E2352950>, 6: <bleak.backends.winrt.characteristic.BleakGATTCharacteristicWinRT object at 0x00000223E23527D0>, 8: <bleak.backends.winrt.characteristic.BleakGATTCharacteristicWinRT object at 0x00000223E2352C20>, 10: <bleak.backends.winrt.characteristic.BleakGATTCharacteristicWinRT object at 0x00000223E2352BF0>, 13: <bleak.backends.winrt.characteristic.BleakGATTCharacteristicWinRT object at 0x00000223E2352710>}
INFO:BLE Event Q.0:Connected to device LEGO Move Hub:00:16:53:A1:AC:1E
INFO:button.2:button.2 attached to Test.6, port 255
INFO:Test.6:Waiting for peripheral motor_ext to attach to a port
INFO:motor_r.4:motor_r.4 attached to Test.6, port 0
INFO:motor_l.3:motor_l.3 attached to Test.6, port 0
INFO:motor_ext.5:motor_ext.5 attached to Test.6, port 2
INFO:led.1:led.1 attached to Test.6, port 50
ERROR:asyncio:Exception in callback BLEventQ.get_messages.<locals>.bleak_received(13, bytearray(b'\...\x00\x00\x10')) at c:\src\bricknil\bricknil\ble_queue.py:76
handle: <Handle BLEventQ.get_messages.<locals>.bleak_received(13, bytearray(b'\...\x00\x00\x10')) at c:\src\bricknil\bricknil\ble_queue.py:76>
Traceback (most recent call last):
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.1264.0_x64__qbz5n2kfra8p0\lib\asyncio\events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "c:\src\bricknil\bricknil\ble_queue.py", line 78, in bleak_received
    msg = msg_parser.parse(data)
  File "c:\src\bricknil\bricknil\message_dispatch.py", line 52, in parse
    Message.parsers[msg_type].parse(msg_bytes, l, self)
  File "c:\src\bricknil\bricknil\messages.py", line 333, in parse 
    assert device_id in DEVICES, f'Unknown device with id {device_id} being attached (port {port}'
AssertionError: Unknown device with id 66 being attached (port 70 being attached

The program here is:

import asyncio
import logging
import bricknil
from bricknil.hub import BoostHub
from bricknil.sensor import InternalMotor, ExternalMotor, LED, Button

@bricknil.attach(LED, name='led')
@bricknil.attach(Button, name='button', capabilities=['sense_press'])
@bricknil.attach(InternalMotor, name='motor_l')
@bricknil.attach(InternalMotor, name='motor_r')
@bricknil.attach(ExternalMotor, name='motor_ext')
class TestHub(BoostHub):
    pass

def main():
    h = TestHub('Test')
    bricknil.run(asyncio.sleep(1))

if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO) # DEBUG
    main()

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.