Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The future of BLE, Bluetooth, and USB #262

Open
laurensvalk opened this issue Feb 26, 2021 · 77 comments
Open

The future of BLE, Bluetooth, and USB #262

laurensvalk opened this issue Feb 26, 2021 · 77 comments
Labels
enhancement New feature or request topic: bluetooth Issues involving bluetooth topic: communication Issues related to hub-to-hub/phone/computer communcation topic: remote control Issues related to remotly controlling hubs

Comments

@laurensvalk
Copy link
Member

laurensvalk commented Feb 26, 2021

Introduction

This issue gathers some ideas for Bluetooth and USB to help us keep track of the big picture while we work on low-level details.

Note that most of this won't be done any time soon (if ever). This is mainly intended to avoid implementation choices now which might come back to hurt us later. If you want us to prioritize this, please consider becoming a sponsor 🚀 .

Nomenclature

  • The code in boxes is pseudocode for commands in user scripts.
  • Arrows: wireless or wired communication.
  • Each color is a different service/characteristic which may or may not share a common connection.

1. Single-hub coding scenario

This is what we are working on today. We are working on a Pybricks BLE service to handle things like starting and stopping programs in a clean way. Standard I/O (print/input) may be a separate service or characteristic on the same connection.

Until now, both were handled on one and the same characteristic, which made it easy to mess up the connection by sending the wrong characters.

Cleaning this up and documenting it also paves the way for other editors or extensions to support Pybricks.

This is the only use case we will target for the upcoming v3.0 release. That is likely also the last release for Move Hub; it won't have enough space for the other features listed below.

image

2. Multi-hub coding scenario

This might work as above, but there are many elements still missing such as:

  • Distinguishing hubs (e.g. by name or color)
  • Multiple Pybricks Code instances (e.g. tabs or windows)

image

3a. Multi-hub communication scenario

This is still to be explored. At the basic level, there might be serial i/o streams between hubs that could be used with standard MicroPython tools. Higher level abstractions such as mailboxes could be added on top of these later.

image

3b. Other BLE UART devices

This is really the same as above, but worth showing in a separate diagram because this really opens up interesting possibilities.

image

4. BLE HID Peripherals

We may support connecting to generic HID devices over BLE (and possibly classic). Users could build on this to add specific mappings for certain devices like popular gaming consoles.

image

5. Bluetooth classic scenario

This is only available on Prime Hub and Inventor Hub. Therefore, we will not use Bluetooth classic for any system purposes. So, you won't be able to download and run programs with it.

Rather, it might be used within end-user scripts. For example, you might set up a generic RFCOMM connection with a server or client. This could be another Prime Hub, an EV3 Brick, a laptop, and so on.

image

6. LEGO Powered Up Remote

See #186.

image

7. USB scenario

This is only available on Prime Hub and Inventor Hub. Therefore, we will probably not use USB for system purposes. So, you probably won't be able to download and run programs with it. USB will be used primarily for charging the hub. We may re-enable REPL over USB if users want this. Since REPL is treated as a regular program, we'd have to define a way to start and stop it. Perhaps USB can also be used for fast(er) file transfer.

@laurensvalk laurensvalk added enhancement New feature or request triage Issues that have not been triaged yet topic: bluetooth Issues involving bluetooth and removed triage Issues that have not been triaged yet labels Feb 26, 2021
@TechnicBRICKs
Copy link

TechnicBRICKs commented Feb 26, 2021

This would be really cool!!
My priorities would be, first scenario # 3 (although this also implies scenario # 2) and then scenario # 4.

My hope is that basic functionality may be feasible with ubluetooth module (# 3), even before you are able to prioritize it, as we start to see examples making use of it.
It would be a matter of finding someone willing to share his work and code examples, or going the hard way and figure it out yourself.

@laurensvalk
Copy link
Member Author

laurensvalk commented Feb 27, 2021

Partly inspired by the existing messaging module, here is one idea for what communication could look like.

Peripherals:

from pybricks.messaging import PeripheralHub

# Can make only one of these
peripheral = PeripheralHub()

# Starts advertising. 
peripheral.advertise(True)

# Waits for incoming connection. On success, stops advertising and returns a stream object, else raises TimeOutException.
hub_connection = peripheral.listen(timeout=1000)

# Now you can send and receive data using hub_connection.read and hub_connection.write

The central:

from pybricks.messaging import CentralHub

# Can make only one of these
central = CentralHub()

# Starts scanning for hubs that are advertising.
central.scan(True)

# Results will be stored in central.scan_results, a dict of  name:address pairs
# So you could wait until some devices are found, or look for a specific one:
while 'my_other_hub' not in central.scan_results:
     pass

# Found it!
address = central.scan_results['my_other_hub']

# Stop scanning
central.scan(False)

# Connect. Returns a stream object on success, else raises TimeOutException.
hub_connection = central.connect(address, timeout=1000)

# Now you can send and receive data using hub_connection.read and hub_connection.write

Peripherals (simplified API idea)

from pybricks.messaging import ble_wait_for_connection

# Waits for incoming connection. On success, stops advertising and returns a stream object, else raises TimeOutException.
stream = ble_wait_for_connection(timeout=1000)

# Now you can send and receive data using stream.read and stream.write

The central (simplified API idea)

from pybricks.messaging import ble_connect

# Connect. Returns a stream object on success, else raises TimeOutException.
stream = ble_connect(address_or_name, timeout=1000)

# Now you can send and receive data using stream.read and stream.write

@mwinkler
Copy link

Really like to see this roadmap!
As TechnicBRICKs already mention, I hope that ubluetooth where available, so that we can experiment and build exciting use cases.

@TechnicBRICKs
Copy link

Partly inspired by the existing messaging module, here is one idea for what communication could look like.

Peripherals:

from pybricks.messaging import PeripheralHub

# Can make only one of these
peripheral = PeripheralHub()

# Starts advertising. 
peripheral.advertise(True)

# Waits for incoming connection. On success, stops advertising and returns a stream object, else raises TimeOutException.
hub_connection = peripheral.listen(timeout=1000)

# Now you can send and receive data using hub_connection.read and hub_connection.write

So it looks halfway done already! :D

Meanwhile maybe playing around these examples could lead us somewhere.
https://github.com/micropython/micropython/tree/master/examples/bluetooth

@laurensvalk
Copy link
Member Author

The examples for ubluetooth already work just fine on the official firmware.

Our objectives are just a little different. Compare those examples with the suggested code snippets above. We're aiming to let you do it with two lines of code and about 10x fewer system resources used. ubluetooth is great for communicating with everything including the kitchen sink, but we don't need all of that just to communicate between hubs.

@TechnicBRICKs
Copy link

Fully understood your approach!
Just thought that for the time being, this could lead to an intermediate solution.

To get it working guess one needs to add the ubluetooth library into your firmware zipfile, correct!?

@laurensvalk
Copy link
Member Author

No, it doesn't work that way. ubluetooth is written in C, not Python.

@mwinkler
Copy link

mwinkler commented Mar 3, 2021

The examples for ubluetooth already work just fine on the official firmware.

Your referring to the official spike prime firmware, correct?
Because there is some stuff missing (pair function for example).

@laurensvalk
Copy link
Member Author

Ah, that's interesting. I haven't tried all of the examples. The ubluetooth module seems to be actively developed still, so perhaps newer spike/inventor firmwares will get the latest versions in future updates too.

@mwinkler, as you've expressed interest in having ubluetooth in the Pybricks firmware too, could you share a few use cases that really need it, which aren't already covered above?

@mwinkler
Copy link

mwinkler commented Mar 3, 2021

With the described use cases, is all covered what I have currently in mind.

My idea is a track based racing game, where every player controls his own vehicle via game pad (or pu remote).
The cars are also connected to a central unit/hub. The central unit then can send commands and receive events from the cars, like increase speed (give a boost), count a marker/checkpoint (to track lap count/time), stop all cars, and so on...

So in the end, if all is implemented in pybricks, I don't need the ubluetooth module.
The only reason I'am asking for the module is, because I have started experimenting with it (HID over GATT with a BLE pad) on spike prime and stuck now because of the missing functions in the lego firmware.
But I see, that providing these modules have impact on memory usage and needs to be carefully selected.

Btw, thank you for the great work you do (you both) with this project.

@NStrijbosch
Copy link

Interesting dicussion!

I have been playing around with the ubluetooth from the official SPIKE Prime firmware to get Scenario 3.A/B working.

I second @laurensvalk statement on the complexity of the code necessary to get everything setup and the resources the ubluetooth module requires. So, once a more resource efficient implementation is available in Pybricks that would be my goto firmware for multi hub projects.

Just a few questions that came to my mind on the implementation of Scenario 3A:

  • what would be the maximum number of pheripherals that can connect to a single central?
  • can a hub be both a central and peripheral at the same time? (e.g., to get a network of three hubs that each have a direct connection to the other two)
  • what happens when a connection is lost? Would that require a complete restart of the programs to get the connection back? or does it trigger a peripheral to start advertising and the central to start scanning automatically?

@laurensvalk
Copy link
Member Author

Thanks @NStrijbosch -- some good points there. We're not sure yet about the connection limits.

I think we could enable the user to monitor the connection status so they can reconnect if they want to. But hopefully it should be stable enough within reasonable distances to the point where most users might not need to do this.

How is your experience with connectivity and stability, since you've worked with 9 hubs at once?

@NStrijbosch
Copy link

From my experience the connection limit of the Robot Inventor/Spike Prime hub:

  • one hub is able to connect to 4 other hubs (3 peripherals and 1 central in my case)
    But I am not knowledgeable enough to know if this is: a hardware limitation; a software limitation in the firmware; or my own implementation. To get 9 hubs to communicate I implemented a BLE Tree network structure, with a few hubs acting as both central and peripheral.

The stability has some issues from time to time, once again not sure if this is hardware/firmware/my software. But some of my observations:

  • For small scale projects (<4 hubs) connection is fine. Although it seems that it it is important to not flood the network at the central side with messages: this leads to an out of memory error. I have not managed to prevent this when implementing the Nordic UART protocol, so I opted for a messaging protocol that prevents this flooding.
  • for bigger projects establishing a connection fails once in a while (at the hub that acts as both a central and peripheral). If this happens an already established connection fails at the moment of connecting a new hub to the same central. Not a big issue if it would have been possible to reestablish the lost connection. But for now the only option I have is restarting all hubs... (which can be a pain with 9 hubs). When everything is connected and the network is not flooded with messages it works fine.

@laurensvalk
Copy link
Member Author

Thanks a lot for sharing this. This will set the minimal benchmark for us 😉

We would certainly be interested in your findings once you test our stuff. We welcome contributions to the code as well.

And good to know that a hub can be both a central and a peripheral, too. We need that, since we use BLE for programming as well, where it is the peripheral to the PC.

@giadefa
Copy link

giadefa commented Mar 16, 2021

Going back to a previous issue which is now referring to this issue for the solution: #164

How do I communicate few floats back and forward between a normal python program running in my PC and the hub micropython? I don't quite see this simple scheme in the description above.

Thanks

@dlech
Copy link
Member

dlech commented Mar 16, 2021

For EV3, we have https://pybricks.github.io/ev3-micropython/messaging.html

For Powered Up hubs, this is not implemented yet.

@laurensvalk
Copy link
Member Author

How do I communicate few floats back and forward between a normal python program running in my PC and the hub micropython? I don't quite see this simple scheme in the description above.

That would be use case 3b. This pictures a phone as an example, but it could be a PC as well. The PC could use any language with BLE support, such as Python with the bleak library, or JS with WebBLE, etc.

And indeed, this is all still in the concept stage.

@dlech
Copy link
Member

dlech commented Mar 17, 2021

One thing I have been thinking about that is specific to BLE UART is that we have write with response or write without response in one direction and indicate (with response) or notify (without response) in the other direction. In the with response case, there should be no packets dropped, but throughput will be lower since there is the extra overhead of a response sent for each packet. In the without response case, the transmitter has no way of knowing if the receiver actually received the data and the receiver has no way of knowing if they missed something (unless it is handled at the protocol level).

So I'm thinking we probably want to default to the slower, safer option. But we might want to add an option for the faster lossy option if there are use cases that require high bandwitdh.

@laurensvalk
Copy link
Member Author

laurensvalk commented Mar 17, 2021

At the moment, we have pybricks.bluetooth for lower-level bluetooth classic and pybricks.messaging with higher level messaging tools. Along these lines, perhaps we can have pybricks.ble for lower level BLE stuff.

I've been brainstorming what that absolute minimum might be, which will still support all of the communication use cases above. It might all boil down to just this:

# SPDX-License-Identifier: MIT
# Copyright (c) 2018-2020 The Pybricks Authors

"""
pybricks.ble: Bluetooth Low Energy communication tools.
"""

class BLEDevice:

    address = "AA:BB"
    """Address of the connected device"""

    name = "my_city_hub"
    """Name of the connected device"""

    mtu = 23
    """Maximum transmission unit."""

    # Add methods and/or attributes such as to configure write with/without response, etc

class NUSDevice(BLEDevice, PybricksIOBuffer):
    pass

def nus_connect(address, timeout=3000) -> NUSDevice:
    """Connects to a peripheral that is currently accepting a connection.

    This hub will act as the central. It can connect to any other peripheral
    that advertises the Nordic UART Service (NUS).

    Arguments:
        address: Bluetooth address or name of the device you wish to connect to.
        timeout: How long to scan for the device before giving up, or
            ``None`` to wait indefinitely.

    Raises:
        TimeoutException: If the connection could not be made within the
            given timeout.

    Returns:
        Stream object representing the connection to the peripheral.
    """
    pass


def nus_wait_for_connection(timeout=None) -> NUSDevice:
    """Accepts an incoming connection from a central.

    This hub will act as a peripheral and advertise the Nordic UART Service
    until a connection is made or the timeout is reached.

    Arguments:
        timeout: How long to wait for a connection before giving up, or
            ``None`` to wait indefinitely.

    Raises:
        TimeoutException: If the connection could not be made within the
            given timeout.

    Returns:
        stream object representing the connection to the central.
    """
    pass
# Peripheral 

from pybricks.ble import nus_wait_for_connection

prime_hub_stream = nus_wait_for_connection()

prime_hub_stream.write("Hello!")
# Central

from pybricks.ble import nus_connect

city_stream = nus_connect("my_city_hub")
technic_stream = nus_connect("AA:BB:CC")
class LWP3Device(BLEDevice):
    pass

def lwp_connect("....") -> LWP3Device
    pass

class pybricks.pupdevices.RemoteControl(LWP3Device):
    pass

my_duplo_hub = lwp_connect("Duplo Hub")

So I'm thinking we probably want to default to the slower, safer option. But we might want to add an option for the faster lossy option if there are use cases that require high bandwitdh.

Could we add this as a parameter above? Or is this a hub-wide setting that will affect all subsequently made connections?

@NStrijbosch
Copy link

Is this the lowest level possible? I am thinking beyond Nordic UART, e.g., LWP ;)

My main question: what are your thoughts on support for LWP, i.e., either by allowing specific characteristics in this low-level BLE to communicate with LWP devices, or by extending scenario 6 beyond the PoweredUp Remote?

I have been diving into the LWP docs lately (that would be extending scenario 6). For myself I see a few advantages (and of course also disadvantages) in using this protocol to communicate between a SPIKE Prime/Robot Inventor hub to any other PU hub supporting LWP. The advantages I enjoy at the moment:

  • the program runs from a single hub, this makes coding/debuging way more efficient compared to updating the program on each of the hubs.
  • No third party firmware necessary on the peripheral hubs.

Of course the disadvantages are numerous:

  • You rely on the computation power of a single hub
  • The update frequency of everything on the peripheral hubs is limited by BLE
  • ...

@laurensvalk
Copy link
Member Author

Thanks for your quick response.

Is this the lowest level possible?

No, this is mainly the low level for the NUS communication stuff, hence the nus_ prefixes. And despite being "low-level", this is really quite usesable in end-user scripts already, since you get a stream object that is fairly easy to use. Higher level pybricks.messaging is optional and maybe we don't even need it.

My main question: what are your thoughts on support for LWP, i.e., either by allowing specific characteristics in this low-level BLE to communicate with LWP devices, or by extending scenario 6 beyond the PoweredUp Remote?

In other words, this leaves room for other functions in pybricks.ble that start with lwp_ 😉

@dlech dlech added the topic: communication Issues related to hub-to-hub/phone/computer communcation label May 20, 2021
@laurensvalk
Copy link
Member Author

Thanks everyone! Please move the gamepad findings and discussion over to #1024. Thanks!

@mhornoloopers
Copy link

I am interested in the possibility of using a USB connection to run Python code on the Spike Prime Hub. I understand that this may not be a readily available feature, but I would like to explore any potential solutions or workarounds.

If there is no existing method to achieve this, could you please provide some guidance or suggestions on how I might approach implementing a USB connection for this purpose? Any information or resources you can share would be greatly appreciated.

@laurensvalk
Copy link
Member Author

Do you have a particular reason why Bluetooth can't be used?

@mhornoloopers
Copy link

mhornoloopers commented Apr 5, 2023

Do you have a particular reason why Bluetooth can't be used?

My client's users are already used to using USB to connect their lego robots and having them change to Bluetooth is not an option unfortunately.

@laurensvalk
Copy link
Member Author

Thanks for the response. I'm still not sure that I follow - no user action is required to use Bluetooth in pybricks (no need to press the Bluetooth button). You can still have USB plugged in for charging.

In other words, whether it not the cable is plugged in, the user work flow is the same.

Can you elaborate on why this won't work? Just trying to get a better picture to see if this would help other users, too.

@mhornoloopers
Copy link

mhornoloopers commented Apr 5, 2023

The type of user are kids so it's more difficult for them to stablish a bluetooth connection rather than just plug an usb (and the teachers are also used to the usb connection).

no user action is required to use Bluetooth in pybricks (no need to press the Bluetooth button)

I'm highly curious about this. How would this work? Should there not be some prior configuration for the connection to be established between the device and the computer?

Even so there's also another two reasons and those are that:

  • In a classroom where several Spike Prime Hubs are used at the same time, so they tend to connect to the wrong hubs.
  • Some computers don't have Bluetooth (desktop computers)

@westurner
Copy link

westurner commented Apr 5, 2023 via email

@laurensvalk
Copy link
Member Author

I'm highly curious about this. How would this work? Should there not be some prior configuration for the connection to be established between the device and the computer?

You click the connect button in the app. Give it a try if you like. Since this is a web app, the procedure would be the same for USB. The browser won't let us auto-connect for security reasons.

You could also use the official LEGO apps with Python if you prefer.

@mhornoloopers
Copy link

I'm highly curious about this. How would this work? Should there not be some prior configuration for the connection to be established between the device and the computer?

You click the connect button in the app. Give it a try if you like. Since this is a web app, the procedure would be the same for USB. The browser won't let us auto-connect for security reasons.

You could also use the official LEGO apps with Python if you prefer.

Thank you for the information on Bluetooth connectivity. However, due to our users' specific requirements I mentioned earlier, we are seeking a USB-based solution and are curious if there's a way to implement it with Pybricks.

Is it not possible to achieve this, then? If you have any suggestions or insights, I'd appreciate your input. I understand that this might be a complex task, but any guidance would be valuable.

@laurensvalk
Copy link
Member Author

It's certainly possible technically. We get a lot of feature requests, so we normally try to prioritize features that help the most users. We do also work with commercial partners on some new features, and USB support could be a good example of that --- feel free to reach out at [email protected] if you'd like to learn more.

@lawrencet149
Copy link

Another reason for USB support is so we can use pybricks for First Lego League Challenge competitions. Our FLL organization here does not allow bluetooth during the competitions and it is crucial to be able to tweak the code and make adjustments based on previous runs on the tables. I have been teaching my group of kids how to use pybricks and it would be awesome if we can stick with pybricks for the robot competitions this coming season. Thank you for pybricks!

@KumphanartDansiri
Copy link

KumphanartDansiri commented Jul 2, 2023

Another reason for USB support is so we can use pybricks for First Lego League Challenge competitions. Our FLL organization here does not allow bluetooth during the competitions and it is crucial to be able to tweak the code and make adjustments based on previous runs on the tables. I have been teaching my group of kids how to use pybricks and it would be awesome if we can stick with pybricks for the robot competitions this coming season. Thank you for pybricks!

Me too. I just get spike prime for my kids to upgrade from ev3 to enter WRO Competition. During competition wireless connection is not allowed. So, either run pybricks in ev3 or move to lego officials for spike prime.

@westurner
Copy link

Let's see if I can summarize weeks later from a deep dive on a weekend iirc:

Is this a usable UX workflow for connecting a bluetooth ble gamepad to a LEGO hub?:

@nkarstens
Copy link

@laurensvalk @dlech Do you have any ideas or requirements for USB support in case we wanted to try to implement and submit a pull request? I'm running an FLL team and would like to avoid Bluetooth at competitions.

@lawrencet149
Copy link

@laurensvalk @dlech Do you have any ideas or requirements for USB support in case we wanted to try to implement and submit a pull request? I'm running an FLL team and would like to avoid Bluetooth at competitions.

@nkarstens I am willing to help with this effort if you need the help.

@dlech
Copy link
Member

dlech commented Sep 6, 2023

Ideas:

  • It should work on Windows with no special drivers. See this page for what Windows expects for this.
  • It should use a vendor-specific device class (0xFF).
  • It should wrap the Pybricks Profile that we use for BLE. This includes emulating the GATT protocol to some extent.

@nkarstens
Copy link

All right I've looked into this a little bit and I'm hoping I'm not too far out of my depth here...

@dlech It looks like there was support for a USB serial connection at one point (usb_stm32_serial.c), but that was removed. What can you tell me about the history there? It looks like you suggested WinUSB; would a serial protocol be a better option for wrapping the GATT profile? Do you have any documentation on options for debugging firmware (such as JTAG or a printf)?

@lawrencet149 Thanks for the offer, do you know much about firmware development?

@dlech
Copy link
Member

dlech commented Sep 13, 2023

Since BLE is message-based instead of stream-based, I don't think serial is a good fit. Also we don't want the official LEGO apps to "see" devices that are running Pybricks firmware.

The initial USB serial implementation was just to debug before we got Bluetooth working.

Debugging is a bit tricky since there is no JTAG (lock bit was set at the factory, so it doesn't work) and printf has to be used with much caution. If we really need to, we can hijack one of the I/O ports for printing via UART. This is discussed in a bit more detail at pybricks/pybricks-micropython#80 (comment).

@nkarstens
Copy link

@dlech I got a NUCLEO-F413ZH board to help with prototyping the initial USB work and will then plan to migrate to pybricks-micropython from there.

It looks like we will need a USB vendor ID and product ID. pid.codes does subassignments of their vendor ID, 1209, for open source projects that meet their criteria. Can you please review the prerequisites listed here and let me know if this sounds good to you? If so, I will take care of making a pull request and send to you for approval, or you can do that yourself and let me know when it has been merged in.

@dlech
Copy link
Member

dlech commented Sep 28, 2023

We should use the LEGO USB VID/PIDs that are already assigned to these hubs. https://github.com/pybricks/technical-info/blob/master/assigned-numbers.md#usb-device-ids

@nkarstens
Copy link

nkarstens commented Sep 28, 2023 via email

@dlech
Copy link
Member

dlech commented Sep 28, 2023

They can be, but for the SPIKE and MINDSTORMS hubs, there is no special driver. It is using the class/subclass/protocol to match to a driver instead. The official LEGO firmware is using the standard serial port (CDC ACM) class/subclass/protocol. If we are using a vendor-specific class/subclass/protocol, then there shouldn't be any conflict.

@nkarstens
Copy link

nkarstens commented Oct 25, 2023

@dlech I got it to a point where Windows will load a WinUSB driver for it. Please see the draft patch at pybricks/pybricks-micropython#208 and let me know if you have any suggestions. Thanks!

@dlech
Copy link
Member

dlech commented Oct 25, 2023

Nice! Looking forward to see what you came up with.

@nkarstens
Copy link

Just FYI for anyone monitoring this issue. The following pull requests have enough USB support implemented to push a program:

@dlech has incorporated these into a dlech-usb branch in each project and is making additional improvements.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request topic: bluetooth Issues involving bluetooth topic: communication Issues related to hub-to-hub/phone/computer communcation topic: remote control Issues related to remotly controlling hubs
Projects
None yet
Development

No branches or pull requests