BenSunnyField wrote: ↑Sunday 15 July 2018 13:29
@Franzelare I see you got the Medisana BU530 working, would you like to share that code?
It is exactly what I want to do so your code would be very welcome.
sorry for the late reply, did not read the forum for a while.
I have 1 file scanning for both devices at the same time, and when detecting 1, connecting and processing.
Issue is that both measurements should be separated by 2 minutes or so, otherwise the second is missing sometimes.
I did not make a file that adds the new devices the first time, so when you run this the first time without the ini file, domoticz will add id that you manually have to fill in into the BU530.domoticz.ini
Medisana.py
Code: Select all
from __future__ import print_function
import pygatt.backends
import logging
from ConfigParser import SafeConfigParser
import time
import subprocess
from struct import *
from binascii import hexlify
from BU530decode import *
from BU530domoticz import *
from BS440decode import *
from BS440domoticz import *
def processIndication1(handle, values):
'''
Indication handler
receives indication and stores values into result Dict
(see medisanaBLE for Dict definition)
handle: byte
value: bytearray
'''
if handle == 0x39:
result = decodeBlood(handle, values)
if result not in blooddata:
log.info(str(result))
blooddata.append(result)
else:
log.info('Duplicate blooddata record')
else:
log.debug('Unhandled Indication encountered')
def processIndication2(handle, values):
'''
Indication handler
receives indication and stores values into result Dict
(see medisanaBLE for Dict definition)
handle: byte
value: bytearray
'''
if handle == 0x25:
result = decodePerson(handle, values)
if result not in persondata:
log.info(str(result))
persondata.append(result)
else:
log.info('Duplicate persondata record')
elif handle == 0x1b:
result = decodeWeight(handle, values)
if result not in weightdata:
log.info(str(result))
weightdata.append(result)
else:
log.info('Duplicate weightdata record')
elif handle == 0x1e:
result = decodeBody(handle, values)
if result not in bodydata:
log.info(str(result))
bodydata.append(result)
else:
log.info('Duplicate bodydata record')
else:
log.debug('Unhandled Indication encountered')
def wait_for_device(devname1, devname2):
found = False
found1 = False
found2 = False
while not found:
try:
devicescan=adapter.scan()
found1 = [device for device in devicescan
if devname1 in (device['name'] or '')]
if found1:
found = found1
found2 = [device for device in devicescan
if devname2 in (device['name'] or '')]
if found2:
found = found2
except pygatt.exceptions.BLEError:
adapter.reset()
return(found1, found2)
def connect_device1(address):
device1_connected = False
tries = 3
device1 = None
while not device1_connected and tries > 0:
try:
device1 = adapter.connect(address, 5, pygatt.BLEAddressType.public)
device1_connected = True
except pygatt.exceptions.NotConnectedError:
tries -= 1
return device1
def connect_device2(address):
device2_connected = False
tries = 3
device2 = None
while not device2_connected and tries > 0:
try:
device2 = adapter.connect(address, 5, pygatt.BLEAddressType.random)
device2_connected = True
except pygatt.exceptions.NotConnectedError:
tries -= 1
return device2
def init_ble_mode():
p = subprocess.Popen("sudo btmgmt le on", stdout=subprocess.PIPE,
shell=True)
(output, err) = p.communicate()
if not err:
log.info(output)
return True
else:
log.info(err)
return False
'''
Main program loop
'''
config = SafeConfigParser()
config.read('/home/pi/medisana/Medisana.ini')
# set up logging
numeric_level = getattr(logging,
config.get('Program', 'loglevel').upper(),
None)
if not isinstance(numeric_level, int):
raise ValueError('Invalid log level: %s' % loglevel)
logging.basicConfig(level=numeric_level,
format='%(asctime)s %(levelname)-8s %(funcName)s %(message)s',
datefmt='%a, %d %b %Y %H:%M:%S',
filename=config.get('Program', 'logfile'),
filemode='w')
log = logging.getLogger(__name__)
ble_address1 = config.get('Blood', 'ble_address1')
device1_name = config.get('Blood', 'device1_name')
ble_address2 = config.get('Scale', 'ble_address2')
device2_name = config.get('Scale', 'device2_name')
'''
Start BLE comms and run that forever
'''
log.info('Medisana Started')
if not init_ble_mode():
sys.exit()
adapter = pygatt.backends.GATTToolBackend()
adapter.start()
while True:
devicestatus1, devicestatus2 = wait_for_device(device1_name, device2_name)
if devicestatus1:
device1 = connect_device1(ble_address1)
if device1:
blooddata = []
continue_comms = True
'''
subscribe to characteristics and have processIndication
process the data received.
'''
try:
device1.subscribe('00002a35-0000-1000-8000-00805f9b34fb',
callback=processIndication1,
indication=True)
except pygatt.exceptions.NotConnectedError:
continue_comms = False
if continue_comms:
try:
device1.char_write_handle(0x3a, [0x02, 0x00],
wait_for_response=True)
except pygatt.exceptions.NotificationTimeout:
pass
except pygatt.exceptions.NotConnectedError:
continue_comms = False
if continue_comms:
log.info('Waiting for notifications for another 30 seconds')
time.sleep(30)
device1.disconnect()
log.info('Done receiving data from blood pressure sensor')
# process data if all received well
if blooddata:
# print("blood data start sending to domoticz")
UpdateDomoticzBlood(config, blooddata)
else:
log.error('Unreliable data received. Unable to process')
if devicestatus2:
device2 = connect_device2(ble_address2)
if device2:
persondata = []
weightdata = []
bodydata = []
continue_comms = True
'''
subscribe to characteristics and have processIndication
process the data received.
'''
try:
device2.subscribe('00008a22-0000-1000-8000-00805f9b34fb',
callback=processIndication2,
indication=True)
device2.subscribe('00008a21-0000-1000-8000-00805f9b34fb',
callback=processIndication2,
indication=True)
device2.subscribe('00008a82-0000-1000-8000-00805f9b34fb',
callback=processIndication2,
indication=True)
except pygatt.exceptions.NotConnectedError:
continue_comms = False
'''
Send the unix timestamp in little endian order preceded by 02 as
bytearray to handle 0x23. This will resync the scale's RTC.
While waiting for a response notification, which will never
arrive, the scale will emit 30 Indications on 0x1b and 0x1e each.
'''
if continue_comms:
timestamp = bytearray(pack('<I', int(time.time())))
timestamp.insert(0, 2)
try:
device2.char_write_handle(0x23, timestamp,
wait_for_response=True)
except pygatt.exceptions.NotificationTimeout:
pass
except pygatt.exceptions.NotConnectedError:
continue_comms = False
if continue_comms:
log.info('Waiting for notifications for another 30 seconds')
time.sleep(120)
device2.disconnect()
log.info('Done receiving data from scale')
# process data if all received well
if persondata and weightdata and bodydata:
# Sort scale output by timestamp to retrieve most recent three results
weightdatasorted = sorted(weightdata, key=lambda k: k['timestamp'], reverse=True)
bodydatasorted = sorted(bodydata, key=lambda k: k['timestamp'], reverse=True)
UpdateDomoticz(config, persondata, weightdatasorted, bodydatasorted)
else:
log.error('Unreliable data received. Unable to process')
BU530.domoticz.ini
Code: Select all
[Person1]
systolic_id = 184
diastolic_id = 185
indicator_id = 186
pulserate_id = 187
arrhythmia_id = 188
BU530decode.py
Code: Select all
from struct import *
import sys
'''
Reads Medisana BU530 blood pressure sensor hex Indication and decodes values from hex data string and returns a dictionary with decoded data
'''
def decodeBlood(handle, values):
'''
decodeBlood
Handle: 0x39
Byte[0] = 0x1e
Returns:
valid (True, False)
timestamp (unix timestamp date and time of measurement)
person (1)
SyS = mmHg
Dia = mmHg
Pull = per min
Indicator =
Arrhythmia =
'''
data = unpack('BBxBxBxxBBBBBxBxxBx', bytes(values[0:19]))
# print(data)
retDict = {}
retDict["valid"] = (handle == 0x39 and data[0] == 0x1e)
retDict["SyS"] = data[1]
retDict["Dia"] = data[2]
retDict["Indicator"] = data[3]
retDict["Date"] = (data[4] , data[5] , data[6])
retDict["Time"] = (data[7] , data[8])
retDict["Pull"] = data[9]
retDict["Arrhythmia"] = data[10]
return retDict
BU530domoticz.py
Code: Select all
'''
BU530domoticz.py
Update blood pressure monitor value to Domoticz home automation system
'''
import urllib
import base64
import logging
import time
import traceback
import json
from ConfigParser import *
configDomoticz = SafeConfigParser()
configDomoticz.read('/home/pi/medisana/BU530.domoticz.ini')
def UpdateDomoticzBlood(config, persondata):
log = logging.getLogger(__name__)
domoticzurl = config.get('Domoticz', 'domoticz_url')
url_blood = 'http://%s/json.htm?type=command¶m=udevice&idx=%s&nvalue=0&svalue=%s'
def open_url(url):
log.debug('Opening url: %s' % (url))
try:
response = urllib.urlopen(url)
except Exception, e:
log.error('Failed to send data to Domoticz (%s)' % (url))
return '{}'
return response
try:
log_update = 'Updating Domoticz for user preson 1 at index %s with '
# print("start to send data")
# send collected values to domoticz
pressureSyS = persondata[0]['SyS']
# print("collected SyS")
hardwareid = configDomoticz.get('Person1', 'systolic_id')
# print("collected hardwareid")
log.info((log_update+'pressure %s') % (hardwareid, pressureSyS))
# print("write to log")
open_url(url_blood % (domoticzurl, hardwareid, pressureSyS))
# print("send url")
# print("next values")
pressureDia = persondata[0]['Dia']
hardwareid = configDomoticz.get('Person1', 'diastolic_id')
log.info((log_update+'pressure %s') % (hardwareid, pressureDia))
open_url(url_blood % (domoticzurl, hardwareid, pressureDia))
pressureInd = persondata[0]['Indicator']
hardwareid = configDomoticz.get('Person1', 'indicator_id')
log.info((log_update+'pressure %s') % (hardwareid, pressureInd))
open_url(url_blood % (domoticzurl, hardwareid, pressureInd))
pulse = persondata[0]['Pull']
hardwareid = configDomoticz.get('Person1', 'pulserate_id')
log.info((log_update+'pulse %s') % (hardwareid, pulse))
open_url(url_blood % (domoticzurl, hardwareid, pulse))
pulseAr = persondata[0]['Arrhythmia']
hardwareid = configDomoticz.get('Person1', 'arrhythmia_id')
log.info((log_update+'pulse %s') % (hardwareid, pulseAr))
open_url(url_blood % (domoticzurl, hardwareid, pulseAr))
log.info('Domoticz succesfully updated')
except:
log.error('Unable to update Domoticz: Error sending data.')