Monitor and Record Temperature With Bluetooth LE and RaspberryPi

1,149

17

4

Published

Introduction: Monitor and Record Temperature With Bluetooth LE and RaspberryPi

This instructable is about how to put together a multi-node temperature monitoring system with Bluetooth LE sensor bug from Blue Radios (BLEHome) and RaspberryPi 3B

Thanks to the development of the Bluetooth LE standard, there is now readily available low power wireless sensors on the market for very low cost and can run on a single coin cell for months at a time. One of these sensor I picked up is from Blue Radio called Sensor Bugs. Costing about $25 on Amazon, it is a Bluetooth LE device with Temperature sensor, light sensor and accerometer all build into a small unit that can communicate wirelessly.

This is a perfect match for Raspberry Pi 3B, which has build in support for Bluetooth LE radio.

Step 1: Setup Raspberry Pi

First step is to get a working Raspberry Pi setup. Follow the instruction from Raspberry Pi’s website, load Raspbian on a SD card, insert into Raspberry Pi and boot it up.

I setup my system with Raspbian Stretch Lite (No GUI) version Nov 2017.
Setup WiFi if needed, I prefer to adjust the time zone to the current time zone instead of UTC. You can do this though the command:
$ sudo dpkg-reconfigure tzdata

Rest of the instruction assume the setup is done though command line interface.

Step 2: Setting Up MySQL on Raspberry Pi

It it useful to have a database installed locally to store all the captured data. Installing MySQL on Raspberry Pi is super easy.
It is also not difficult to modify the script to connect to a SQL server externally, you can skip this step if you wish to use an SQL server on the network.

There are many instructions on the net, I suggest this:
https://www.stewright.me/2014/06/tutorial-install-...


Once the SQL server is installed, you can use MySQL CLI client to create user, database and table.

To enter MySQL CLI, use the command:

$ sudo mysql -uroot-p

First, create a local user to insert captured data:
> CREATE USER ‘datasrc’@‘localhost’ IDENTIFYED BY ‘datasrc000’ ;

Next, create a database and table:
> CREATE DATABASE SensorBug;

Setting up the user permission:
> GRANT ALL PRIVILEGES ON SensorBug.* TO 'datasrc'@'localhost';

Now add a new table to the database. For this example, I’m going to add a table with the following columns: DATE, TIME, ADDRESS, LOCATION, TEMPERATURE and ACCEROMETER

  • DATE/TIME - This is the date and time the data is recorded
  • ADDRESS - This is the MAC of the SensorBug the message is captured from
  • LOCATION - A human readable string to indicate where the sensor is located
  • TEMPERATURE - This is the recorded temperature
  • ACCELE - This is the value of the accelerometer output, useful for recording sensor position (if enabled)


The command that does this is:
> USE SensorBug;
> CREATE TABLE data (date DATE, time TIME, address TINYTEXT, location TINYTEXT, temperature FLOAT, accele INT);

Now the database is ready, we can move on to setting up the sensorBugs.

Step 3: Setting Up the SensorBugs

The sensor bugs are pretty neat little devices. Unfortunately, the manufacturer only provided IOS app for programming it. Never the less, it is still possible to work with it if you only have an Android device.

First step, pair the device with a phone.
Without pairing the device, the SensorBug will not advertise data. I tried to see if I can do this directly with RaspberryPi, unfortunately, it seems like the Bluetooth LE driver on RaspberryPi is still experimental and contain bugs to prevent it from pairing with Bluetooth LE devices. Future version of the blueZ driver might fix this, but as the current writing, there is no way to pair the SensorBug with RaspberryPi.

Fortunately, we don’t need to pair the device to capture its advertised data. The only thing we need is a phone to configure the SensorBug. By default, the SensorBug will start advertise temperature data at 1s interval once it is paired with a device. For capture temperature data, that’s all that’s needed. If you plan to expand to use the position or light sensor, than configuration the device will be needed. For starter, we’ll pair the device and disconnect. This will be good enough for temperature capture purpose.

Start by pressing both buttons on the SensorBug. The blue/green LED will blink, which indicate it is powered on.
Press one of the button, the green LED should light up, indicate the power is on. If the green LED isn’t lit, press both button to try turn on the device again.
Press and hold one of the button till the blue LED start to blink. This will put the device into pair mode.
Go into Bluetooth configuration menu on the phone and look for the SensorBug device. Once it shows up, select it to pair with the device.

That’s it, now the SensorBug is powered and advertising the temperature data

Step 4: Installing the Bluetooth LE Python Wrapper

Next we need to install the library for python to talk to Bluetooth LE stack.

The instruction can be found here:
https://github.com/IanHarvey/bluepy

For Python 2.7, it is as simple as entering the following commands:

   $ sudo apt-get install python-pip libglib2.0-dev
$ sudo pip install bluepy

Step 5: Scan and Find Out the Address of the SensorBug

To find out the SensorBug MAC address, use this command:
$ sudo hcitool lescan

You should see output like:

EC:FE:7E:10:B1:92 (unknown)

If you have a lot of bluetooth LE devices around, it might be hard to find out which one you are talking to. You can try bluetoothctl which give more details:

$ sudo bluetoothctl
[bluetooth]# scan on [NEW] Device EC:FE:7E:10:B1:92 SensorBug10B192 [CHG] Device EC:FE:7E:10:B1:92 ManufacturerData Key: 0x0085 [CHG] Device EC:FE:7E:10:B1:92 ManufacturerData Value: 0x02 [CHG] Device EC:FE:7E:10:B1:92 ManufacturerData Value: 0x00 [CHG] Device EC:FE:7E:10:B1:92 ManufacturerData Value: 0x3c [CHG] Device EC:FE:7E:10:B1:92 ManufacturerData Value: 0x25 [CHG] Device EC:FE:7E:10:B1:92 ManufacturerData Value: 0x09 [CHG] Device EC:FE:7E:10:B1:92 ManufacturerData Value: 0x41 [CHG] Device EC:FE:7E:10:B1:92 ManufacturerData Value: 0x02 [CHG] Device EC:FE:7E:10:B1:92 ManufacturerData Value: 0x02 [CHG] Device EC:FE:7E:10:B1:92 ManufacturerData Value: 0x43 [CHG] Device EC:FE:7E:10:B1:92 ManufacturerData Value: 0x0b [CHG] Device EC:FE:7E:10:B1:92 ManufacturerData Value: 0x01 [CHG] Device EC:FE:7E:10:B1:92 ManufacturerData Value: 0x6f

Record the MAC address, this will need to be entered into the python script to filter out unwanted Bluetooth LE devices

Step 6: Add the Python Script

A copy of the Python script is available from:

https://drive.google.com/open?id=10vOeEAbS7mi_eXn_...

Here is the same file, take care of the indent when copying:

Also, update the MAC address in the python file to match the sensor address obtained from the scan result.

# This program is free software: you can redistribute it and/or modify

# it under the terms of the GNU General Public License as published by

# the Free Software Foundation, either version 3 of the License, or

# (at your option) any later version.

#

# This program is distributed in the hope that it will be useful,

# but WITHOUT ANY WARRANTY; without even the implied warranty of

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

# GNU General Public License for more details.

#

# You should have received a copy of the GNU General Public License

# along with this program. If not, see .

# bscan.py - Simple bluetooth LE scanner and data extractor

from bluepy.btle import Scanner, DefaultDelegate

import time

import pymysql

import struct

hostname = 'localhost'

username = 'datasrc'

password = 'datasrc000'

database = 'bleSensor'

#Enter the MAC address of the sensor from the lescan

SENSOR_ADDRESS = ["ec:fe:7e:10:b9:92", "ec:fe:7e:10:b9:93"]

SENSOR_LOCATION = ["Garage", "Exterior"]

class DecodeErrorException(Exception):

def __init__(self, value):

self.value = value

def __str__(self):

return repr(self.value)

class ScanDelegate(DefaultDelegate):

def __init__(self):

DefaultDelegate.__init__(self)

def handleDiscovery(self, dev, isNewDev, isNewData):

if isNewDev:

print "Discovered device", dev.addr

elif isNewData:

print "Received new data from", dev.addr

def doQueryInsert (conn, addr, loc, temp, accero):

#blesensor table is date, time, addr, location, temp, accero

cur = conn.cursor()

dostr = 'INSERT INTO data VALUES (CURRENT_DATE(), NOW(), %s, %s, %s, %s);'

cur.execute (dostr, (addr, loc, temp, accero))

conn.commit()

scanner = Scanner().withDelegate(ScanDelegate())

myConnection = pymysql.connect (host=hostname, user=username, passwd=password, db=database)

ManuDataHex = []

ReadLoop = True

try:

while (ReadLoop):

devices = scanner.scan(2.0)

ManuData = ""

for dev in devices:

entry = 0

AcceroData = 0

AcceroType = 0

TempData = 0

for saddr in SENSOR_ADDRESS:

entry += 1

if (dev.addr == saddr):

print "Device %s (%s), RSSI=%d dB" % (dev.addr, dev.addrType, dev.rssi)

CurrentDevAddr = saddr

CurrentDevLoc = SENSOR_LOCATION[entry-1]

for (adtype, desc, value) in dev.getScanData():

print " %s = %s" % (desc, value)

if (desc == "Manufacturer"):

ManuData = value

if (ManuData == ""):

print "No data received, end decoding"

continue

#print ManuData

for i, j in zip (ManuData[::2], ManuData[1::2]):

ManuDataHex.append(int(i+j, 16))

#Start decoding the raw Manufacturer data

if ((ManuDataHex[0] == 0x85) and (ManuDataHex[1] == 0x00)):

print "Header byte 0x0085 found"

else:

print "Header byte 0x0085 not found, decoding stop"

continue

#Skip Major/Minor

#Index 5 is 0x3c, indicate battery level and config #

if (ManuDataHex[4] == 0x3c):

BatteryLevel = ManuDataHex[5]

ConfigCounter = ManuDataHex[6]

idx = 7

#print "TotalLen: " + str(len(ManuDataHex))

while (idx < len(ManuDataHex)):

#print "Idx: " + str(idx)

#print "Data: " + hex(ManuDataHex[idx])

if (ManuDataHex[idx] == 0x41):

#Accerometer data

idx += 1

AcceleroType = ManuDataHex[idx]

AcceleroData = ManuDataHex[idx+1]

idx += 2

elif (ManuDataHex[idx] == 0x43):

#Temperature data

idx += 1

TempData = ManuDataHex[idx]

TempData += ManuDataHex[idx+1] * 0x100

TempData = TempData * 0.0625

idx += 2

else:

idx += 1

print "Device Address: " + CurrentDevAddr

print "Device Location: " + CurrentDevLoc

print "Battery Level: " + str(BatteryLevel) + "%"

print "Config Counter: " + str(ConfigCounter)

print "Accelero Data: " + hex(AcceleroType) + " " + hex(AcceleroData)

print "Temp Data: " + str(TempData)

doQueryInsert(myConnection, CurrentDevAddr, CurrentDevLoc, TempData, AcceleroData)

ReadLoop = False

except DecodeErrorException:

pass

Step 7: Test Out the Python Script

The script must be run in root, so:

$ sudo python bscan.py
Discovered device ec:6e:7e:10:b1:92 Device ec:6e:7e:10:b1:92 (public), RSSI=-80 dB Flags = 06 Incomplete 16b Services = 0a18 Manufacturer = 850002003c25094102024309016f Header byte 0x0085 found Device Address: ec:6e:7e:10:b1:92 Device Location: Garage Battery Level: 37% Config Counter: 9 Accero Data: 0x2 0x2 Temp Data: 16.5625

Step 8: Add the Python Scrip to the Crontab

The python script must be run in root, so if you want to capture the data automatically, it will need to be added to the root’s crontab. For this example, I run the script every 20 minutes

Use the command:

$ sudo crontab -e

# Edit this file to introduce tasks to be run by cron. # # Each task to run has to be defined through a single line # indicating with different fields when the task will be run # and what command to run for the task # # To define the time you can provide concrete values for # minute (m), hour (h), day of month (dom), month (mon), # and day of week (dow) or use '*' in these fields (for 'any').# # Notice that tasks will be started based on the cron's system # daemon's notion of time and timezones. # # Output of the crontab jobs (including errors) is sent through # email to the user the crontab file belongs to (unless redirected). # # For example, you can run a backup of all your user accounts # at 5 a.m every week with: # 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/ # # For more information see the manual pages of crontab(5) and cron(8) # # m h dom mon dow command 0 * * * * python /home/pi/bscan.py 20 * * * * python /home/pi/bscan.py 40 * * * * python /home/pi/bscan.py


That’s it. The python script will be run at regular interval and recode the output into the SQL database

Step 9: Extra: Configure the SensorBug for Position Sensing Output

It is possible to configure the SensorBug on Android for position sensing output
For position change sensing, so called Garage door.sensing, the SensorBug will detect if the device is standing upright or laying down flat.
When the device is flat, the value recorded is 0x20 while if the device is standing upright, the value is 0x02
It does not differentiate if the X or Y position is up, as long as Z axis is not up or down.

The easiest way to do this is to use LightBlue App. The SensorBug should show up in the scan menu.

Select the device you wish to configure, go to the GATT characteristics for Accelerometer configuration
UUID:9DC84838-7619-4F09-A1CE-DDCF63225B11

See image:

Write a new configuration string:

010d3f02020000002d00000002

Read back the configuration string to confirm the write.

This enables the accelerometer for position sensing.

Share

    Recommendations

    • Clocks Contest

      Clocks Contest
    • Creative Misuse Contest

      Creative Misuse Contest
    • Water Contest

      Water Contest

    4 Discussions

    How do you know what to write as the configuration string?

    Does these strings (

    010d3f02020000002d00000002) mean anything?

    I only know that the UUID ending with B11 is the config characteristic based on the sensorbug manual

    1 more answer

    The building of the configuration string is not very well documented in the data sheet. Though all the information required to build it is in there.

    You can find the link to the data sheet at:

    http://www.blehome.com/SensorBug%20Interface%20Spe...

    Looks like you are getting the string from my example to write into the Accelerometer characteristics.

    To form that string, see section 5.6

    The format of the string is in the form of:

    5.6.1.2 Value Structure
    typedef struct
    {

    uint8 enableSensor;

    accelSvcModeFlags_t modeFlags;

    accelSvcAxisFlags_t axisFlags;

    uint8 dataRate;

    uint8 notiDataRate;

    uint8 range;

    uint8 hpfCutOffFreq;

    uint8 alertMode;

    uint8 alertThreshold;

    uint8 alertDuration;

    uint16 reserved;

    uint8 appType;
    }accelSvcConfig_t;

    So, 1st byte is enable: set to 0x01

    2nd byte is is mode, it is set to binary 1011 = 0x0d (See structure for modeFlags)

    3rd byte is enable axisFlags, this is set to enable all axis.

    The best way to understand it is to read the old default value out than write it back with slight modification. The only change here is the modeFlags to enable ad data.

    Is it possible to get the accelerometer data in the form of acceleration values from x, y, and z axis? I use the BlueSense app to change the accelerometer mode from Garage Door to Vibration. These are the images on the app when the sensor is static and where it is shaken. I was hoping I could record these values in the MySQL database. Thank you in advance!

    Static.PNGShaken.PNG
    1 more answer

    Yes and no... It is possible to get the value, but the BLEbug won't send them out as broadcast message. The only way to get it is to connect to the device and read it out from register.

    Current version of the BLE driver on RPi has bugs which prevented this, but I was able to do it on Android with LightBlue App. Until there is a way to pair with BLE device on RPi... it can't be done yet.