Raspberry Pi: Control Bluetooth LE Devices

Raspberry Pi: Control Bluetooth LE Devices

by ACROBOTIC Industries

Overview

Time to complete: 10–15min; Level of difficulty: Beginner

In this tutorial we’ll install and use the Bluetooth Linux stack Bluez to read and write data voer Bluetooth LE to and from a nearby device. We’ll run the Node.js program Bleno to simulate a peripheral device that will receive and transmit data from and to the Pi!

List of Materials

  • 1 x Raspberry Pi 3 Model B
  • 1 x 5V USB Power Supply
  • 1 x 8GB MicroSD Card
  • 1 x USB Bluetooth 4.0 Adapter
  • 1 x MicroSD USB Adapter (Optional)

Overview of Bluetooth Low Energy

Bluetooth Low Energy (aka BLE/Bluetooth 4.0/Bluetooth Smart) is the most recent incarnation of Bluetooth technology developed by the Bluetooth SIG (the organization that maintains the specification). This communication protocol is designed for applications where data needs to be transferred in small amounts at relatively low speed while consuming low amounts of power (e.g., heart rate monitor, step counter, wireless keyboard). This latest version of the protocol is not compatible with its predecessor (Bluetooth classic), as an upside, long gone are the days where pairing devices was necessary!

The goal of this tutorial is to demonstrate how you can setup your Raspberry Pi to read and write data from Bluetooth Low Energy (BLE) devices nearby. Whether you want to read the number of steps from your fitbit, or your heart rate from your iWatch, or read/write any type of data from/to BLE devices, this guide should help you along the way.

Currently, Bluetooth Low Energy (BLE) is not well supported by the standard Raspberry Pi distributions, thus some additional work is required to get it working. We describe in detail the steps you'll need to get your Raspberry Pi ready to start using Bluetooth LE.


Getting the Operating System (Raspbian) on the MicroSD Card

To complete the rest of this tutorial you'll need to have your Raspberry Pi up and running. This process involves roughly 3 steps:

1. Using your computer to flash Raspbian onto the MicroSD Card

2. Inserting the card into the Raspberry Pi and powering the system

3. Running your first programs on the Raspberry Pi

In our "Getting Started with Raspberry Pi" tutorial, we show you how to go from unboxing your Raspberry Pi to running your first applications on it; be sure to check it out.

In addition, we've made a detailed video of the process:


Configuring Raspbian to use Bluetooth LE

By default, the Raspbian distribution comes without a Bluetooth stack. The Bluez package is quite old and has patchy support for Low Energy. You can build and install a more modern version as described below.  After the system is up and running open up the Terminal program and a browser window, then start following the commands.

First, do not, I repeat, do NOT use the version available through aptitude. It is a very old version and doesn't work very well.

# Do NOT do this → sudo apt-get install bluez

In fact, just in case you have it already installed let's try and remove it; this is okay to do even if it's not installed in your system:

sudo apt-get --purge remove bluez

Next, we have to determine what's the latest version available. To do this, navigate to the official website https://www.kernel.org/pub/linux/bluetooth/ and look for the package bluez-X.XX.tar.xz where X.XX is the version. At the time of this writing the latest version is 5.34!

Bluez Package Versions

You can then go back to the Terminal on the Raspberry Pi and remembering to change X.XX for the latest version you find (in our case X.XX would be 5.34) enter the command:

cd ~; wget https://www.kernel.org/pub/linux/bluetooth/bluez-X.XX.tar.xz

Subsequently, uncompress the package by:

tar xvf bluez-X.XX.tar.xz

At this point you need to make sure all the necessary libraries for running the bluetooth stack:

sudo apt-get install libusb-dev libdbus-1-dev libglib2.0-dev libudev-dev libical-dev libreadline-dev

After doing so, the system is ready to compile the Bluez package:

cd bluez-X.XX
export LDFLAGS=-lrt 
./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --enable-library --disable-systemd
make
sudo make install

For some reason the standard installation process misses installing one of the files to the correct directory.  To solve this:

sudo cp attrib/gatttool /usr/bin/

And that's it!  We're now ready to test out our Bluetooth LE USB Adapter on our Raspberry Pi!


Connecting the Bluetooth LE USB Adapter

Connecting the BLE USB adapter is very straight-forward as the Raspbian Operating System is capable of detecting when the device is hotplugged (i.e., plugged in when the system is running). This is great as we need not reboot in order to use it. 

Connecting the BLE USB Adapter to Raspberry Pi

We can simply plug it into an available USB port, and then ensure that the system has recognized it by going back to the Terminal and entering:

lsusb

The output should show the device as one of the entries.


Testing the Bluetooth LE USB Adapter

Now that the Bluetooth LE USB Adapter is recognized by the system and we've successfully installed the Bluetooth stack, we're ready to test that we can access it. From the terminal we can first bring up the interface by:

sudo hciconfig hci0 up

And see if the device is up and running:

hciconfig

This command should return the status of the BLE interface, in our case hci0.

Configuring the BLE interface with hciconfig

We can then move to the next step where scan for nearby BLE devices!


Scanning for Nearby BLE Devices

After installing the Bluetooth stack and successfully added the Bluetooth LE USB adapter to our Raspberry Pi we're ready to scan for nearby BLE devices.

In our case, we've downloaded a Node.js app to a MacBook called Bleno. This very useful app allows us to set our laptop as a Peripheral Device. We've configured the app so that our peripheral device advertises the id "I <3 Instructables" and echoes any message we send to it (for more details look at the examples in the Bleno repository).

Then, going back to the terminal in our Raspberry Pi, we run:

sudo hcitool lescan

Often times this command runs indefinitely, thus we can hit CTRL+C in order to stop it. From the output, we can pick up the MAC addresses of nearby BLE devices.

Scanning for nearby BLE device with hcitool

We can see that before we hit CTRL+C, the device is present in the output of the scan with a MAC address of 28:37:37:1A:D3:CF. We'll need this address in the next step, so make sure you're able to complete this before moving forward.


Connecting to a BLE Device

One of our favorite features of Bluetooth 4.x is that, different than in previous versions, we need not go through the obnoxious pairing process to connect to a device. On our Raspberry Pi, we'll be using the program gatttool to connect to our nearby BLE device, and write/read data to/from it.

In the previous step we were able to get the MAC address 28:37:37:1A:D3:CF of the Peripheral Device that we're running through the Bleno app. To connect to this device we're going to use another tool part of the BLE stack we installed called gatttool. We're able to run this tool in an interactive mode so that we step through the process of issuing commands over BLE to our Peripheral Devices!

sudo gatttool -b 28:37:37:1A:D3:CF -I

Note that your MAC address will likely be different smile Also, you might need to add "-t random" to the list of arguments if you have trouble with the next steps. If you get device busy error simply remove the dongle and reconnect it.

Connecting to a nearby BLE peripheral with gatttool

Once you see the interactive prompt you can simply type connect and wait for the connection to be established. After getting the "Connection successful" message on the screen, we're now ready to start writing and reading data to and from the BLE Peripheral Device!


Writing Data To a BLE Device

Now that we're connected to our BLE Peripheral Device, which in this case is a laptop running the Bleno Node.js app, we can use the commands primary and char-desc to list all the different services and characteristics available on the device.

In our case, our simple device only has 1 characteristic for writing data to it, and retrieving the last data that it received. The handle for this characteristic is 0x0026, thus within the interactive command line interface for gatttool we can:

[28:37:37:1A:D3:CF][LE]> char-write-req 0x0026 1234

This command sends the value 1234 to the handle 0x0026.

Writing data to a BLE peripheral device with gatttool

We can verify that the command is received by looking at the debug messages of our Bleno app. In our case, the app prints out the value 1234 after receiving it!

Bleno Node.js app used to receive BLE data from a nearby device

In our final step, we'll proceed in a similar fashion in order to read back the value we've just written to the device.


Reading Data From a BLE Device

Now that we've written some data to our BLE Peripheral Device it's time to read it!  For this we make use of the same handle 0x0026, and again within the interactive command line interface for gatttool we can:

[28:37:37:1A:D3:CF][LE]> char-read-hnd 0x0026

This command reads the value we last sent (which in this case should be "1234") to the handle 0x0026.

Reading data from a BLE peripheral device with gatttool

We can verify that the request is received by looking at the debug messages of our Bleno app. In our case, the app prints out EchoCharacteristic - onReadRequest: value = 1234 after receiving it!

Bleno Node.js app used to send BLE data to a nearby device

In our next tutorial we'll see how to read and write data to BLE devices programmatically using Python!

Leave a comment

  • Please note, comments must be approved before they are published

$1 Days
$2 Hours
$3 Minutes
$4 Second
{ "en":{ "general": { "field": { "required": "Required", "actions": "Actions", "top_btn": "Top" }, "accessibility": { "skip_to_content": "Skip to content", "close_modal": "Close (esc)" }, "meta": { "tags": "Tagged \"[[ tags ]]\"", "page": "Page [[ page ]]" }, "404": { "title": "404 Page Not Found", "subtext": "The page you requested does not exist.", "link": "Continue shopping" }, "pagination": { "previous": "Previous", "next": "Next", "current_page": "Page [[ current ]] of [[ total ]]" }, "password_page": { "opening_soon": "Opening Soon", "login_form_heading": "Enter store using password", "login_form_password_label": "Password", "login_form_password_placeholder": "Your password", "login_form_submit": "Enter", "signup_form_email_label": "Email", "signup_form_success": "We will send you an email right before we open!", "admin_link_html": "Are you the store owner? Log in here<\/a>", "password_link": "Enter using password", "powered_by_shopify_html": "This shop will be powered by [[ shopify ]]" }, "social": { "share_on_facebook": "Share", "share_on_twitter": "Tweet", "share_on_pinterest": "Pin it", "alt_text": { "share_on_facebook": "Share on Facebook", "share_on_twitter": "Tweet on Twitter", "share_on_pinterest": "Pin on Pinterest" } }, "search": { "no_results_html": "Your search for \"[[ terms ]]\" did not yield any results.", "results_with_count": { "one": "[[ count ]] result for \"[[ terms ]]\"", "other": "[[ count ]] results for \"[[ terms ]]\"" }, "title": "Search our site", "placeholder": "Search", "submit": "Submit", "close": "Close search" }, "newsletter_form": { "newsletter_email": "Join our mailing list", "email_placeholder": "Email address", "confirmation": "Thanks for subscribing", "submit": "Subscribe", "show_me_text": "Do not show me again" }, "filters": { "show_more": "Show More", "show_less": "Show Less" }, "breadcrumbs": { "home": "Home", "create_account": "Create account", "account": "Account", "addresses": "Addresses" }, "item": { "remove": "Remove Item" } }, "sections": { "header": { "top_header_login": "Login", "top_header_register": "Register", "top_header_wishlist": "Wish list", "register_dropdown": "No account? Create one here", "forgot": "Forgot password", "all_collection": "All Collections", "world_wide_delivery": "Worldwide delivery", "shipping_text": "Free UK Delivery on orders over £ 100", "hot_line": "Hot line" }, "menu": { "mobile_menu_tab": "Menu", "mobile_account_tab": "Account", "mobile_settings_tab": "Settings" }, "slideshow": { "next_slide": "Next slide", "previous_slide": "Previous slide", "pause_slideshow": "Pause slideshow", "play_slideshow": "Play slideshow", "play_video": "Play video", "close_video": "Close video" }, "map": { "get_directions": "Get directions", "address_error": "Error looking up that address", "address_no_results": "No results for that address", "address_query_limit_html": "You have exceeded the Google API usage limit. Consider upgrading to a Premium Plan<\/a>.", "auth_error_html": "There was a problem authenticating your Google Maps account. Create and enable the JavaScript API<\/a> and Geocoding API<\/a> permissions of your app." } }, "blogs": { "article": { "view_all": "View all", "all_topics": "All topics", "by_author": "by [[ author ]]", "posted_in": "Posted in", "read_more": "Read more", "back_to_blog": "Back to [[ title ]]" }, "comments": { "title": "Leave a comment", "name": "Name", "email": "Email", "message": "Message", "post": "Post comment", "moderated": "Please note, comments must be approved before they are published", "success_moderated": "Your comment was posted successfully. We will publish it in a little while, as our blog is moderated.", "success": "Your comment was posted successfully! Thank you!", "comments_with_count": { "one": "[[ count ]] comment", "other": "[[ count ]] comments" } } }, "cart": { "general": { "title": "Your cart", "note": "Add a note to your order", "remove": "Remove", "subtotal": "Subtotal", "savings": "You're saving", "shipping_at_checkout": "Shipping & taxes calculated at checkout", "update": "Update", "checkout": "Process Check out", "empty": "Your cart is currently empty.", "cookies_required": "Enable cookies to use the shopping cart", "edit": "Edit", "cancel": "Cancel", "continue_shopping": "Continue shopping", "recently_added_item": "Recently added item(s)", "remove_item": "Remove This Item", "view_and_edit_cart": "View and edit cart", "clear": "Clear cart", "empty_page_title": "Shopping Cart is Empty", "here": "here", "empty_continue_html": "Click here to continue shopping.", "processing": "Processing...", "items_count_label" : "[[ count ]] item(s) in your cart", "ok" : "Ok" }, "label": { "product": "Product", "price": "Price", "quantity": "Quantity", "total": "Total", "total_item": "Total item", "sub_total_top": "Cart Subtotal" } }, "collections": { "general": { "view_all": "View all", "clear_all": "Clear All", "no_matches": "Sorry, there are no products in this collection", "items_with_count": { "one": "[[ count ]] product", "other": "[[ count ]] products" }, "load_more": "Load More", "sidebar_btn": "Filter by" }, "sorting": { "title": "Sort by", "manual": "Featured", "best_selling": "Best Selling", "title_ascending": "Alphabetically, A-Z", "title_descending": "Alphabetically, Z-A", "price_ascending": "Price, low to high", "price_descending": "Price, high to low", "created_descending": "Date, new to old", "created_ascending": "Date, old to new" }, "filters": { "title_tags": "Filter", "all_tags": "All products", "categories": "categories", "title": "Filter", "color": "Color", "size": "Size", "brand": "Brand", "price": "Price", "green": "Green", "blue": "Blue", "red": "Red", "pink": "Pink", "black": "Black", "purple": "Purple", "white": "White", "orange": "Orange" }, "product_item": { "quick_shop": "Quick View", "compare": "Compare", "wishlist": "Add to Wishlist" } }, "contact": { "form": { "name": "Name", "email": "Email", "phone": "Phone Number", "message": "Message", "submit": "Submit", "post_success": "Thanks for contacting us. We'll get back to you as soon as possible.", "address": "Address", "telephone": "Telephone", "title": "Write us", "required": "Required" } }, "customer": { "account": { "title": "My Account", "details": "Account Details", "view_addresses": "View Addresses", "return": "Return to Account Details" }, "activate_account": { "title": "Activate Account", "subtext": "Create your password to activate your account.", "password": "Password", "password_confirm": "Confirm Password", "submit": "Activate Account", "cancel": "Decline Invitation" }, "addresses": { "title": "Your Addresses", "default": "Default", "add_new": "Add a New Address", "edit_address": "Edit address", "first_name": "First Name", "last_name": "Last Name", "company": "Company", "address1": "Address1", "address2": "Address2", "city": "City", "country": "Country", "province": "Province", "zip": "Postal\/Zip Code", "phone": "Phone", "set_default": "Set as default address", "add": "Add Address", "update": "Update Address", "cancel": "Cancel", "edit": "Edit", "delete": "Delete", "delete_confirm": "Are you sure you wish to delete this address?" }, "login": { "title": "Login", "desc": "If you have an account, sign in with your email address.", "email": "Email", "password": "Password", "forgot_password": "Forgot your password?", "sign_in": "Sign In", "guest_title": "Continue as a guest", "guest_continue": "Continue" }, "orders": { "title": "Order History", "order_number": "Order", "date": "Date", "payment_status": "Payment Status", "fulfillment_status": "Fulfillment Status", "total": "Total", "none": "You haven't placed any orders yet." }, "order": { "title": "Order [[ name ]]", "date": "Placed on [[ date ]]", "cancelled": "Order Cancelled on [[ date ]]", "cancelled_reason": "Reason: [[ reason ]]", "billing_address": "Billing Address", "payment_status": "Payment Status", "shipping_address": "Shipping Address", "fulfillment_status": "Fulfillment Status", "discount": "Discount", "shipping": "Shipping", "tax": "Tax", "product": "Product", "sku": "SKU", "price": "Price", "quantity": "Quantity", "total": "Total", "fulfilled_at": "Fulfilled [[ date ]]", "subtotal": "Subtotal" }, "recover_password": { "title": "Reset your password", "email": "Email", "submit": "Submit", "cancel": "Cancel", "subtext": "We will send you an email to reset your password.", "success": "We've sent you an email with a link to update your password." }, "reset_password": { "title": "Reset account password", "subtext": "Enter a new password for [[ email ]]", "password": "Password", "password_confirm": "Confirm Password", "submit": "Reset Password" }, "register": { "title": "Create Account", "first_name": "First Name", "last_name": "Last Name", "email": "Email", "password": "Password", "submit": "Create", "desc": "Creating an account is easy. Just fill in the form below." } }, "homepage": { "onboarding": { "product_title": "Your product's name", "product_description": "This area is used to describe your product’s details. Tell customers about the look, feel, and style of your product. Add details on color, materials used, sizing, and where it was made.", "collection_title": "Your collection's name", "blog_title": "Your post's title", "blog_excerpt": "Your store hasn’t published any blog posts yet. A blog can be used to talk about new product launches, tips, or other news you want to share with your customers. You can check out Shopify’s ecommerce blog for inspiration and advice for your own store and blog.", "blog_author": "Author name", "no_content": "This section doesn’t currently include any content. Add content to this section using the sidebar." } }, "layout": { "navigation": { "search": "Search", "toggle": "expand\/collapse", "expand": "expand", "collapse": "collapse", "all_categories": "All Categories" }, "cart": { "title": "Cart", "items_count": { "one": "item", "other": "items" } }, "customer": { "account": "Account", "log_out": "Log out", "logout": "Log out", "log_in": "Log in", "create_account": "Create account", "sign_up": "Sign up", "wishlist": "Wishlist" }, "footer": { "social_platform": "[[ name ]] on [[ platform ]]" }, "list_page": { "grid": "Grid", "list": "List" } }, "products": { "product": { "regular_price": "Regular price", "sold_out": "Sold out", "unavailable": "Unavailable", "on_sale": "Sale", "quantity": "Quantity", "add_to_cart": "Add to cart", "back_to_collection": "Back to [[ title ]]", "related_title": "Related Products", "qty_increase": "Increase", "qty_decrease": "Decrease", "deal_days": "Days", "deal_hours": "Hours", "deal_minutes": "Minutes", "deal_second": "Second", "select_option": "Select Option", "add_to_wishlist": "Add to Wishlist", "add_to_review": "Add to review", "compare_success_msg": "[[ product_title ]] has added to comparing box successful", "compare_exist_msg": "[[ product_title ]] is exist in comparing box", "compare_cart_msg": "[[ product_title ]] has added to shopping cart", "compare_remove_msg": "[[ product_title ]] has removed from comparing box", "compare_remove_msg": "[[ product_title ]] has removed from comparing box", "comparing_box": "Comparing box", "compare_no_items": "There is no items in comparing box", "wishlist_success_msg": "[[ product_title ]] has added to wishlist successful", "wishlist_exist_msg": "[[ product_title ]] is exist in wishlist", "wishlist_cart_msg": "[[ product_title ]] has added to shopping cart", "wishlist_box": "Wishlist", "wishlist_remove_msg": "[[ product_title ]] has removed from wishlist", "wislist_no_items": "There is no items in wishlist", "upsell_cart_msg": "\"[[ product_title ]]\" has added to shopping cart", "upsell_block_title": "Frequently bought with \"[[ product_title ]]\"", "upsell_cart_qty": "[[ count ]] item(s)", "upsell_product_page_title": "You may also like these products", "upsell_checkout_btn": "Checkout", "share": "Share product", "share_on_facebook": "Share on Facebook", "share_on_twitter": "Share on Twitter", "share_on_pinterest": "Share on Pinterest", "share_on_google": "Share on Google+", "share_on_linkedin": "Share on LinkedIn", "availability": "Availability", "in_stock": "In Stock", "out_of_stock": "Out of stock", "quick_overview": "Quick Overview", "details": "Details", "reviews": "Reviews", "first_review": "Be the first review", "tags": "Product Tags", "size_chart": "Size Chart", "options": "Options", "vendor": "Vendor", "features": "Features", "sale_left_text": "[[ sales ]] SOLD. HURRY! ONLY A FEW LEFT!", "checkout_text": "Secured and trusted checkout with" }, "upsell": { "recommend_text": "Someone purchased a", "minute_ago": "minutes ago" } }, "gift_cards": { "issued": { "title_html": "Here's your [[ value ]] gift card for [[ shop ]]!", "subtext": "Your gift card", "disabled": "Disabled", "expired": "Expired on [[ expiry ]]", "active": "Expires on [[ expiry ]]", "redeem_html": "Use this code at checkout to redeem your [[ value ]] gift card", "shop_link": "Start shopping", "print": "Print this gift card", "remaining_html": "[[ balance ]] left", "add_to_apple_wallet": "Add to Apple Wallet" } }, "date_formats": { "month_day_year": "%B %d, %Y" } } }