This is a continuation of # 1 that notifies LINE of body temperature from the BLE thermometer with Raspberry Pi. This time we will implement it in python. GATT with python. The BLE library uses bluepy.
The BLE library uses bluepy. Install bluepy.
$ sudo apt-get update
$ sudo apt-get -y upgrade
$ sudo apt-get -y install python3-pip libglib2.0-dev
$ sudo pip3 install bluepy
>Successfully installed bluepy-1.3.0
$ reboot
Talk to BLE device with bluepy.
First, scan the advertisement packet.
from bluepy import btle
scanner = btle.Scanner(0)
devices = scanner.scan(3.0)
for device in devices:
print(f'BLE Address:{device.addr}')
for (adTypeCode, description, valueText) in device.getScanData():
print(f'- {description}:{valueText}')
The code is simple, but it has traps and requires ** sudo privileges ** to run scans with bluepy. If you do not have enough permissions, you will get an error with scanner.scan ()
.
Put the thermometer in ** pairing mode ** and then run py.
$ sudo python3 bluepyadv.py
BLE Address:18:93:d7:76:c9:b8
- Flags:05
- Incomplete 16b Services:00001809-0000-1000-8000-00805f9b34fb
- 0x12:5000a000
- Tx Power:00
- Complete Local Name:A&D_UT201BLE_76C9B8
BLE Address:6f:1a:a5:25:58:55
- Flags:06
- Manufacturer:4c001005571cf70bed
BLE Address: 18: 93: d7: 76: c9: b8
is the information of the thermometer device. It has been scanned properly.
Get a list of services by connecting to a thermometer.
The BLE address is like a MAC address, so it depends on the device. Please specify the BLE address of your device.
The BLE address of my thermometer is 18: 93: D7: 76: C9: B8
.
from bluepy import btle
BLE_ADDRESS="18:93:D7:76:C9:B8"
peripheral = btle.Peripheral()
peripheral.connect(BLE_ADDRESS)
for service in peripheral.getServices():
print(f'Service UUID:{service.uuid}')
for characteristic in service.getCharacteristics():
print(f'- Characteristic UUID:{characteristic.uuid} , Handle:{hex(characteristic.getHandle())} , Property:{characteristic.propertiesToString()}')
Measure with a thermometer and execute py when it is in ** transmission mode **. Since it connects, it is in transmission mode, not pairing mode. You don't have to have sudo privileges as before.
$ python3 bluepyconnect.py
Service UUID:00001800-0000-1000-8000-00805f9b34fb
- Characteristic UUID:00002a00-0000-1000-8000-00805f9b34fb , Handle:0x3 , Property:READ
- Characteristic UUID:00002a01-0000-1000-8000-00805f9b34fb , Handle:0x5 , Property:READ
- Characteristic UUID:00002a02-0000-1000-8000-00805f9b34fb , Handle:0x7 , Property:READ WRITE
- Characteristic UUID:00002a03-0000-1000-8000-00805f9b34fb , Handle:0x9 , Property:WRITE
- Characteristic UUID:00002a04-0000-1000-8000-00805f9b34fb , Handle:0xb , Property:READ
Service UUID:00001801-0000-1000-8000-00805f9b34fb
- Characteristic UUID:00002a05-0000-1000-8000-00805f9b34fb , Handle:0xe , Property:INDICATE
Service UUID:00001809-0000-1000-8000-00805f9b34fb
- Characteristic UUID:00002a1c-0000-1000-8000-00805f9b34fb , Handle:0x12 , Property:INDICATE
- Characteristic UUID:00002a1d-0000-1000-8000-00805f9b34fb , Handle:0x15 , Property:READ
- Characteristic UUID:00002a08-0000-1000-8000-00805f9b34fb , Handle:0x17 , Property:READ WRITE
Service UUID:0000180a-0000-1000-8000-00805f9b34fb
- Characteristic UUID:00002a29-0000-1000-8000-00805f9b34fb , Handle:0x1a , Property:READ
- Characteristic UUID:00002a24-0000-1000-8000-00805f9b34fb , Handle:0x1c , Property:READ
- Characteristic UUID:00002a25-0000-1000-8000-00805f9b34fb , Handle:0x1e , Property:READ
- Characteristic UUID:00002a27-0000-1000-8000-00805f9b34fb , Handle:0x20 , Property:READ
- Characteristic UUID:00002a26-0000-1000-8000-00805f9b34fb , Handle:0x22 , Property:READ
- Characteristic UUID:00002a28-0000-1000-8000-00805f9b34fb , Handle:0x24 , Property:READ
- Characteristic UUID:00002a23-0000-1000-8000-00805f9b34fb , Handle:0x26 , Property:READ
- Characteristic UUID:00002a2a-0000-1000-8000-00805f9b34fb , Handle:0x28 , Property:READ
Service UUID:0000180f-0000-1000-8000-00805f9b34fb
- Characteristic UUID:00002a19-0000-1000-8000-00805f9b34fb , Handle:0x2b , Property:READ
Service UUID:233bf000-5a34-1b6d-975c-000d5690abe4
- Characteristic UUID:233bf001-5a34-1b6d-975c-000d5690abe4 , Handle:0x2e , Property:READ WRITE
Try to receive the measured data.
import sys
from bluepy import btle
BLE_ADDRESS="18:93:D7:76:C9:B8"
SERVICE_UUID="00001809-0000-1000-8000-00805f9b34fb"
class MyDelegate(btle.DefaultDelegate):
def __init__(self):
btle.DefaultDelegate.__init__(self)
def handleNotification(self, cHandle, data):
print("Handle = " + hex(cHandle))
print("- Flags = " + hex(data[0]))
print("- C1:Temperature Measurement Value(Celsius) = " + hex(data[1])+":"+hex(data[2])+":"+hex(data[3])+":"+hex(data[4]))
print("- C3:Time Stamp = " + hex(data[5])+":"+hex(data[6])+":"+hex(data[7])+":"+hex(data[8])+":"+hex(data[9])+":"+hex(data[10])+":"+hex(data[11]))
if __name__ == '__main__':
print("Start")
print("Connecting Wait...")
try:
peripheral = btle.Peripheral()
peripheral.connect(BLE_ADDRESS)
except:
print("connect Error!")
sys.exit(0)
print("Connected!")
peripheral.withDelegate(MyDelegate())
# Enable Indicate
peripheral.writeCharacteristic(0x13, b'\x02\x00', True)
#Wait for notification
print("Indicate Wait...")
try:
TIMEOUT = 3.0
while True:
if peripheral.waitForNotifications(TIMEOUT):
# handleNotification()Was called
continue
# handleNotification()Was not called even after waiting for TIME OUT seconds
print("wait...")
except:
#Come here when disconnected
print("except!")
print("end")
The point is that the event handler is registered with peripheral.withDelegate (MyDelegate ())
and peripheral.writeCharacteristic (0x13, b'\ x02 \ x00', True)
is done.
** Indicate starts when you set 0200 to the characteristic of handle 0x13 ** It is as in Previous explanation. The handleNotification ()
event is fired when data is received. The received data is jammed in data.
Caution The handle value of 0x13 may differ depending on the device (unconfirmed), so check the handle value with gatttool and specify the value of your device.
pi@raspberrypi:~/work/git/BLEThermometer/src $ python3 bluepygatt.py
Start
Connecting Wait...
Connected!
Indicate Wait...
Handle = 0x12
- Flags = 0x6
- C1:Temperature Measurement Value(Celsius) = 0x73:0x1:0x0:0xff
- C3:Time Stamp = 0xe4:0x7:0x5:0x4:0xa:0x7:0x2f
wait...
wait...
except!
end
In this execution log
--Body temperature = 0x73: 0x1: 0x0: 0xff --Timestamp = xe4: 0x7: 0x5: 0x4: 0xa: 0x7: 0x2f
You can see that it was removed.
I found out earlier that "body temperature = 0x73: 0x1: 0x0: 0xff", but this 4-byte data is a binary in ** IEEE 11073 32bit float ** format. I couldn't find an easy way to convert an IEEE 11073 32bit float binary to a float type variable in python. I couldn't help it, so I made it myself.
import numpy as np
def to_float_from_11073_32bit_float(data):
tmp = int.from_bytes(data,'little')
uint32val = np.array([tmp],dtype=np.uint32)
#Find the mantissa(0-24bit)
tmp = bin(uint32val[0] & 0xffffff)
mantissa = int(tmp,0)
#Find the index part(25-32bit)
tmp = int(data[3])
tmp2 = np.array([tmp],dtype=np.byte)
exponent = int(tmp2[0])
#Calculate real numbers
ret = round(mantissa * pow(10,exponent),1)
return ret
# 37.1
temp = to_float_from_11073_32bit_float(b'\x73\x01\x00\xff')
print("temp = " + str(temp) + " C")
I will do it.
$ python3 to_float_from_11073_32bit_float.py
temp = 37.1 C
0x73: 0x1: 0x0: 0xff
is now ** 37.1 **. Very good.
The time stamp is 7 bytes. Temperature Measurement C3 format.
def to_date_time(data):
tmp = data[0:2]
yyyy = int.from_bytes(tmp,'little')
mm = int(data[2])
dd = int(data[3])
hh = int(data[4])
min = int(data[5])
ss = int(data[6])
strdate_time=str(yyyy)+"/"+str(mm)+"/"+str(dd)+" "+str(hh)+":"+str(min)+":"+str(ss)
return strdate_time
# 2020/5/4 10:07:47
date_time = to_date_time(b'\xe4\x07\x05\x04\x0a\x07\x2f')
print("date_time = " + date_time)
I will do it.
$ python3 to_date_time.py
date_time = 2020/5/4 10:7:47
So far, you can do the same thing with bulepy that you did with gatttool last time.
By the way, I had to notify the measurement result on LINE. LINE notifications are easy with LINE Notify.
Implement LINE notification by referring to the following article.
-Create a mechanism to record attendance and departure with Raspberry Pi, MAMORIO and LINE
The Token written in this source is invalid, so issue it and use a valid one.
import requests
#LINE notification
def send_notify(access_token,comment):
if( len(access_token) <= 0 ):
return
url = "https://notify-api.line.me/api/notify"
headers = {'Authorization': 'Bearer ' + access_token}
message = comment
payload = {'message': message}
print(message)
requests.post(url, headers=headers, params=payload,)
send_notify("token xxx","comment")
I will do it.
$ python3 sendline.py
comment
Kita.
From here is the production. It's easy because all you have to do is do what you've looked up so far in order.
The python source code is a bit long.
#You need sudo privileges to run this py.
#Scanner without permission.scan()Will result in an error.
from bluepy import btle
import sys
import time
import to_float_from_11073_32bit_float as tofl
import to_date_time as todt
import sendline as line
# define
SERVICE_UUID="00001809-0000-1000-8000-00805f9b34fb"
# global
BLE_ADDRESS="xx:xx:xx:xx:xx:xx"
TOKEN = "this is token"
def scan():
try:
scanner = btle.Scanner(0)
devices = scanner.scan(3.0)
for device in devices:
print(f'SCAN BLE_ADDR:{device.addr}')
if(device.addr.lower()==BLE_ADDRESS.lower()):
print("Find!")
return True
except:
print("scan Error!")
return False
print("---")
return False
class MyDelegate(btle.DefaultDelegate):
def __init__(self):
btle.DefaultDelegate.__init__(self)
def handleNotification(self, cHandle, data):
print("Indicate Handle = " + hex(cHandle))
print("Flags = " + hex(data[0]))
print("C1:Temperature Measurement Value(Celsius) = " + hex(data[1])+":"+hex(data[2])+":"+hex(data[3])+":"+hex(data[4]))
print("C3:Time Stamp = " + hex(data[5])+":"+hex(data[6])+":"+hex(data[7])+":"+hex(data[8])+":"+hex(data[9])+":"+hex(data[10])+":"+hex(data[11]))
temp = tofl.to_float_from_11073_32bit_float(data[1:5])
print("temp = " + str(temp))
timestamp = todt.to_date_time(data[5:12])
print("timestamp = " + timestamp)
line.send_notify(TOKEN,str(temp)+" C "+timestamp)
def main():
#
# Scan
#
print("<Scan Start>")
while True:
scanresult = scan()
if( scanresult==True):
break
time.sleep(3)
print("Scan End")
#
# Connect
#
print("Connect Start")
try:
peripheral = btle.Peripheral()
peripheral.connect(BLE_ADDRESS)
except:
print("connect Error!")
sys.exit(0)
print("Connected!")
service = peripheral.getServiceByUUID(SERVICE_UUID)
peripheral.withDelegate(MyDelegate())
# Enable Indicate
peripheral.writeCharacteristic(0x0013, b'\x02\x00', True)
#Wait for notification
print("Indicate Wait...")
try:
TIMEOUT = 3.0
while True:
if peripheral.waitForNotifications(TIMEOUT):
# handleNotification()Was called
continue
# handleNotification()Was not called even after waiting for TIME OUT seconds
print("wait...")
except:
print("except!")
print("<end>")
if __name__ == '__main__':
print(sys.argv[0])
#global TOKEN
TOKEN = sys.argv[1]
print("token = " + TOKEN)
#gloval BLE_ADDRESS
BLE_ADDRESS = sys.argv[2]
print("BLE device = " + BLE_ADDRESS)
while True:
main()
time.sleep(3)
When you run py, the scan will start and you will measure your body temperature. When the measurement is completed, it will be automatically received and the body temperature and time stamp (measurement date and time) will be notified to LINE.
$ sudo python3 bluepythermo.py [token] 18:93:D7:76:C9:B8
bluepythermo.py
token = [token]
BLE device = 18:93:D7:76:C9:B8
<Scan Start>
SCAN BLE_ADDR:18:93:d7:76:c9:b8
Find!
Scan End
Connect Start
Connected!
Indicate Wait...
Indicate Handle = 0x12
Flags = 0x6
C1:Temperature Measurement Value(Celsius) = 0x72:0x1:0x0:0xff
C3:Time Stamp = 0xe4:0x7:0x5:0x4:0xa:0x16:0x1c
temp = 37.0
timestamp = 2020/5/4 10:22:28
37.0 C 2020/5/4 10:22:28
wait...
wait...
except!
<end>
I got a notification on LINE properly.
If you put a Raspberry Pi in the room and run py, it will operate as expected by repeating scan → LINE notification. If you install a thermometer and Raspberry Pi, you can also notify the body temperature of a person far away, so you can use it to check your survival.
** This is definitely a body temperature logging! ** **
I can't buy a thermometer or Raspberry Pi right now ...