├── LICENSE ├── README.md ├── demo01.py ├── makecode-ble-01.png ├── makecode-ble-02.png ├── makecode-ultrasonic-ble.png ├── ultrasonic-ble-speak-2.py └── writemsg1.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jorge Pereira 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # microbit 2 | Using micro:bit with the LEGO MINDSTORMS EV3 3 | 4 | The [BBC micro:bit](https://en.wikipedia.org/wiki/Micro_Bit) is a small ARM-based embedded system designed for Education. 5 | It has a few sensors, LEDs, buttons and GPIO pins and can be used through USB or Bluetooh Low Energy. 6 | 7 | This is a quick demonstration of how we can use a LEGO MINDSTORMS EV3 to communicate with a micro:bit through BLE. Will just how to present something in the the LED Matrix and read the two buttons but is very easy to use all the onboard devices. 8 | 9 | Requirements: 10 | - a LEGO MINDSTORMS EV3 11 | - a [micro:bit](https://microbit.org/) 12 | - a microSD card with [ev3dev linux](https://www.ev3dev.org/) 13 | - a USB BT 4.x dongle with BLE 14 | 15 | We want to use BLE so we need to flash a program into the micro:bit that exposes the devices we need as gatt charecteristics. A couple of years ago this was somewhat confusing but as of today it is quite easy by using the the online [Makecode editor](https://makecode.microbit.org/#editor): 16 | 17 | We just need to add the Bluetooh extension to have the required Bluetooth blocks and create a very simple program to activate the required BLE services when the micro:bit starts. For that we just need to add two blocks wo our initial "on start" block: 18 | 19 | + bluetooth led service 20 | + bluetooth button service 21 | 22 | This is our "program": 23 | ![Program](https://github.com/JorgePe/microbit/blob/master/makecode-ble-01.png) 24 | 25 | We also need to configure the micro:bit to accept BLE connections without pairing. This was very confusing a few years ago, but now it's just an option in the "Project Settings': 26 | 27 | + No Pairing Required: Anyone can connect via Bluetooth 28 | 29 | ![Project settings](https://github.com/JorgePe/microbit/blob/master/makecode-ble-02.png) 30 | 31 | 32 | Just save the project and upload the '.hex' file to the micro:bit, it will start almost immediately to announce itself as a BLE device named 'BBC micro:bit [zuvat]'. 33 | 34 | If you have Nordic nRF Connect App you can already control the LED Matrix or read the buttons' state with it. 35 | 36 | [Lancaster University](https://lancaster-university.github.io/microbit-docs/resources/bluetooth/bluetooth_profile.html) has documented all the BLE characteristics so we know that we need 3 UUID's: 37 | 38 | + Button A ('e95dda90-251d-470a-a062-fa1922dfa9a8') 39 | + Button B ('e95dda91-251d-470a-a062-fa1922dfa9a8') 40 | + LED Matrix ('e95d7b77-251d-470a-a062-fa1922dfa9a8') 41 | 42 | Now for the EV3 to make use of these BLE services it need to run ev3dev linux. I will assume that you already know how to install and configure ev3dev and then connect to your EV3 through ssh and activate Bluetooth so I just explain what to do after: 43 | 44 | We need a python library that supports BLE. Currently ev3dev includes pybluez ang gattlib buth it doesn't work with the micro:bit so I added [pygatt]()https://github.com/peplin/pygatt. 45 | 46 | ``` 47 | sudo apt-get install python3-pip 48 | sudo pip3 install pexpect 49 | sudo pip3 install pygatt 50 | ``` 51 | 52 | it will take a while so be sure to have fresh batteries or a wall adapter charging a EV3 battery. 53 | 54 | Now just transfer the [demo01.py](https://github.com/JorgePe/microbit/blob/master/demo01.py) python script to your EV3, connect a Medium Motor to port A and you should control the motor with short presses of Button A or B (longer presses will not work because they return different codes). 55 | 56 | Just two notes about the script: 57 | 58 | + The micro:bit requires a connection of type random (it is the only BLE device I own where I have to set this option, had to dig into the pygatt source code to find it) 59 | ``` 60 | device = adapter.connect('F5:91:E3:32:23:39',address_type=pygatt.BLEAddressType.random) 61 | ``` 62 | + I had to use the LED Matrix handle instead of the UUID because pygatt complains that no characteristic is found that matches it. The handle is 0x27 but it can change with future versions of the firmware (you can check the actual handle with the Nordic nRF Connect App or with a tool like 'gatttool'). 63 | 64 | A [video](https://youtu.be/7sGR8Ce65QA) showing the above script running: 65 | [![EV3 and micro:bit](https://66.media.tumblr.com/97e82bcb856c7ac19df3f3683e98ae88/tumblr_ppaise45aV1ws4ayp_1280.jpg)](https://youtu.be/7sGR8Ce65QA "EV3 and micro:bit") 66 | 67 | The [writemsg1.py](https://github.com/JorgePe/microbit/blob/master/writemsg1.py) script shows how to use just the LED Matrix service to draw some squares and crosses (a very short video: https://youtu.be/AHGr_eLJYz0) 68 | 69 | As you probably noticed in the video, the responsiveness to the buttons is bad. Yes, EV3 hasn't the faster processor of h world but the micro:bit BLE services also don't seem much fast. So how can this be useful? 70 | 71 | I started using BLE gadgets as a way to get sensory data. With the EV3 that's a way to get extra sensors without additional connections, some times even sensors that aren't available in LEGO world. Most of the time, we don't need real time access to the data - 1 to 10 samples each second are usually enough. So how can we use the micro:bit for that? 72 | 73 | For onboard sensors, like the buttons or the temperature sensor, there are already BLE services that expose that data. But since the micro:bit also has several GPIO pins available, we can also use external sensors and expose the data through the micro:bit' BLE UART Service. 74 | 75 | I'll show how to use this to add a wireless ultrasonic distance sensor to the MINDSTORMS EV3: 76 | 77 | The HC-SR04 ultrasonic sensor is quite common among Arduino users and can also be ![used with the micro:bit](http://www.teachwithict.com/hcsr045v.html). The Makecode online editor already has a 'sonar' extension for it so the resulting program is quite simple - just read the value from the sensor once per second, display the result on the LED matrix and also send it over BLE to whoever might be listening. 78 | 79 | ![Program](https://github.com/JorgePe/microbit/blob/master/makecode-ultrasonic-ble.png) 80 | 81 | On the EV3 side we just need to connect to the micro:bit and then subscribe to the micro:bit BLE UART TX characteristic in indication mode so that everytime the micro:bit sends a new reading we will receive it. 82 | 83 | The [ultrasonic-ble-speak-2.py](https://github.com/JorgePe/microbit/blob/master/ultrasonic-ble-speak-2.py) script makes just that, printing the distance in the EV3 display but also speaking it loud: 84 | [![Micro:bit Ultrasonic Sensor and EV3](https://i9.ytimg.com/vi/gnZdKOMnr2E/mq2.jpg?sqp=CJy8weUF&rs=AOn4CLAfBCdQWLpNinD4KkZGjjiYK6Crxw)](https://youtu.be/gnZdKOMnr2E "Micro:bit Ultrasonic Sensor and EV3") 85 | 86 | Just a note about the subscribing step with pygatt: on my Ubuntu laptop, subscribing to the TX characteristic always work fine. But in the EV3 running ev3dev the same script failed: 87 | 88 | ```pygatt.exceptions.BLEError: No characteristic found matching 6e400002-b5a3-f393-e0a9-e50e24dcca9"``` 89 | 90 | I am using same pygatt library on both devices (4.0.3) and the linux kernels aren's much different by now so the BlueZ stack is probably the same so I was about to give up when I decided to try to repeat the command - so I included it inside a while loop and used the try/except commands (something new for me so excuse me it it looks ugly code). And it worked - usually the first try fails but the second works fine. Not sure if it is just because of the slower CPU... 91 | -------------------------------------------------------------------------------- /demo01.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import pygatt 4 | from time import sleep 5 | import ev3dev.ev3 as ev3 6 | 7 | adapter = pygatt.GATTToolBackend() 8 | m = ev3.MediumMotor('outA') 9 | 10 | DELAY = 0.1 11 | 12 | try: 13 | adapter.start() 14 | device = adapter.connect('F5:91:E3:32:23:39',address_type=pygatt.BLEAddressType.random) 15 | 16 | device.char_write_handle(0x27, bytearray([0x00, 0x00, 0x00, 0x00, 0x00])) 17 | sleep(DELAY) 18 | device.char_write_handle(0x27, bytearray([0x11, 0x0A, 0x04, 0x0A, 0x11])) 19 | sleep(DELAY) 20 | device.char_write_handle(0x27, bytearray([0x00, 0x0A, 0x04, 0x0A, 0x00])) 21 | sleep(DELAY) 22 | device.char_write_handle(0x27, bytearray([0x00, 0x00, 0x04, 0x00, 0x00])) 23 | sleep(DELAY) 24 | device.char_write_handle(0x27, bytearray([0x00, 0x00, 0x00, 0x00, 0x00])) 25 | sleep(DELAY) 26 | 27 | while True: 28 | buttonA = device.char_read("e95dda90-251d-470a-a062-fa1922dfa9a8") 29 | buttonB = device.char_read("e95dda91-251d-470a-a062-fa1922dfa9a8") 30 | 31 | if buttonA == bytearray(b'\x01'): 32 | device.char_write_handle(0x27, bytearray([0x04, 0x08, 0x10, 0x08, 0x04])) 33 | m.run_timed(time_sp=100, speed_sp=-500) 34 | elif buttonB == bytearray(b'\x01'): 35 | device.char_write_handle(0x27, bytearray([0x04, 0x02, 0x01, 0x02, 0x04])) 36 | m.run_timed(time_sp=100, speed_sp=500) 37 | else: 38 | device.char_write_handle(0x27, bytearray([0x00, 0x00, 0x00, 0x00, 0x00])) 39 | 40 | sleep(0.1) 41 | 42 | finally: 43 | adapter.stop() 44 | 45 | -------------------------------------------------------------------------------- /makecode-ble-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JorgePe/microbit/536daba7ff949cbf386658a4cc03c7316f29113b/makecode-ble-01.png -------------------------------------------------------------------------------- /makecode-ble-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JorgePe/microbit/536daba7ff949cbf386658a4cc03c7316f29113b/makecode-ble-02.png -------------------------------------------------------------------------------- /makecode-ultrasonic-ble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JorgePe/microbit/536daba7ff949cbf386658a4cc03c7316f29113b/makecode-ultrasonic-ble.png -------------------------------------------------------------------------------- /ultrasonic-ble-speak-2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from ev3dev2.sound import Sound 4 | import pygatt 5 | from time import sleep 6 | from binascii import hexlify 7 | 8 | DELAY = 1.0 9 | 10 | def handle_data(handle, value): 11 | """ 12 | handle -- integer, characteristic read handle the data was received on 13 | value -- bytearray, the data returned in the notification 14 | """ 15 | distance = int(value.decode("utf-8")) 16 | print("Dist = {0:.2f} m".format(distance/100) ) 17 | sound.speak(value.decode("utf-8")) 18 | 19 | adapter = pygatt.GATTToolBackend() 20 | adapter.start() 21 | sound = Sound() 22 | sound.speak('Welcome to the E V 3 dev project!') 23 | 24 | while True: 25 | try: 26 | print("Connecting...") 27 | device = adapter.connect('F5:91:E3:32:23:39', address_type=pygatt.BLEAddressType.random) 28 | print("Connected") 29 | break 30 | except pygatt.exceptions.NotConnectedError: 31 | print("Not connected") 32 | sleep(1) 33 | continue 34 | 35 | while True: 36 | try: 37 | device.subscribe("6e400002-b5a3-f393-e0a9-e50e24dcca9e", callback=handle_data, indication=True) 38 | break 39 | except pygatt.exceptions.BLEError: 40 | print("unknown characteristic") 41 | 42 | try: 43 | while True: 44 | sleep(DELAY) 45 | except Exception as e: 46 | print(e) 47 | except (KeyboardInterrupt, SystemExit): 48 | print("Kbd/SysX") 49 | adapter.stop() 50 | finally: 51 | adapter.stop() 52 | 53 | -------------------------------------------------------------------------------- /writemsg1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import pygatt 4 | from time import sleep 5 | 6 | DELAY=0.1 7 | 8 | adapter = pygatt.GATTToolBackend() 9 | 10 | try: 11 | adapter.start() 12 | device = adapter.connect('F5:91:E3:32:23:39',address_type=pygatt.BLEAddressType.random) 13 | while True: 14 | device.char_write_handle(0x27, bytearray([0x1F, 0x11, 0x11, 0x11, 0x1F])) 15 | sleep(DELAY) 16 | device.char_write_handle(0x27, bytearray([0x00, 0x0E, 0x0A, 0x0E, 0x00])) 17 | sleep(DELAY) 18 | device.char_write_handle(0x27, bytearray([0x00, 0x00, 0x04, 0x00, 0x00])) 19 | sleep(DELAY) 20 | device.char_write_handle(0x27, bytearray([0x00, 0x00, 0x00, 0x00, 0x00])) 21 | sleep(DELAY) 22 | device.char_write_handle(0x27, bytearray([0x11, 0x0A, 0x04, 0x0A, 0x11])) 23 | sleep(DELAY) 24 | device.char_write_handle(0x27, bytearray([0x00, 0x0A, 0x04, 0x0A, 0x00])) 25 | sleep(DELAY) 26 | device.char_write_handle(0x27, bytearray([0x00, 0x00, 0x04, 0x00, 0x00])) 27 | sleep(DELAY) 28 | device.char_write_handle(0x27, bytearray([0x00, 0x00, 0x00, 0x00, 0x00])) 29 | sleep(DELAY) 30 | finally: 31 | adapter.stop() 32 | 33 | --------------------------------------------------------------------------------