Update BlueIO firmware using RaspberryPi

The BleuIO comes with a bootloader to allow you to update the firmware or flash your own application to the dongle. To flash the dongle, you will need an image file containing the new firmware or your own application and a host USB loader application.

You can download the latest firmware, and host USB loader from our getting started guide.

This article will explain how to update a BleuIO dongle using a RaspberryPi.

Connect the BleuIO dongle to your Linux / RaspberryPi.

Dongle

To identify which device name the dongle is connected to, you will need to run ls /dev on the terminal.
You might need to do it twice, once before you connect the dongle and once after, to be able to identify which one is the device name.

The following screenshot shows devices list before and after connecting the dongle.

The BleuIO includes a bootloader that allows you to update the firmware easily. When starting up, the dongle will open up a COM port for the bootloader for 10 seconds to allow you to update the firmware (or flash your own application). Afterwards, it will close that port and open a new port for the BleuIO application. 

Now that you know the device name, you can run the following command to update the dongle. 

sudo ./host_usb_updater /dev/ttyACM0 ./example_firmware.img -verbose 

The /dev/ttyACM0 means USB-CDC driver of Linux.

NOTE: The device name may vary and may not be the same as in the picture above.

After running this command successfully, you will see Result: Pass on your terminal.  

Share this post on :

Location analysis using Bluetooth Low Energy

Distance to a Bluetooth device can be measured using RSSI value. The following script will help you locate nearby unique BLE devices within a selected range.

This script scans for nearby unique Bluetooth devices within the given range using a well-known RSSI to distance formula. 

Requirments

Instructions

git clone https://github.com/smart-sensor-devices-ab/device_within_range.git

Go inside the folder type in terminal

parcel index.html

The script will scan for five seconds for nearby devices within range. You can update the value based on your requirements.

You can also run this script from online at

https://smart-sensor-devices-ab.github.io/device_within_range/dist/index.html

**Make sure your BleuIO dongle is connected

Read more about how to calculate the distanceHow to Calculate Distance from the RSSI value of the BLE Beacon

Share this post on :

Get Bluetooth Device Distance in meter Using Javascript

Bluetooth ranging technology is very popular. There are many localization systems that exist based on beacons. Beacon technology usually estimates the distance between devices using the received signal strength (RSSI). 

Bluetooth can be an excellent way to narrow down a search area at close distances when tracking something. This feature can be used in several fields. such as Secure Locks for Buildings and Automotive, Asset localization & tracking, Indoor navigation etc

GPS tracking isn’t excellent at giving accurate measurements of the close distance, especially in the indoor environment. On the other hand, Bluetooth is excellent in short ranges because the waves can go through walls. This might fill the gap that GPS tracking has when tracking devices in indoor spaces.

However, most calculations of the distance between two Bluetooth devices are estimates. It’s hard to determine the exact distance between two Bluetooth devices because many factors affect the calculations. Despite the challenges, there are methods to determine the distance between two Bluetooth devices with an accuracy of at least 60-80%.

The ranging method is simple to implement, and it has the formula to calculate the distance between two Bluetooth devices. As the name suggests, both devices need to be within Bluetooth range to estimate the distance. 

This article will share a simple script written in JavaScript to determine nearby Bluetooth devices and their distance in meters.

This script scans for nearby Bluetooth devices and gets an approximation of the distance by using the well-known RSSI to distance formula.

Read more about how to calculate the distance

How to Calculate Distance from the RSSI value of the BLE Beacon

Requirments

Instructions

git clone https://github.com/smart-sensor-devices-ab/ble_distance_measure.git

The script will an index.html and script.js file. Index.html is just has a connect and scan for device button. upon connecting we will be able to scan for nearby devices and. Once the scanning is completed we will be able to see a list of devices and their distance from my computer.

Here is the index.html file

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <title>Get Device Distance Using BleuIO</title>
  </head>
  <body>
    <div class="container"> <br>
    <a href="https://www.bleuio.com/"> <img src="https://www.bleuio.com/images/logo.png" alt="BleuIO get device distance"></a>
    <h1>Get Device Distance Using BleuIO</h1>
<br>
    <button id="connect" class="btn btn-success">Connect</button>
<button id="scan" class="btn btn-warning">Scan Devices</button> &nbsp; &nbsp;  <span id="scanning" class="d-none"> scanning ...</span>  <br><br><br>
<div id="deviceList"></div>
</div>
<br><br>
<script src="./script.js"></script>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>


  </body>
</html>

The script.js file uses bleuio javascript library to connect and scan for BLE devices. Once we get the device list, we try to sort it out by RSSI value and convert RSSI value to distance. Finally we print the result into a table.

Here is the script.js file

import * as my_dongle from 'bleuio'
document.getElementById('connect').addEventListener('click', function(){
  my_dongle.at_connect()
})

/*
Functions to converts rssi to distance 
read more at 
https://iotandelectronics.wordpress.com/2016/10/07/how-to-calculate-distance-from-the-rssi-value-of-the-ble-beacon/
*/
const getDistance =(rssi=>{
  let n=2
  let mp=-69
  return 10 ** ((mp - (rssi))/(10 * n))
})
document.getElementById('scan').addEventListener('click', function(){
  var element = document.getElementById("scanning");
  element.classList.remove("d-none");
  my_dongle.at_central().then((data)=>{    
    my_dongle.at_gapscan(4,false).then((dev)=>{
      //convert array string to array of object with key value
      const formated = dev.map((item) => {
        const [ id, dev,devid,none,rssi,rssival,devname ] = item.split(' ');
        return { id, dev,devid,none,rssi,rssival,devname};
      });
      //array list unique
      let uniqueArr= formated.filter(y=>y.devname!=undefined)
      //sort based on rssi value
      uniqueArr.sort((a, b) => a.rssival > b.rssival && 1 || -1)
      
      let withDistance= uniqueArr.map(r=>{
        r.distance=getDistance(r.rssival).toFixed(2)+' meter'
        return r 
      })
      //generate output
      let mytable = `<h2>Device List</h2>
      <table class='table table-striped table-bordered'>
      <tr>
      <th>Device</th>
      <th>RSSI</th>
      <th>Distance</th>
      </tr>`;
      withDistance.map(j=>{
        mytable += "<tr><td>" +j.devid+" - "+ j.devname + "</td><td>"+ j.rssival+"</td><td>"+j.distance+"</td></tr>";
      })
      mytable += "</table>";
      document.getElementById("deviceList").innerHTML = mytable;
      element.classList.add("d-none");
    })
  })
})

To run this script , we can use a web bundler called parcel. https://parceljs.org/

lets go inside the folder and in terminal type.

parcel index.html

The script will scan for five seconds for nearby devices. You can update the value based on your requirements.

You can also run this script from online at
https://smart-sensor-devices-ab.github.io/ble_distance_measure/dist/

**Make sure your BleuIO dongle is connected

Output

Share this post on :

Transfer files wirelessly over the Bluetooth Low Energy protocol Using Python

This tutorial will show how to transfer files wirelessly over the Bluetooth Low Energy protocol. We will use two BleuIO dongles for this project—one for sending and another one for receiving. The sender dongle will be in central mode, while the receiver dongle will be in peripheral mode. 

We have already created two python scripts for sending and receiving. You can get the source code from 

https://github.com/smart-sensor-devices-ab/bleuio_file_transfer_example

You can try this project on two different computers.

A video tutorial will show you how to do it on one computer and send files between two BleuIO dongles.

Requirements :

Steps:

  • First, connect two BleuIO dongles to your computer. 
  • Note down the COM port for each dongle. You can find the port using device manager.
  • Try to find receiver dongle MAC id. To do that, start advertising your receiver dongle and note down the MAC id.  Follow the video if you need more details on how to do it.
  • Open file_recieve.py and file_transfer.py files. Update COM port and MAC id where necessary.

Here is the file_recieve.py

import serial
import time

your_com_port = "COM6"  # Change this to the com port your dongle is connected to.
file_name = "result.png"  # Change this to match the file type you are recieving

connecting_to_dongle = True
trying_to_connect = False

print("Connecting to dongle...")
# Trying to connect to dongle until connected. Make sure the port and baudrate is the same as your dongle.
# You can check in the device manager to see what port then right-click and choose properties then the Port Settings
# tab to see the other settings
while connecting_to_dongle:
    try:
        console = serial.Serial(
            port=your_com_port,
            baudrate=57600,
            parity="N",
            stopbits=1,
            bytesize=8,
            timeout=0,
        )
        if console.is_open.__bool__():
            connecting_to_dongle = False
    except:
        print("Dongle not connected. Please reconnect Dongle.")
        time.sleep(5)

print("Connected to Dongle.")

file_data = ""
file_transfer_done = False
connected = "0"
line = ""
while 1 and console.is_open.__bool__() or not file_transfer_done:
    console.write(str.encode("AT+DUAL"))
    console.write("\r".encode())
    print("Putting dongle in Dual role.")
    time.sleep(0.1)
    console.write(str.encode("AT+ADVSTART"))
    console.write("\r".encode())
    time.sleep(0.1)
    print("Starting advertising and awaiting connection from other dongle...")
    while connected == "0":
        dongle_output2 = console.read(console.in_waiting)
        time.sleep(2)
        if not dongle_output2.isspace():
            if dongle_output2.decode().__contains__("\r\nCONNECTED."):
                connected = "1"
                print("Connected!")
            if dongle_output2.decode().__contains__("\r\nDISCONNECTED."):
                connected = "0"
                print("Disconnected!")
            dongle_output2 = " "
    while connected == "1" or not file_transfer_done:
        time.sleep(0.2)
        dongle_output3 = console.read(console.in_waiting)
        if not dongle_output3.isspace():
            if dongle_output3.__contains__(str.encode("\r\nDISCONNECTED.")):
                print("Disconnected!")
                connected = "0"
            elif dongle_output3.__contains__(str.encode("[DONE]")):
                file_transfer_done = True
                print("File transfer complete!\r\n")
                fo = open(file_name, "wb")
                hex = bytes.fromhex(file_data)
                fo.write(hex)
                fo.close()
                print("Exiting script...")
                exit()
            elif dongle_output3.__contains__(str.encode("[Received]:")):
                line = dongle_output3.decode()
                line = line.replace("\r", "")
                line = line.replace("\n", "")
                line = line.replace("[Received]:", "")
                line = line.replace(" ", "")
                file_data += line
                print(line)
            dongle_output3 = ""
        msg = ""

  • Set the file type that you are expecting to receive. For example, if you are expecting a jpg file, change file_name on file_recieve.py to result.jpg, and for a text file, you can rename it to result.txt

Here is the file_transfer.py

import serial
import time

target_dongle_mac_address = (
    "[0]40:48:FD:E5:2D:AF"  # Change this to the peripheral's mac address.
)
your_com_port = "COM25"  # Change this to the com port your dongle is connected to.

connecting_to_dongle = True
trying_to_connect = False
file_name = "test.txt"

print("Connecting to dongle...")
# Trying to connect to dongle until connected. Make sure the port and baudrate is the same as your dongle.
# You can check in the device manager to see what port then right-click and choose properties then the Port Settings
# tab to see the other settings
while connecting_to_dongle:
    try:
        console = serial.Serial(
            port=your_com_port,
            baudrate=57600,
            parity="N",
            stopbits=1,
            bytesize=8,
            timeout=0,
        )
        if console.is_open.__bool__():
            connecting_to_dongle = False
    except:
        print("Dongle not connected. Please reconnect Dongle.")
        time.sleep(5)

print("Connected to Dongle.")

connected = "0"
while 1 and console.is_open.__bool__():
    console.write(str.encode("AT+DUAL"))
    console.write("\r".encode())
    time.sleep(0.1)
    print("Putting dongle in Dual role and trying to connect to other dongle.")
    while connected == "0":
        time.sleep(0.5)
        if not trying_to_connect:
            console.write(str.encode("AT+GAPCONNECT="))
            console.write(str.encode(target_dongle_mac_address))
            console.write("\r".encode())
            trying_to_connect = True
        dongle_output2 = console.read(console.in_waiting)
        time.sleep(2)
        print("Trying to connect to Peripheral...")
        if not dongle_output2.isspace():
            if dongle_output2.decode().__contains__("\r\nCONNECTED."):
                connected = "1"
                print("Connected!")
                time.sleep(10)
            if dongle_output2.decode().__contains__("\r\nDISCONNECTED."):
                connected = "0"
                print("Disconnected!")
                trying_to_connect = False
            dongle_output2 = " "
    while connected == "1":
        dongle_output3 = console.read(console.in_waiting)
        want_to_exit = input(
            "Press Enter to start sending file or 'exit' to quit script.\n\r>> "
        )
        want_to_exit = want_to_exit.upper()
        if "EXIT" in want_to_exit:
            print("Exiting script...")
            exit()
        hex_to_send = ""
        fo2 = open(file_name, "rb")
        print("Sending file. Please wait...")
        while (byte := fo2.read(1)) :
            hex_to_send += byte.hex().upper()
            if len(hex_to_send) >= 200:
                # my_dongle.at_spssend(hex_to_send)
                console.write(str.encode("AT+SPSSEND=" + hex_to_send + "\r"))
                time.sleep(0.2)
                hex_to_send = ""
        if len(hex_to_send) != 0:
            # my_dongle.at_spssend(hex_to_send)
            console.write(str.encode("AT+SPSSEND=" + hex_to_send + "\r"))
            time.sleep(0.2)
        fo2.close()
        console.write(str.encode("AT+SPSSEND=[DONE]\r"))
        print("File transfer complete!\r\n")
        print("Exiting script...")
        exit()
  • Keep the file in the same directory with file_transfer.py and change the file name accordingly.
  • Now run the script file_recieve.py using a terminal. It will start advertising and will be ready to receive.
  • Now run the file_transfer.py script. 
  • Once both the dongles are connected, you will be asked to send the file from the file transfer script. 
  • The receiver dongle will give you a confirmation once received, and you will find the file stored in the same folder with the receiver script.
  • Finally, you can check if you received the file correctly by running a checksum. 

Please follow the video if you have difficulty in understanding. 

Share this post on :

Create a Bluetooth Low Energy repeater using Raspberry pi

Bluetooth low energy technology offers a suitable way of connecting smart devices.
However, despite the convenience, you can bear witness that the range offered can be a little limiting. Sometimes the connection tends to drop or lag when you move a little further away from the device. Fortunately, it’s easy to overcome this range limitation with the Bluetooth repeater.

This article will explain how to create a BLE Repeater using BLE USB dongle called BleuIO and python.

The BleuIO is Bluetooth low energy solution that can be used to create new BLE 5.0 applications in the fastest and easiest way. Using this dongle’s multi-connection feature, we will make a simple repeater where one dongle scans nearby devices data and sends it to the repeater. At the same time, the repeater passes the data to the receiver dongle. And that’s how a repeater will help us overcome the range limitation.

For this project, sender dongle will scan for air quality data from HibouAir device and pass the advertised data along with timestamp to the repeater. The repeater dongle will be connected to a Raspberry pi which will forward data to the receiver dongle.

We have already created a sample script in python, which will help us to do the task.

Requirements :

  1. 3 pcs BleuIO Dongle.
  2. HibouAir or any BLE device
  3. Python 2.7 or Python 3.4 and newer
  4. pyserial 3.5

Task:

Step 1: 

Let’s start by cloning the repository from https://github.com/smart-sensor-devices-ab/bleuio_repeater_example

Once you cloned the script, you will find three different python script called

  • repeater_example_reciever_dongle.py
  • repeater_example_repeter_dongle.py
  • repeater_example_sender_dongle.py

We need to update the ports on those scripts manually.

repeater_example_reciever_dongle.py

import time
from bleuio_lib.bleuio_funcs import BleuIo


reciever_dongle_port = "COM74"  # Change this to your dongle's COM port
mac_addr_to_repeater = (
    "[0]40:48:FD:E5:2D:74"  # Change this to your repeater dongle's mac address
)

buffer = ""
connected = False
connecting = ""

reciever_dongle = BleuIo(port=reciever_dongle_port)
reciever_dongle.start_daemon()

print("Dongle found.")


def save_msg(buffer):
    """
    Parses incomming data string for just the data and prints it out.
    """
    result = buffer
    result_array1 = result.split("\r\n")
    result_array = result_array1[2].split(" ")
    msg_to_save = str(result_array[0])
    print("Recieved = " + msg_to_save)


try:
    reciever_dongle.at_dual()

    ready = input(
        "Press enter to connect to the repeater dongle. (This should be connected first)."
    )
    print("Connecting...")
    reciever_dongle.at_gapconnect(mac_addr_to_repeater)
    time.sleep(5)
    while not connected:
        connected_status = reciever_dongle.ati()
        if "\r\nConnected" in connected_status[0]:
            connected = True
            break
        if "\r\nNot Connected" in connected_status[0]:
            reciever_dongle.at_gapconnect(mac_addr_to_repeater)
            time.sleep(5)
        print("Trying to connect...")
        time.sleep(2)

    print("Connected.")
    print("Waiting to recieve...")

    while 1:
        buffer = reciever_dongle.rx_buffer.decode("utf-8", "ignore")
        if "\r\nhandle_evt_gattc_notification:" in buffer:
            save_msg(buffer)
        time.sleep(0.5)
except KeyboardInterrupt:
    reciever_dongle.at_cancel_connect()
    reciever_dongle.at_gapdisconnect()
    reciever_dongle.at_advstop()
    print("Shutting down script.")

repeater_example_repeter_dongle.py

import time
from bleuio_lib.bleuio_funcs import BleuIo

repeter_dongle_port = "COM38"  # Change this to your dongle's COM port

repeter_dongle = BleuIo(port=repeter_dongle_port)
repeter_dongle.start_daemon()

buffer = ""
num_of_connected_devices = 0
connection_list = []

print("Dongle found.")

repeter_dongle.at_dual()
repeter_dongle.at_advstart()
repeter_dongle.rx_state = "rx_waiting"
print("Waiting for other dongles to connect...")


def send_msg(buffer):
    """
    Parses incomming data string for just the data and sends it forward via the Serial Port Service.
    """
    try:
        result = buffer
        msg_to_send = ""
        result_array1 = result.split("\r\n")
        for line in result_array1:
            if "[Received]:" in line:
                msg_to_send = line.split(" ")
                msg_to_send = msg_to_send[1]
                break
        print("Forwarding data to reciever:")
        print(msg_to_send)
        if not msg_to_send == "":
            repeter_dongle.at_spssend(msg_to_send)
            repeter_dongle.rx_state = "rx_waiting"
    except:
        print(" ")


try:
    while 1:
        buffer = repeter_dongle.rx_buffer.decode("utf-8", "ignore")
        if "\r\nCONNECTED." in buffer:
            num_of_connected_devices = num_of_connected_devices + 1
            print("A Dongle has connected!")
            repeter_dongle.at_advstart()
            repeter_dongle.rx_state = "rx_waiting"
        if "DISCONNECTED." in buffer:
            num_of_connected_devices = num_of_connected_devices - 1
            connection_list = []
            print("No Dongles connected.")
        if num_of_connected_devices > 1:
            if "\r\n[Received]:" in buffer:
                # print(buffer)
                send_msg(buffer)
        buffer = ""
        time.sleep(0.1)
except KeyboardInterrupt:
    repeter_dongle.at_advstop()
    repeter_dongle.at_gapdisconnect()
    print("Shutting down script.")

repeater_example_sender_dongle.py

import time
from bleuio_lib.bleuio_funcs import BleuIo
import random

sender_dongle_port = "COM73"  # Change this to your dongle's COM port
mac_addr_to_repeater = (
    "[0]40:48:FD:E5:2D:74"  # Change this to your repeater dongle's mac address
)

sender_dongle = BleuIo(port=sender_dongle_port)
sender_dongle.start_daemon()

connected = False
data = ""
buffer = ""


def scan_and_get_results():
    """
    Starts a BLE scan for three seconds, looking for results including the 'FF' flag in the advertising data.
    Then it saves the results in a list and returns one of the results as a string.
    :return: string
    """
    print("Scanning...")
    return_data = ""
    result_list = []
    time.sleep(0.5)
    try:
        scanning = sender_dongle.at_findscandata("FF")
        if "SCANNING" in scanning[0]:
            time.sleep(4)
            sender_dongle.stop_scan()
            time.sleep(0.5)
            result_list = sender_dongle.rx_scanning_results
            if not result_list == []:
                if len(result_list) > 3:
                    lines = result_list[2].split("\r\n")
                    if not lines[1] == "":
                        data_array = lines[1].split(" ")
                        lenght = len(data_array)
                        if lenght == 5:
                            data = data_array[4]
                            return_data = data
            else:
                sender_dongle.stop_scan()
                result_list = []
                sender_dongle.rx_state = "rx_waiting"
                return_data = ""
    except:
        sender_dongle.stop_scan()
        result_list = []
        sender_dongle.rx_state = "rx_waiting"
        return_data = ""
    return return_data


print("Dongle found.")

try:
    sender_dongle.at_dual()
    ready = input(
        "Press enter to connect to the repeater dongle. (This should be connected last)."
    )
    print("Connecting...")
    sender_dongle.at_gapconnect(mac_addr_to_repeater)
    time.sleep(5)
    while not connected:
        connected_status = sender_dongle.ati()
        if "\r\nConnected" in connected_status[0]:
            connected = True
            break
        if "\r\nNot Connected" in connected_status[0]:
            sender_dongle.at_gapconnect(mac_addr_to_repeater)
            time.sleep(5)
        print("Trying to connect...")
        time.sleep(2)

    print("Connected.")
    print("Getting services...")
    get_services = sender_dongle.at_get_services()
    sender_dongle.rx_state = "rx_waiting"
    time.sleep(2)
    ready = input("Press enter to start sending data to the repeater dongle.")

    while 1:
        data = scan_and_get_results()
        time.sleep(1)
        if not data == "":
            sent = sender_dongle.at_spssend(data)
            time.sleep(0.1)
            if len(sent) == 1:
                if "[Sent]" in sent[0]:
                    print("Data = (" + data + ") sent.")
                    time.sleep(1)
            data = ""
        time.sleep(1)
        sender_dongle.rx_state = "rx_waiting"
except KeyboardInterrupt:
    sender_dongle.at_gapdisconnect()
    print("Shutting down script.")

Step 2:
Connect two dongles on your PC. You can do the process on three different PC or Raspberry Pi.
For this project, I have connected both the sender and receiver dongles to one PC.
After connecting the dongles, open device manager (windows) to find ports of each dongle.

On my PC, I have two dongles connected on port 5 and 6.
Let’s make COM 5 as the sender, COM 6 as a receiver.
Now open the scripts and set the ports number accordingly.


We also need to know repeater dongles ID. To do that, we can simply advertise the dongle on Raspberry pi using AT+ADVSTART command .Then do a gapscan using AT+GAPSCAN from sender dongle and look for a dongle called BleuIO


Once we have repeater dongles id, we can put it on our scripts.

Step 3 :

Now lets run the script. 

First move to the script directory on Raspberry pi and using command prompt type sudo python3 repeater_example_repeter_dongle.py to run repeater script.

Similarly, run receiver and sender scripts accordingly on PC.

As the name suggests, the repeater will repeat its content. On the terminal, we will see the message sent to the repeater from the sender, and the repeater forwards the data to the receiver dongle. 

Follow this video for a better understanding.

Share this post on :

Create simple android app and transfer data between pc and mobile using JavaScript

This project will demonstrate how to easily create an android app and transfer data between PC and mobile phone. 

At first, we will connect BleuIO dongle to our pc. This dongle will help us connect our pc to mobile app and also the data transfer between them.

We already created a simple android app for this project. All you need to clone this project and run the app on your mobile phone. You can make changes if you need to. 

Requirements

We will set BleuIO dongle as peripheral, and our mobile app will connect to this dongle. Then we can start sending data back and forth.

Connect your BleuIO dongle to the computer.

Connect to dongle using comport and start advertising by AT+ADVSTART.

Clone this git repo.
git clone https://github.com/smart-sensor-devices-ab/bleuio_sps_example_mobileapp.git

go inside the folder and run npm install on terminal

The most important file of this script is Home.tsx , which is inside src/pages folder. This script has the service UUID information to communicate with the dongle which is connected to our computer.

It also helps the mobile app to connects to the computer and send messages using SPS.

This file also works as front end of the mobile app and takes input from user to send message to computer or receive and display message on mobile screen.

Here is the full code for Home.tsx

import {
  IonButton,
  IonContent,
  IonHeader,
  IonInput,
  IonPage,
  IonTitle,
  IonToolbar,
} from "@ionic/react";
import {
  BluetoothLE,
  DescriptorParams,
  WriteCharacteristicParams,
  CharacteristicParams,
  OperationResult,
} from "@ionic-native/bluetooth-le";
import "./Home.css";
import { AndroidPermissions } from "@ionic-native/android-permissions";
import React, { useState } from "react";
let notiMsg: string;
//BleuIO dongle info
const DONGLE_SERVICE_UUID = "0783B03E-8535-B5A0-7140-A304D2495CB7";
const DONGLE_CHAR_TX_UUID = "0783B03E-8535-B5A0-7140-A304D2495CB8";
const DONGLE_CHAR_RX_UUID = "0783B03E-8535-B5A0-7140-A304D2495CBA";
const DONGLE_FLOW_CONTROL_UUID = "0783B03E-8535-B5A0-7140-A304D2495CB9";
const ccc_on = new Uint8Array([0x01]);
let devices: { address: string; name: string }[] = [];
const Home: React.FC = () => {
  const [deviceList, setDeviceList] = useState<any>([]);
  const [dongleID, setDongleID] = useState<string>();
  const [response, setResponse] = useState<string>();
  const [text, setText] = useState<string>();
  const [loading, setLoading] = useState<string>();
  const [deviceConnectionStatus, setDeviceConnectionStatus] = useState<string>(
    "Not Connected"
  );
  //Scan for BleuIO Dongle
  const scanDongle = () => {
    setLoading("Scanning...");
    BluetoothLE.initialize().subscribe((ble) => {
      //console.log("ble stastus", ble.status); // logs 'enabled'
    });
    let scanParam = {
      services: [],
      allowDuplicates: true,
      scanMode: BluetoothLE.SCAN_MODE_LOW_LATENCY,
      matchMode: BluetoothLE.MATCH_MODE_AGGRESSIVE,
      matchNum: BluetoothLE.MATCH_NUM_MAX_ADVERTISEMENT,
      callbackType: BluetoothLE.CALLBACK_TYPE_ALL_MATCHES,
    };
    AndroidPermissions.checkPermission(
      AndroidPermissions.PERMISSION.ACCESS_FINE_LOCATION
    ).then(
      (result) => {
        if (result.hasPermission) {
          //console.log("Has permission?", result.hasPermission);
          BluetoothLE.startScan(scanParam).subscribe((device) => {
            devices.push({ address: device.address, name: device.name });
            //console.log("the devices", JSON.stringify(devices));
          });
        } else {
          AndroidPermissions.requestPermission(
            AndroidPermissions.PERMISSION.ACCESS_FINE_LOCATION
          );
        }
      },
      (err) => console.log("Has permission?", err)
    );

    setTimeout(() => {
      BluetoothLE.stopScan().then(() => {
        //console.log("scan stopped");
        let bleuIOdev = devices.filter((d) => {
          return d.name === "BleuIO";
        });
        //console.log("the devices", JSON.stringify(bleuIOdev));
        setDeviceList(bleuIOdev);
        setLoading("");
      });
    }, 3000);
  };

  const connectToDongle = (e: string) => {
    setDongleID(e);

    BluetoothLE.connect({ address: e, autoConnect: false }).subscribe(
      (peripheralData) => {
        //console.log("peripheral data", JSON.stringify(peripheralData));
        setDeviceConnectionStatus(peripheralData.status);
        //BLE services
        BluetoothLE.discover({ address: e }).then(
          (services) => {
            //console.log("Services: " + JSON.stringify(services));
          },
          (error) => {
            //console.log("Service error: " + JSON.stringify(error));
            BluetoothLE.services({ address: e });
          }
        );
      }
    );
  };

  const sendMessage = () => {
    if (deviceConnectionStatus === "connected") {
      //console.log("Going into settingNotification!");
      let notiService = setupNotification(dongleID!);
      if (notiService !== false) {
        notiService.subscribe(onNotificationSuccess, onNotificationFailure);
        setTimeout(() => {
          let wQwRmsg: WriteCharacteristicParams = {
            address: dongleID!,
            service: DONGLE_SERVICE_UUID,
            characteristic: DONGLE_FLOW_CONTROL_UUID,
            value: BluetoothLE.bytesToEncodedString(ccc_on),
            type: "noResponse",
          };
          BluetoothLE.write(wQwRmsg);
          writeBleData(text!, null);
        }, 1500);
      } else {
        //console.log("setting notification failed!");
      }
    } //end inotificatio set
  };

  function stringToBytes(string: string) {
    var array = new Uint8Array(string.length);
    for (var i = 0, l = string.length; i < l; i++) {
      array[i] = string.charCodeAt(i);
    }
    return array.buffer;
  }

  async function writeBleData(cmd: string, value: string | null) {
    //let ourMsg: string = value ? cmd + value + "    " : cmd + "    ";
    let ourMsg: string = cmd;
    let msgToSend = new Uint8Array(stringToBytes(ourMsg));
    let writeParam: WriteCharacteristicParams = {
      address: dongleID!,
      service: DONGLE_SERVICE_UUID,
      characteristic: DONGLE_CHAR_RX_UUID,
      value: BluetoothLE.bytesToEncodedString(msgToSend),
      type: "noResponse",
    };
    BluetoothLE.write(writeParam);
    //console.log("Send Write Request= " + ourMsg);
  }
  function setupNotification(dongleID: string) {
    let isServiceOK: any = false;
    let isCharacOK: any = false;
    let counter = 0;
    let notiParams: DescriptorParams = {
      address: dongleID,
      service: DONGLE_SERVICE_UUID,
      characteristic: DONGLE_CHAR_TX_UUID,
    };

    while (!isCharacOK && !isServiceOK) {
      counter++;
      if (!isServiceOK) {
        isServiceOK = setupBleServices(dongleID);
      }
      if (!isCharacOK) {
        isCharacOK = setupCharacteristics(dongleID);
      }
      if (counter > 100) {
        return false;
      }
    }

    return BluetoothLE.subscribe(notiParams);
  }

  async function setupBleServices(dongleID: string) {
    let success = false;
    await BluetoothLE.services({ address: dongleID! }).then(
      (services) => {
        //console.log("Services: " + JSON.stringify(services));
        success = true;
      },
      (error) => {
        //console.log("Service error: " + JSON.stringify(error));
        success = false;
      }
    );

    return success;
  }

  async function setupCharacteristics(dongleID: string) {
    let success = false;
    let charParam: CharacteristicParams = {
      address: dongleID!,
      service: DONGLE_SERVICE_UUID,
    };

    await BluetoothLE.characteristics(charParam).then(
      (characteristics) => {
        //console.log("Services: " + JSON.stringify(characteristics));
        success = true;
      },
      (error) => {
        //console.log("Service error: " + JSON.stringify(error));
        success = false;
      }
    );

    return success;
  }

  function onNotificationSuccess(buffer: OperationResult) {
    if (buffer.status === "subscribed") {
      //isNotificationSet = true;
      //setIsNotificationSet((isNotificationSet) => (isNotificationSet = true));
    }
    if (buffer.value) {
      var byteString = BluetoothLE.encodedStringToBytes(buffer.value);
      notiMsg = BluetoothLE.bytesToString(byteString);
      //console.log("Value: " + buffer.value);
      //console.log("Value parsed: " + notiMsg);
      //console.log("Notification msg: " + notiMsg);
      setResponse(notiMsg);
    }
  }
  function retrySubscribe() {
    let notiParams: DescriptorParams = {
      address: dongleID!,
      service: DONGLE_SERVICE_UUID,
      characteristic: DONGLE_CHAR_TX_UUID,
    };
    BluetoothLE.subscribe(notiParams).subscribe(
      onNotificationSuccess,
      onNotificationFailure
    );
  }

  function onNotificationFailure(error: any) {
    if (error.message !== "Already subscribed") {
      //isNotificationSet = false;
      //setIsNotificationSet((isNotificationSet) => (isNotificationSet = false));
      //console.log("Failed to get Notification: " + JSON.stringify(error));
      if (error.error !== "neverConnected") {
        retrySubscribe();
      }
    }
    if (error.message === "Device isn't connected") {
      BluetoothLE.close({
        address: dongleID!,
      });
    }
    if (error.error === "isDisconnected") {
      //console.log("Disconnected...");
    }
  }
  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle slot="start">BleuIO SPS</IonTitle>
          <IonTitle size="small" slot="end">
            {deviceConnectionStatus}
          </IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent className="container ion-padding">
        <IonHeader collapse="condense">
          <IonToolbar>
            <IonTitle slot="start">BleuIO SPS</IonTitle>
            <IonTitle size="small" slot="end">
              {deviceConnectionStatus}
            </IonTitle>
          </IonToolbar>
        </IonHeader>
        <br />
        <br />
        <IonButton color="primary" onClick={scanDongle}>
          Scan for BleuIO devices
        </IonButton>
        <br />
        {loading}
        <ul>
          {deviceList &&
            deviceList.length > 0 &&
            deviceList.map((d: any) => (
              <IonButton
                color="success"
                onClick={() => connectToDongle(d.address)}
              >
                Connect to {d.name} <br /> Address: {d.address}
              </IonButton>
            ))}
        </ul>
        <br />
        <IonInput
          value={text}
          placeholder="Write message"
          onIonChange={(e) => setText(e.detail.value!)}
        ></IonInput>
        <IonButton
          disabled={deviceConnectionStatus !== "connected" && !text}
          color="warning"
          onClick={sendMessage}
        >
          Write Message
        </IonButton>
        {response && (
          <>
            Response : {response} <br />
          </>
        )}
        <br />
        <br />
        <br />
        <div
          style={{
            display: "flex",
            justifyContent: "center",
            alignItems: "center",
          }}
        >
          Learn more about BleuIO https://www.bleuio.com/
        </div>
      </IonContent>
    </IonPage>
  );
};

export default Home;

if you run ionic serve on terminal, you will be able to see the layout on the browser. But for this app, we need mobile phones native feature because we need Bluetooth connection. Therefore connect your android phone to your computer and run ionic cap run android on terminal.

If you get an error regarding gradle, Type ionic capacitor update and run  ionic cap run android again.

Your app will install and open up on your mobile phone.

Make sure the dongle is on peripheral mode and advertising. 

Click scan for BleuIO devices.

On the scan result, select one of your dongles to connect.

After a successful connection, you can send data back and forth, and you will be able to see your messages on both the mobile app and the terminal.

For better understanding, watch the video.

Share this post on :

Establishing a Secure BLE Connection

Recently, Bluetooth Low Energy (BLE) has become one of the most popular wireless standards for IoT devices. Most applications are now using this technology to transfer sensitive information between devices. Therefore, designers looking to integrate BLE into their products should be aware of the BLE security features and limitations. 

The main security issues with the pairing between devices with BLE are passive eavesdropping, man in the middle (MITM) attacks and identity tracking. 

BLE offers several security features and levels for communication between devices. A designers has to be aware of the specific security threats facing BLE, and try to implement and mitigate security risk in both hardware and firmware design.

BleuIO team is continuously updating its firmware and libraries to make the BLE connection more secure between devices.

At the moment, we have Numeric Comparison, Just Works or Passkey Entry for pairing and bonding. 

  • Numeric Comparison: In this scenario, both the devices have a display unit able to display a six-digit number. Both displays show the same number, and the user is asked to confirm that these numbers match. 
  • Passkey Entry: The Passkey Entry is initially intended for the case that one device has a keyboard, but no display unit and the other device has at least a display unit, for example, a BLE and a PC keyboard scenario. The user is shown a six-digit number (from “000000” to “999999”) on the device with a display and then is asked to enter the number on the other device. If the value entered on the second device is correct, the pairing is successful.
  • Just Works: This model is primarily intended for the most constrained devices in terms of I/O. The Just Works association model uses the Numeric Comparison protocol, but the user is never shown a number, and the application may simply ask the user to accept the connection. This method doesn’t offer protection against a Man in the Middle (MITM) attack, but it provides the same protection level against passive eavesdropping as the Numeric Comparison.

BleuIO security features :

  • Set the passkey for passkey authentication on your dongle yourself.
  • Set the minimum security level your dongle will use. This can be done either when already connected to a device or before. If the device you are connecting to does not meet the security level requirement, the dongle will disconnect from it.
  • Choose if the dongle should auto accept the numeric comparison authentication request or manually.
  • Can access protected characteristics that need an increased Security Level. 

Please take a look at our Get Started guide to know more about it. 

Following video shows how to securely pair between two BleuIO devices.

Share this post on :

Store air quality sensor data from Bluetooth device to google firebase cloud

In this article, we will see how to send BLE data to the cloud. Together with the BleuIO javascript library and BleuIO USB dongle, we will get BLE data from HibouAir. After getting the advertised data, we will pass it to a simple function that will give us an object with meaningful air quality data numbers. Then we will send the data to the firebase cloud. 

For this project, we will need 

Step 1:

Clone the GitHub repository using git clone https://github.com/smart-sensor-devices-ab/hbiouAirToFirebase.git

Step 2:

  • Create a firebase account from https://firebase.google.com/.
  • After creating a firebase account, create a project and an app under this project. Make sure its a web app.
  • Once the app is ready, collect your app information API key, apiKey etc.
  • Add a real time database

Also, make sure your firebase realtime database read-write rules are true.

{ “rules”: { “.read”: true, “.write”: true } }

Now open firebaseconfig.js file from the root folder and paste your information collected.

Note : if the database information is not on the list, please update it manually by getting the realtime database url

Follow the detailed guideline to setup firebase for this script.

https://hibouair.com/cloud-storage/firebase-api-instructions.pdf

Step 3:

Let’s start the app. To run the app, we need to have a website bundler. We can use parceljs. Install parceljs from https://parceljs.org/getting-started/webapp/

Once it’s installed, go to the app folder and type parcel index.html

You will have your app running on the browser.

Step 4:

  • Connect your dongle to your pc. 
  • Open the app on your browser and click connect. 
  • Click on the device information. If you see the device is in peripheral mode than you have to make it central. 
  • Click on the central button. And recheck device information. 

Step 4:

  • Lets scan for nearby devices. 
  • Click on the scan button and look at the list of the scanned device on your console log. Pick any HibouAir devices and copy the device information. 
  • Open the index.js file and go to line 95
  • replace the scan target information with the one you copied.
  • Update firebaseconfig.js with

Now save the page and click on send data to the cloud. If your firebase configuration is correct, you should see data showing on your cloud database. 

Here is the code for index.html file

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl"
      crossorigin="anonymous"
    />

    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://www.gstatic.com/firebasejs/8.2.9/firebase-app.js"></script>
    <script src="https://www.gstatic.com/firebasejs/8.2.9/firebase-database.js"></script>
    <title>BLE data to google firebase</title>
  </head>
  <body>
    <div class="container">
      <h2>Send BLE data to cloud</h2>
      <p>
        This script helps you to connect to BleuIO and sends data to Firebase
        cloud.
      </p>
      <p>
        Learn more about BleuIO.
        <a href="https://www.bleuio.com/">https://www.bleuio.com/</a>
      </p>
      <br />
      <button class="btn btn-success" id="connect">Connect</button>
      <button class="btn btn-success" id="deviceinfo">Device Info</button>
      <!-- To get Ble data from HibouAir (Peripheral device), BleuIO has to be on central mode -->
      <button class="btn btn-success" id="central">Central Mode</button>

      <button class="btn btn-success" id="scan">Scan for devices</button>

      <button class="btn btn-success" id="sendDataTCloudBtn">
        Send data to cloud Data
      </button>

      <button class="btn btn-success" id="stopSendingData">
        Stop Sending Data
      </button>
      <br /><br />
      <h5><div id="log"></div></h5>
    </div>

    <script src="./firebaseconfig.js"></script>
    <script src="./index.js"></script>
  </body>
</html>

Here is the code for index.js file

import * as my_dongle from 'bleuio'
document.getElementById('connect').addEventListener('click', function(){
  my_dongle.at_connect()
})
document.getElementById('deviceinfo').addEventListener('click', function(){
  my_dongle.ati().then((data)=>console.log(data))
})
document.getElementById('central').addEventListener('click', function(){
    my_dongle.at_central().then((data)=>console.log(data))
})
document.getElementById('scan').addEventListener('click', function(){
my_dongle.at_gapscan(2).then((data)=>console.log(data))
})
const parseSensorData = ((input) =>{
    let counter = 13;
    if (input.includes("5B070503")) {
      counter = 17;
    }
    let sensorData = {
      sensorid:
        input[counter + 1] +
        input[counter + 2] +
        input[counter + 3] +
        input[counter + 4] +
        input[counter + 5] +
        input[counter + 6],
      pressure:
        parseInt(
          input[counter + 13] +
            input[counter + 14] +
            input[counter + 11] +
            input[counter + 12],
          16
        ) / 10,
      temperature:
        parseInt(
          input[counter + 17] +
            input[counter + 18] +
            input[counter + 15] +
            input[counter + 16],
          16
        ) / 10,
      humidity:
        parseInt(
          input[counter + 21] +
            input[counter + 22] +
            input[counter + 19] +
            input[counter + 20],
          16
        ) / 10,
        voc:
        parseInt(
          input[counter + 25] +
            input[counter + 26] +
            input[counter + 23] +
            input[counter + 24],
          16
        ) / 10,
      als: parseInt(
        input[counter + 9] +
          input[counter + 10] +
          input[counter + 7] +
          input[counter + 8],
        16
      ),
      pm1:
        parseInt(
          input[counter + 29] +
            input[counter + 30] +
            input[counter + 27] +
            input[counter + 28],
          16
        ) / 10,
      pm25:
        parseInt(
          input[counter + 33] +
            input[counter + 34] +
            input[counter + 31] +
            input[counter + 32],
          16
        ) / 10,
      pm10:
        parseInt(
          input[counter + 37] +
            input[counter + 38] +
            input[counter + 35] +
            input[counter + 36],
          16
        ) / 10}
    return sensorData
  })

const sendDataToCloud = (()=>{
    //get the scan target device id by scanning for device.
    my_dongle.at_scantarget('[1]F9:0D:35:E7:72:65',2).then((data)=>{
        let theAdvData = data.filter(element => element.includes("ADV"));
        if(theAdvData && theAdvData.length>0){
            console.log(theAdvData)
            let advData = theAdvData[0].split("[ADV]: ")
            // converting advertising string to meaningfull numbers 
            //and pass it to an array of objects
            let airQualityData = parseSensorData(advData[1])
            console.log(airQualityData)
            // save the data to database 
            let database = firebase.database(); // which gets the database 
            let ref = database.ref("records");
            //pushing the object to the reference
            ref.push(airQualityData)
        }
    })
})
var intervalId
document.getElementById('sendDataTCloudBtn').addEventListener('click', function(){
    sendDataToCloud()
    if (intervalId) {
        clearInterval(intervalId);
    }
    intervalId = setInterval(sendDataToCloud ,5000);
    document.getElementById("log").innerHTML="Sending data to cloud. Click stop sending data to stop the process.";

})
document.getElementById('stopSendingData').addEventListener('click', function(){
    clearInterval(intervalId)
    document.getElementById("log").innerHTML="Sending data stopped.";
 })
document.getElementById('stopProcess').addEventListener('click', function(){
   console.log(my_dongle.stop()) 
})

Right now, its sending data every 5 seconds. 

You can stop the process by clicking stop sending data.

Have a look at the following video for a better understanding. 

Share this post on :

Make your Bluetooth Low Energy connection secure using BleuIO

Protection of private information is essential for every wireless low energy device, from fitness band to payment systems. Privacy mechanisms prevent devices from being tracked by untrusted devices.

Secure communications keep data safe while also preventing unauthorized devices from injecting data to trigger the system’s unintended operation.

In Bluetooth Low Energy (BLE), devices connected to a link can pass sensitive data by setting up a secure encrypted connection, which means making the data unreadable to all but the Bluetooth master and slave devices.

BleuIO has introduced security feature into its latest release (firmware v1.3.0 ). User can now use Numeric Comparison, Just Works or Passkey Entry to make data transmission more secure when working with Bluetooth low energy application using BleuIO. 

  • Numeric Comparison: In this scenario, both devices have a display unit capable of displaying a six-digit number. Both displays output the same number, and the user is asked to confirm that these numbers match. 
  • Passkey Entry: The Passkey Entry is primarily intended for the case that one device has a keyboard but no display unit and the other device has at least a display unit, for example, a PC and a BLE keyboard scenario. The user is shown a six-digit number (from “000000” to “999999”) on the device with a display and then is asked to enter the number on the other device. If the value entered on the second device is correct, the pairing is successful.
  • Just Works: This model is primarily intended for the most constrained devices in terms of I/O. The Just Works association model uses the Numeric Comparison protocol, but the user is never shown a number, and the application may simply ask the user to accept the connection. This method doesn’t offer protection against a Man in the Middle (MITM) attack, but it provides the same protection level against passive eavesdropping as the Numeric Comparison.

Use the following AT commands to apply secure connection.

AT Commands :

  • AT+SETPASSKEY for setting or querying set passkey for passkey authentication.
  • AT+SECLVL for setting or querying minimum security level used when connected to other devices.
  • AT+NUMCOMPA for accepting a numeric comparison authentication request or enabling/disabling auto-accepting numeric comparisons.

Following video shows how to pair between two BleuIO devices and apply above mentioned security.

Share this post on :

Collect Realtime Air Quality Data From Bluetooth Device

This project will show how to collect Realtime Bluetooth low energy data and show it on web browser.

For this project, I am using Bluetooth Low Energy USB dongle called BlueIO, which will act as a central device to retrieve data. Hibou Air Quality Monitor which will serve as a peripheral device to transmit the data. The script is simple to use and can be used for other purposes such as store the data into database or cloud.

Things we need:

Before we start

The article assumes you have some general knowledge of how Bluetooth Low Energy (BLE) work. Since the Chrome Serial specification on Google Chrome is not finalized yet, you will have to go to enable the highlighted flag, and restart Chrome. open chrome://flags/#enable-experimental-web-platform-features in chrome browser. In this example, we are going to use JavaScript + html (and some CSS for styling) to setup the BleuIO and quickly start scanning.

The script

The source code is available on Github.

https://github.com/smart-sensor-devices-ab/get_realtime_data_bleuio

The index.html file contains the layout of the script. There are two main buttons. Connect, scan and stop getting data. The connect button will use web serial to connect to BleuIO dongle. After that the scan BLE devices button will send some AT commands to the dongle and the respnse will be printed on the scree.

Here is the index.html file

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>BleuIO Web Bluetooth Example</title>
    <meta charset="utf-8" />
    <meta name="ssd" content="beaconexample" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="shortcut icon" type="image/png" href="images/favicon.png" />

    <script>
      // Redirect to HTTPS if HTTP is requested.
      if (window.location.protocol === "http:") {
        window.location.href = "https:" + window.location.href.substring(5);
      }
    </script>

    <link rel="stylesheet" href="style.css" />
    <link
      rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
      integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z"
      crossorigin="anonymous"
    />
  </head>
  <body>
    <div class="container">
      <div
        id="carouselExampleFade"
        class="carousel slide carousel-fade"
        data-ride="carousel"
      >
        <div class="carousel-inner">
          <div class="carousel-item active">
            <div class="row">
              <div class="col-md-9 caption">
                <img src="images/logo.png" />
                <h1>Bluetooth® low energy adapter</h1>
                <a
                  class="btn btn-info btn-lg"
                  href="https://www.bleuio.com/"
                  target="_blank"
                  >Learn More</a
                >
              </div>
              <div class="col-md-3">
                <img
                  src="images/bleuIO_white_withlogo.png"
                  class="d-block w-100"
                  alt="..."
                />
              </div>
            </div>
          </div>
          <div class="carousel-item">
            <div class="row">
              <div class="col-md-9 caption">
                <img src="images/logo.png" />
                <h1>Create your own BLE applications</h1>
                <a
                  class="btn btn-info btn-lg"
                  href="https://www.bleuio.com/"
                  target="_blank"
                  >Learn More</a
                >
              </div>
              <div class="col-md-3">
                <img
                  src="images/bleuIO_black_withlogo.png"
                  class="d-block w-100"
                  alt="..."
                />
              </div>
            </div>
          </div>
          <div class="carousel-item">
            <div class="row">
              <div class="col-md-9 caption">
                <img src="images/logo.png" />
                <h1>Quick, Innovative, Simple</h1>
                <a
                  class="btn btn-info btn-lg"
                  href="https://www.bleuio.com/"
                  target="_blank"
                  >Learn More</a
                >
              </div>
              <div class="col-md-3">
                <img
                  src="images/bleuIO_black_withlogo.png"
                  class="d-block w-100"
                  alt="..."
                />
              </div>
            </div>
          </div>
        </div>
      </div>
      <!-- end carousel -->
    </div>
    <!-- end container -->
    <div class="codesection">
      <div class="container">
        <h1>Get realtime data from BLE device</h1>
        <main class="main">
          <div id="notSupported" class="hidden alert alert-danger">
            Sorry, <b>Web Serial</b> is not supported on this device, make sure
            you're running Chrome 78 or later and have enabled the
            <code>#enable-experimental-web-platform-features</code> flag in
            <code>chrome://flags</code> <br />
            Open
            <strong>
              chrome://flags/#enable-experimental-web-platform-features</strong
            >
            in Google Chrome browser.
          </div>

          <br />
          <button id="butConnect" type="button" class="btn btn-success">
            Connect
          </button>
          <br /><br />
          <div class="row">
            <div class="col-md-3">
              <button
                id="butScan"
                type="button"
                disabled
                class="btn btn-primary"
              >
                Scan BLE Devices
              </button>
            </div>
            <div class="col-md-3">
              <select
                class="devices form-control"
                id="devices"
                onchange="getSelectedDevice(this)"
              >
                <option value="">Select a Device</option>
              </select>
            </div>
            <div class="col-md-3">
              <button
                id="butGetData"
                type="button"
                disabled
                class="btn btn-primary"
              >
                Get Data
              </button>
            </div>
          </div>

          <br /><br />

          <pre id="log" class="mt-5 d-none"></pre>
          <div id="dataIntoTable" class="mt-5"></div>

          <div style="background: white"></div>
        </main>
      </div>
    </div>
    <div class="footer text-center mt-3">
      Powered by <a href="https://www.bleuio.com/" target="_blank">BleuIO</a> .
      A product of
      <a href="http://smartsensordevices.com/" target="_blank"
        >Smart Sensor Devices</a
      >
    </div>

    <!-- end container -->
    <!-- JS, Popper.js, and jQuery -->
    <script
      src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
      integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
      integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"
      integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV"
      crossorigin="anonymous"
    ></script>

    <script src="script.js" defer></script>
  </body>
</html>

The Script.js file contains all the JavaScript codes to connect to the dongle and get advertised data from the air quality monitoring device. After getting the advertised data we try to decodes it to get a meaningful format.

"use strict";

let port;
let reader;
let inputDone;
let outputDone;
let inputStream;
let outputStream;
let isScanning = false;
let isGettingData = false;
let hibouDevices = [];
let rightDevice = false;
let scannedSensorData = []
const log = document.getElementById("log");
const butConnect = document.getElementById("butConnect");
const butScan = document.getElementById("butScan");
const butGetData = document.getElementById("butGetData");
const outputTable = document.getElementById('dataIntoTable')
let outputData='';
document.addEventListener("DOMContentLoaded", () => {
  butScan.addEventListener("click", clickScan);
  butGetData.addEventListener("click", clickGetData);
  butConnect.addEventListener("click", clickConnect);
  const notSupported = document.getElementById("notSupported");
  notSupported.classList.toggle("hidden", "serial" in navigator);
});



/**
 * @name connect
 * Opens a Web Serial connection to a serial device such as a Smart USB Dongle 2.0 and sets up the input and
 * output stream.
 */
async function connect() {
  // - Request a port and open a connection.
  port = await navigator.serial.requestPort();
  // - Wait for the port to open.
  await port.open({ baudRate: 9600 });

  const encoder = new TextEncoderStream();
  outputDone = encoder.readable.pipeTo(port.writable);
  outputStream = encoder.writable;

  let decoder = new TextDecoderStream();
  inputDone = port.readable.pipeTo(decoder.writable);
  inputStream = decoder.readable.pipeThrough(
    new TransformStream(new LineBreakTransformer())
  );

  reader = inputStream.getReader();
  readLoop().catch((error) => {
    toggleUIConnected(false);
    port = null;
    log.textContent = "Dongle Disconnected!";
  });
}

/**
 * @name disconnect
 * Closes the Web Serial connection.
 */
async function disconnect() {
  // Close the input stream (reader).
  if (reader) {
    await reader.cancel();
    await inputDone.catch(() => {});
    reader = null;
    inputDone = null;
  }
  // Close the output stream.
  if (outputStream) {
    await outputStream.getWriter().close();
    await outputDone;
    outputStream = null;
    outputDone = null;
  }
  // Close the port.
  await port.close();
  port = null;
  log.textContent = "Dongle Disconnected!";
}

/**
 * @name clickConnect
 * Click handler for the connect/disconnect button.
 * Checks if port != null
 * If true: Checks if any beacons is advertising or scans are running and stops the advertsing or scan if so. Then runs disconnect() and set toggleUIConnected to false.
 * if false: Runs connect() then set toggleUIConnected to true.
 */
async function clickConnect() {
  log.textContent = "";
  if (port) {

    // If disconnected while scanning the dongle will restart
    if (isScanning) {
      writeCmd("\x03");
      butScan.textContent = "Scan BLE Devices";
      isScanning = false;
    }
    await disconnect();
    toggleUIConnected(false);
    return;
  }
  await connect();
  toggleUIConnected(true);
}

function getSelectedDevice(selectObject) {
  var selectedDevice = selectObject.value;  
  localStorage.setItem("selectedDevice", selectedDevice);
}

/**
 * @name clickScan
 * Click handler for the Scan button.
 * Checks if a scan is already running by checking the boolean isScanning.
 * If isScanning = true: Stops scanning and goes back to peripheral mode, changes the button text and shows the beacon buttons. Finally sets isScanning = false.
 * If isScanning = false: Goes into Central mode and starts scanning for ble devices. Also changes button text and hides the beacon buttons. Finally sets isScanning = true.
 */
function clickScan() {
  console.log("SCAN BUTTON PRESSED");
  if (isScanning) {
    writeCmd("\x03"); // Ctrl+C to stop the scan
    setTimeout(() => {
      writeCmd("AT+PERIPHERAL"); // Set the dongle in Peripheral mode needed for advertising.
    }, 500); // Waiting half a bit to make sure each command will get through separately.
    isScanning = false;
    butGetData.removeAttribute("disabled");
    butScan.textContent = "Scan BLE Devices";
    
    return;
  }
  hibouDevices = [];
  writeCmd("AT+CENTRAL"); // Set the dongle in Central mode needed for scanning.
  setTimeout(() => {
    writeCmd("AT+GAPSCAN=2");
  }, 500); // Waiting half a bit to make sure each command will get through separately.

  butScan.textContent = "Stop Scanning...";
  butGetData.setAttribute("disabled", "true");
  log.classList.toggle("d-none", false);

  isScanning = true;
}

/**
 * @name clickGetData
 * Click handler for the 'Get Data' button.
 * Checks if a getData scan is already running by checking the boolean isGettingData.
 * If isGettingData = true: Stops scanning and goes back to peripheral mode, changes the button text and shows the scan button. Finally sets isGettingData = false.
 * If isGettingData = false: Goes into Central mode and starts scanning for ble devices data. Also changes button text and hides the scan button. Finally sets isGettingData = true.
 */
function clickGetData() {
  console.log("GET DATA BUTTON PRESSED");
  if (isGettingData) {
    writeCmd("\x03"); // Ctrl+C to stop the scan
    setTimeout(() => {
      writeCmd("AT+PERIPHERAL"); // Set the dongle in Peripheral mode needed for advertising.
    }, 500); // Waiting half a bit to make sure each command will get through separately.
    isGettingData = false;

    butScan.removeAttribute("disabled");
    butGetData.textContent = "Get Data";
    return;
  }
  writeCmd("AT+CENTRAL"); // Set the dongle in Central mode needed for scanning.
  setTimeout(() => {
   writeCmd("AT+FINDSCANDATA=FF5B07"); // Will just scan for adv data that contains 'FF5B07' which is the tag for Manufaturing Specific Data (FF) and our Company ID (5B07).
  }, 500); // Waiting half a bit to make sure each command will get through separately.

  butGetData.textContent = "Stop Getting Data...";
  butScan.setAttribute("disabled", "true");
  log.classList.toggle("d-none", false);

  isGettingData = true;

  
}


/**
 * @name readLoop
 * Reads data from the input stream and displays it on screen.
 */
async function readLoop() {
  let i=0;
  while (true) {
    i++;
    const { value, done } = await reader.read();
    if (value && (!isScanning && !isGettingData)) {
      log.textContent += value + "\n";
    }
    if (value && isScanning) {
      if(value === "SCAN COMPLETE") {
        isScanning = false;
        butScan.textContent = "Scan BLE Devices";
        log.textContent += "\n" +"Scan Done" + "\n";
        butGetData.removeAttribute("disabled");
        log.classList.toggle("d-none", false);
      }
      let lineValueArray = value.split(" ");
      if (lineValueArray[6] === "(HibouAIR)") {
        if(lineValueArray[2]) {
          hibouDevices.push("["+lineValueArray[2].replace("[1]", "") +"]");

        }
        log.textContent = "\n" + "hibouDevices found: " + hibouDevices.length + "\n";
      }
      if(value === "SCAN COMPLETE") {
        var select = document.getElementById("devices");
        hibouDevices.map(function(item){
          var option = document.createElement("option");
          option.value = item;
          option.text  = item;
          select.appendChild(option)
        });
      }

    }
    if (value && isGettingData) {
      if(value === "SCAN COMPLETE") {
        isGettingData = false;
        butGetData.textContent = "Get Data";
        log.textContent += "\n" +"Scan Done" + "\n";
        butScan.removeAttribute("disabled");
        log.classList.toggle("d-none", false);
        
      }
      let lineValueArray = value.split(" ");
      
        if (lineValueArray[0] ===   localStorage.getItem("selectedDevice") && lineValueArray[3] === "[ADV]:") {

          scannedSensorData = parseSensorData(lineValueArray[4]);
          outputData = ''
          if((i%30) === 0) {

            outputData += 'Time: '+new Date().getHours() + ":" + new Date().getMinutes() + ":" + new Date().getSeconds()+' '
            outputData += 'Pressure: '+scannedSensorData.p+' '
            outputData += 'Temperature: '+scannedSensorData.t+' '
            outputData += 'Humidity: '+scannedSensorData.h+' '
            outputData += 'ALS: '+scannedSensorData.als+' '
            outputData += 'PM1.0: '+scannedSensorData.pm1+' '
            outputData += 'PM2.5: '+scannedSensorData.pm25+' '
            outputData += 'PM10: '+scannedSensorData.pm10+' '
            //log.innerHTML  += "\n" + "SensorData= " + JSON.stringify(scannedSensorData) + "\n";
            log.innerHTML  += "\n" +outputData
          }
        
        }


    }
    if (done) {
      console.log("[readLoop] DONE", done);
      reader.releaseLock();
      break;
    }
  }
}

/**
 * @name writeCmd
 * Gets a writer from the output stream and send the command to the Smart USB Dongle 2.0.
 * @param  {string} cmd command to send to the Smart USB Dongle 2.0
 */
function writeCmd(cmd) {
  // Write to output stream
  const writer = outputStream.getWriter();
  console.log("[SEND]", cmd);

  writer.write(cmd);
  // Ignores sending carriage return if sending Ctrl+C
  if (cmd !== "\x03") {
    writer.write("\r"); // Important to send a carriage return after a command
  }
  writer.releaseLock();
}

/**
 * @name LineBreakTransformer
 * TransformStream to parse the stream into lines.
 */
class LineBreakTransformer {
  constructor() {
    // A container for holding stream data until a new line.
    this.container = "";
  }

  transform(chunk, controller) {
    // Handle incoming chunk
    this.container += chunk;
    const lines = this.container.split("\r\n");
    this.container = lines.pop();
    lines.forEach((line) => controller.enqueue(line));
  }

  flush(controller) {
    // Flush the stream.
    controller.enqueue(this.container);
  }
}

/**
 * @name toggleUIConnected
 * Changes the text on butConnect depending on the action it actually will preform in the current state.
 * @param  {boolean} connected true if connected, false if disconnected.
 */
function toggleUIConnected(connected) {
  let lbl = "Connect";
  if (connected) {
    lbl = "Disconnect";
    butGetData.removeAttribute("disabled");
    butScan.removeAttribute("disabled");
  }
  butScan.classList.toggle("disabled", !connected);
  butGetData.classList.toggle("disabled", !connected);
  butConnect.textContent = lbl;
}

/**
 * @name parseSensorData
 * Parse the data from advertising data string.
 * @param  {string} input advertising data string.
 * @returns {object ={sensorid:{string}, p:{int}, t:{int}, h:{int}, als:{int}, pm1:{int}, pm25:{int}, pm10:{int}}} 
 */
function parseSensorData(input) {
  let counter = 13;
  if (input.includes("5B070503")) {
    counter = 17;
  }
  let sensorData = {
    sensorid:
      input[counter + 1] +
      input[counter + 2] +
      input[counter + 3] +
      input[counter + 4] +
      input[counter + 5] +
      input[counter + 6],
    p:
      parseInt(
        input[counter + 13] +
          input[counter + 14] +
          input[counter + 11] +
          input[counter + 12],
        16
      ) / 10,
    t:
      parseInt(
        input[counter + 17] +
          input[counter + 18] +
          input[counter + 15] +
          input[counter + 16],
        16
      ) / 10,
    h:
      parseInt(
        input[counter + 21] +
          input[counter + 22] +
          input[counter + 19] +
          input[counter + 20],
        16
      ) / 10,
      voc:
      parseInt(
        input[counter + 25] +
          input[counter + 26] +
          input[counter + 23] +
          input[counter + 24],
        16
      ) / 10,
    als: parseInt(
      input[counter + 9] +
        input[counter + 10] +
        input[counter + 7] +
        input[counter + 8],
      16
    ),
    pm1:
      parseInt(
        input[counter + 29] +
          input[counter + 30] +
          input[counter + 27] +
          input[counter + 28],
        16
      ) / 10,
    pm25:
      parseInt(
        input[counter + 33] +
          input[counter + 34] +
          input[counter + 31] +
          input[counter + 32],
        16
      ) / 10,
    pm10:
      parseInt(
        input[counter + 37] +
          input[counter + 38] +
          input[counter + 35] +
          input[counter + 36],
        16
      ) / 10}
  return sensorData
}

Steps to run the script

  • Clone the git repository .git clone https://github.com/smart-sensor-devices-ab/get_rea…
  • Connect the Bleuio dongle to your computer.
  • Open index.html file
  • Click connect and wait for the device to load on your com port.
  • Select your com port.
  • Scan for BLE devices. (this script only scan for Hibou Devices . You can change the manufacturer value at script.js file)
  • Select device and start getting data. You will see real time value on the screen.
  • Click on stop getting data to stop the script.

Output

Here is the output of the script.

Follow the video for better understanding

Share this post on :