0.1.1
This commit is contained in:
parent
22dbb9e6d1
commit
c47614f196
|
@ -22,7 +22,9 @@ import binascii
|
|||
import time
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
from bluepy import btle
|
||||
from bluepy.btle import Scanner, BTLEDisconnectError, BTLEManagementError, DefaultDelegate
|
||||
import paho.mqtt.client as mqtt
|
||||
from datetime import datetime
|
||||
|
||||
|
@ -38,6 +40,7 @@ MQTT_TIMEOUT = int(os.getenv('MQTT_TIMEOUT', 60))
|
|||
MQTT_PREFIX = os.getenv('MQTT_PREFIX', 'miscale')
|
||||
TIME_INTERVAL = int(os.getenv('TIME_INTERVAL', 30))
|
||||
OLD_MEASURE = ''
|
||||
MQTT_CONNECTED = False
|
||||
|
||||
# User Variables...
|
||||
USER1_GT = int(os.getenv('USER1_GT', '70')) # If the weight is greater than this number, we'll assume that we're weighing User #1
|
||||
|
@ -59,125 +62,158 @@ USER3_DOB = os.getenv('USER3_DOB', '1988-01-01') # DOB (in yyyy-mm-dd format)
|
|||
|
||||
|
||||
class ScanProcessor():
|
||||
def GetAge(self, d1):
|
||||
d1 = datetime.strptime(d1, "%Y-%m-%d")
|
||||
d2 = datetime.strptime(datetime.today().strftime('%Y-%m-%d'),'%Y-%m-%d')
|
||||
return abs((d2 - d1).days)/365
|
||||
def GetAge(self, d1):
|
||||
d1 = datetime.strptime(d1, "%Y-%m-%d")
|
||||
d2 = datetime.strptime(datetime.today().strftime('%Y-%m-%d'),'%Y-%m-%d')
|
||||
return abs((d2 - d1).days)/365
|
||||
|
||||
def __init__(self):
|
||||
self.mqtt_client = None
|
||||
self.connected = False
|
||||
self._start_client()
|
||||
def __init__(self):
|
||||
global MQTT_CONNECTED
|
||||
DefaultDelegate.__init__(self)
|
||||
if not MQTT_CONNECTED:
|
||||
self.mqtt_client = None
|
||||
self._start_client()
|
||||
|
||||
|
||||
def handleDiscovery(self, dev, isNewDev, isNewData):
|
||||
global OLD_MEASURE
|
||||
if dev.addr == MISCALE_MAC.lower() and isNewDev:
|
||||
for (sdid, desc, data) in dev.getScanData():
|
||||
### Xiaomi V1 Scale ###
|
||||
if data.startswith('1d18') and sdid == 22:
|
||||
measunit = data[4:6]
|
||||
measured = int((data[8:10] + data[6:8]), 16) * 0.01
|
||||
unit = ''
|
||||
if measunit.startswith(('03', 'b3')): unit = 'lbs'
|
||||
if measunit.startswith(('12', 'b2')): unit = 'jin'
|
||||
if measunit.startswith(('22', 'a2')): unit = 'kg' ; measured = measured / 2
|
||||
if unit:
|
||||
if OLD_MEASURE != round(measured, 2):
|
||||
print('')
|
||||
self._publish(round(measured, 2), unit, str(datetime.today().strftime('%Y-%m-%d-%H:%M:%S')), "", "")
|
||||
OLD_MEASURE = round(measured, 2)
|
||||
def handleDiscovery(self, dev, isNewDev, isNewData):
|
||||
global OLD_MEASURE
|
||||
if dev.addr == MISCALE_MAC.lower() and isNewDev:
|
||||
for (sdid, desc, data) in dev.getScanData():
|
||||
### Xiaomi V1 Scale ###
|
||||
if data.startswith('1d18') and sdid == 22:
|
||||
measunit = data[4:6]
|
||||
measured = int((data[8:10] + data[6:8]), 16) * 0.01
|
||||
unit = ''
|
||||
if measunit.startswith(('03', 'b3')): unit = 'lbs'
|
||||
if measunit.startswith(('12', 'b2')): unit = 'jin'
|
||||
if measunit.startswith(('22', 'a2')): unit = 'kg' ; measured = measured / 2
|
||||
if unit:
|
||||
if OLD_MEASURE != round(measured, 2):
|
||||
print('')
|
||||
self._publish(round(measured, 2), unit, str(datetime.today().strftime('%Y-%m-%d-%H:%M:%S')), "", "")
|
||||
OLD_MEASURE = round(measured, 2)
|
||||
|
||||
### Xiaomi V2 Scale ###
|
||||
if data.startswith('1b18') and sdid == 22:
|
||||
data2 = bytes.fromhex(data[4:])
|
||||
ctrlByte1 = data2[1]
|
||||
isStabilized = ctrlByte1 & (1<<5)
|
||||
hasImpedance = ctrlByte1 & (1<<1)
|
||||
### Xiaomi V2 Scale ###
|
||||
if data.startswith('1b18') and sdid == 22:
|
||||
data2 = bytes.fromhex(data[4:])
|
||||
ctrlByte1 = data2[1]
|
||||
isStabilized = ctrlByte1 & (1<<5)
|
||||
hasImpedance = ctrlByte1 & (1<<1)
|
||||
|
||||
measunit = data[4:6]
|
||||
measured = int((data[28:30] + data[26:28]), 16) * 0.01
|
||||
unit = ''
|
||||
if measunit == "03": unit = 'lbs'
|
||||
if measunit == "02": unit = 'kg' ; measured = measured / 2
|
||||
#mitdatetime = datetime.strptime(str(int((data[10:12] + data[8:10]), 16)) + " " + str(int((data[12:14]), 16)) +" "+ str(int((data[14:16]), 16)) +" "+ str(int((data[16:18]), 16)) +" "+ str(int((data[18:20]), 16)) +" "+ str(int((data[20:22]), 16)), "%Y %m %d %H %M %S")
|
||||
miimpedance = str(int((data[24:26] + data[22:24]), 16))
|
||||
if unit and isStabilized:
|
||||
if OLD_MEASURE != round(measured, 2) + int(miimpedance):
|
||||
print('')
|
||||
self._publish(round(measured, 2), unit, str(datetime.today().strftime('%Y-%m-%d-%H:%M:%S')), hasImpedance, miimpedance)
|
||||
OLD_MEASURE = round(measured, 2) + int(miimpedance)
|
||||
measunit = data[4:6]
|
||||
measured = int((data[28:30] + data[26:28]), 16) * 0.01
|
||||
unit = ''
|
||||
if measunit == "03": unit = 'lbs'
|
||||
if measunit == "02": unit = 'kg' ; measured = measured / 2
|
||||
#mitdatetime = datetime.strptime(str(int((data[10:12] + data[8:10]), 16)) + " " + str(int((data[12:14]), 16)) +" "+ str(int((data[14:16]), 16)) +" "+ str(int((data[16:18]), 16)) +" "+ str(int((data[18:20]), 16)) +" "+ str(int((data[20:22]), 16)), "%Y %m %d %H %M %S")
|
||||
miimpedance = str(int((data[24:26] + data[22:24]), 16))
|
||||
if unit and isStabilized:
|
||||
if OLD_MEASURE != round(measured, 2) + int(miimpedance):
|
||||
print('')
|
||||
self._publish(round(measured, 2), unit, str(datetime.today().strftime('%Y-%m-%d-%H:%M:%S')), hasImpedance, miimpedance)
|
||||
OLD_MEASURE = round(measured, 2) + int(miimpedance)
|
||||
|
||||
if not dev.scanData:
|
||||
print ('\t(no data)')
|
||||
if not dev.scanData:
|
||||
print ('\t(no data)')
|
||||
|
||||
def _start_client(self):
|
||||
self.mqtt_client = mqtt.Client()
|
||||
self.mqtt_client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
|
||||
def _start_client(self):
|
||||
global MQTT_CONNECTED
|
||||
self.mqtt_client = mqtt.Client()
|
||||
self.mqtt_client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
|
||||
|
||||
def _on_connect(client, _, flags, return_code):
|
||||
self.connected = True
|
||||
sys.stdout.write("MQTT connection: %s\n" % mqtt.connack_string(return_code))
|
||||
def _on_connect(client, _, flags, return_code):
|
||||
global MQTT_CONNECTED
|
||||
MQTT_CONNECTED = True
|
||||
loop_flag = 0
|
||||
sys.stdout.write("MQTT connection: %s\n" % mqtt.connack_string(return_code))
|
||||
|
||||
self.mqtt_client.on_connect = _on_connect
|
||||
self.mqtt_client.connect(MQTT_HOST, MQTT_PORT, MQTT_TIMEOUT)
|
||||
self.mqtt_client.loop_start()
|
||||
self.mqtt_client.on_connect = _on_connect
|
||||
self.mqtt_client.connect(MQTT_HOST, MQTT_PORT, MQTT_TIMEOUT)
|
||||
self.mqtt_client.loop_start()
|
||||
|
||||
def _publish(self, weight, unit, mitdatetime, hasImpedance, miimpedance):
|
||||
if not self.connected:
|
||||
sys.stderr.write('Not connected to MQTT server\n')
|
||||
exit()
|
||||
if int(weight) > USER1_GT:
|
||||
user = USER1_NAME
|
||||
height = USER1_HEIGHT
|
||||
age = self.GetAge(USER1_DOB)
|
||||
sex = USER1_SEX
|
||||
elif int(weight) < USER2_LT:
|
||||
user = USER2_NAME
|
||||
height = USER2_HEIGHT
|
||||
age = self.GetAge(USER2_DOB)
|
||||
sex = USER2_SEX
|
||||
else:
|
||||
user = USER3_NAME
|
||||
height = USER3_HEIGHT
|
||||
age = self.GetAge(USER3_DOB)
|
||||
sex = USER3_SEX
|
||||
lib = Xiaomi_Scale_Body_Metrics.bodyMetrics(weight, height, age, sex, 0)
|
||||
message = '{'
|
||||
message += '"Weight":"' + "{:.2f}".format(weight) + '"'
|
||||
message += ',"BMI":"' + "{:.2f}".format(lib.getBMI()) + '"'
|
||||
message += ',"Basal Metabolism":"' + "{:.2f}".format(lib.getBMR()) + '"'
|
||||
message += ',"Visceral Fat":"' + "{:.2f}".format(lib.getVisceralFat()) + '"'
|
||||
while not MQTT_CONNECTED: # wait for MQTT connecting Ack
|
||||
time.sleep(.01)
|
||||
|
||||
if hasImpedance:
|
||||
lib = Xiaomi_Scale_Body_Metrics.bodyMetrics(weight, height, age, sex, int(miimpedance))
|
||||
message += ',"Lean Body Mass":"' + "{:.2f}".format(lib.getLBMCoefficient()) + '"'
|
||||
message += ',"Body Fat":"' + "{:.2f}".format(lib.getFatPercentage()) + '"'
|
||||
message += ',"Water":"' + "{:.2f}".format(lib.getWaterPercentage()) + '"'
|
||||
message += ',"Bone Mass":"' + "{:.2f}".format(lib.getBoneMass()) + '"'
|
||||
message += ',"Muscle Mass":"' + "{:.2f}".format(lib.getMuscleMass()) + '"'
|
||||
message += ',"Protein":"' + "{:.2f}".format(lib.getProteinPercentage()) + '"'
|
||||
#message += ',"Body Type":"' + str(lib.getBodyTypeScale(getBodyType())) + '"'
|
||||
#message += ',"Metabolic Age":"' + str(lib.getMetabolicAge()) + '"'
|
||||
def _publish(self, weight, unit, mitdatetime, hasImpedance, miimpedance):
|
||||
global MQTT_CONNECTED
|
||||
if not MQTT_CONNECTED:
|
||||
sys.stderr.write('Not connected to MQTT server\n')
|
||||
exit()
|
||||
if int(weight) > USER1_GT:
|
||||
user = USER1_NAME
|
||||
height = USER1_HEIGHT
|
||||
age = self.GetAge(USER1_DOB)
|
||||
sex = USER1_SEX
|
||||
elif int(weight) < USER2_LT:
|
||||
user = USER2_NAME
|
||||
height = USER2_HEIGHT
|
||||
age = self.GetAge(USER2_DOB)
|
||||
sex = USER2_SEX
|
||||
else:
|
||||
user = USER3_NAME
|
||||
height = USER3_HEIGHT
|
||||
age = self.GetAge(USER3_DOB)
|
||||
sex = USER3_SEX
|
||||
lib = Xiaomi_Scale_Body_Metrics.bodyMetrics(weight, height, age, sex, 0)
|
||||
message = '{'
|
||||
message += '"Weight":"' + "{:.2f}".format(weight) + '"'
|
||||
message += ',"BMI":"' + "{:.2f}".format(lib.getBMI()) + '"'
|
||||
message += ',"Basal Metabolism":"' + "{:.2f}".format(lib.getBMR()) + '"'
|
||||
message += ',"Visceral Fat":"' + "{:.2f}".format(lib.getVisceralFat()) + '"'
|
||||
|
||||
message += ',"TimeStamp":"' + mitdatetime + '"'
|
||||
message += '}'
|
||||
self.mqtt_client.publish(MQTT_PREFIX + '/' + user + '/weight', message, qos=1, retain=True)
|
||||
sys.stdout.write('Sent data to topic %s: %s' % (MQTT_PREFIX + '/' + user + '/weight', message + '\n'))
|
||||
if hasImpedance:
|
||||
lib = Xiaomi_Scale_Body_Metrics.bodyMetrics(weight, height, age, sex, int(miimpedance))
|
||||
bodyscale = ['Obese', 'Overweight', 'Thick-set', 'Lack-exerscise', 'Balanced', 'Balanced-muscular', 'Skinny', 'Balanced-skinny', 'Skinny-muscular']
|
||||
message += ',"Lean Body Mass":"' + "{:.2f}".format(lib.getLBMCoefficient()) + '"'
|
||||
message += ',"Body Fat":"' + "{:.2f}".format(lib.getFatPercentage()) + '"'
|
||||
message += ',"Water":"' + "{:.2f}".format(lib.getWaterPercentage()) + '"'
|
||||
message += ',"Bone Mass":"' + "{:.2f}".format(lib.getBoneMass()) + '"'
|
||||
message += ',"Muscle Mass":"' + "{:.2f}".format(lib.getMuscleMass()) + '"'
|
||||
message += ',"Protein":"' + "{:.2f}".format(lib.getProteinPercentage()) + '"'
|
||||
message += ',"Body Type":"' + str(bodyscale[lib.getBodyType()]) + '"'
|
||||
message += ',"Metabolic Age":"' + "{:.0f}".format(lib.getMetabolicAge()) + '"'
|
||||
|
||||
message += ',"TimeStamp":"' + mitdatetime + '"'
|
||||
message += '}'
|
||||
try:
|
||||
self.mqtt_client.publish(MQTT_PREFIX + '/' + user + '/weight', message, qos=1, retain=True)
|
||||
sys.stdout.write('Sent data to topic %s: %s' % (MQTT_PREFIX + '/' + user + '/weight', message + '\n'))
|
||||
except:
|
||||
sys.stdout.write('Could not publish to MQTT, Disconnecting...\n')
|
||||
MQTT_CONNECTED = False
|
||||
self.mqtt_client.disconnect()
|
||||
pass
|
||||
|
||||
def main():
|
||||
|
||||
sys.stdout.write(' \n')
|
||||
sys.stdout.write('-------------------------------------\n')
|
||||
sys.stdout.write('Starting Xiaomi mi Scale...\n')
|
||||
scanner = btle.Scanner().withDelegate(ScanProcessor())
|
||||
while True:
|
||||
try:
|
||||
scanner.scan(5, passive=True) # Adding passive=True to try and fix issues on RPi devices
|
||||
except:
|
||||
sys.stderr.write("Error while running the script, continuing. If you see this message too often/constantly there is probably a real issue...\n")
|
||||
pass
|
||||
time.sleep(TIME_INTERVAL)
|
||||
sys.stdout.write(' \n')
|
||||
sys.stdout.write('-------------------------------------\n')
|
||||
sys.stdout.write('Starting Xiaomi mi Scale...\n')
|
||||
BluetoothFailCounter = 0
|
||||
while True:
|
||||
try:
|
||||
scanner = btle.Scanner().withDelegate(ScanProcessor())
|
||||
scanner.scan(5) # Adding passive=True to try and fix issues on RPi devices
|
||||
except BTLEDisconnectError as error:
|
||||
sys.stderr.write(f"btle disconnected: {error}\n")
|
||||
pass
|
||||
except BTLEManagementError as error:
|
||||
sys.stderr.write(f"Bluetooth connection error: {error}\n")
|
||||
if BluetoothFailCounter >= 4:
|
||||
sys.stderr.write(f"5+ Bluetooth connection errors. Resetting Bluetooth...\n")
|
||||
cmd = 'hciconfig hci0 reset'
|
||||
ps = subprocess.Popen(cmd, shell=True)
|
||||
time.sleep(30)
|
||||
BluetoothFailCounter = 0
|
||||
else:
|
||||
BluetoothFailCounter+=1
|
||||
pass
|
||||
except:
|
||||
sys.stderr.write("Error while running the script, continuing...\n")
|
||||
pass
|
||||
else:
|
||||
BluetoothFailCounter = 0
|
||||
time.sleep(TIME_INTERVAL)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
Loading…
Reference in a new issue