add-on repo

This commit is contained in:
lolouk44 2020-07-06 14:08:56 +01:00
parent 89ee8545af
commit 40ce443732
18 changed files with 690 additions and 593 deletions

View File

@ -1,100 +1,100 @@
# Xiaomi Mi Scale Add On for Home Assistant
Add-On for [HomeAssistant](https://www.home-assistant.io/) to read weight measurements from Xiaomi Body Scales.
## Supported Scales:
Name | Model | Picture
--- | --- | :---:
[Mi Smart Scale 2](https://www.mi.com/global/scale)                                                                                               | XMTZC04HM | ![Mi Scale_2](https://github.com/lolouk44/xiaomi_mi_scale/blob/master/Screenshots/Mi_Smart_Scale_2_Thumb.png)
[Mi Body Composition Scale](https://www.mi.com/global/mi-body-composition-scale/) | XMTZC02HM | ![Mi Scale](https://github.com/lolouk44/xiaomi_mi_scale/blob/master/Screenshots/Mi_Body_Composition_Scale_Thumb.png)
[Mi Body Composition Scale 2](https://c.mi.com/thread-2289389-1-0.html) | XMTZC05HM | ![Mi Body Composition Scale 2](https://github.com/lolouk44/xiaomi_mi_scale/blob/master/Screenshots/Mi_Body_Composition_Scale_2_Thumb.png)
## Setup
1. Retrieve the scale's MAC Address from the Xiaomi Mi Fit App:
![MAC Address](Screenshots/MAC_Address.png)
2. Clone this repository
`git clone https://github.com/lolouk44/xiaomi_mi_scale_ha_add_on`
3. Create a new directory xiaomi_mi_scale in the folder addons in your Home Assistant installation and place all files in it via SSH / Samba
![Add-On](Screenshots/addon.png)
4. Open Home Assistant and navigate to add-on store and clock the reload button on the top right corner. Now you should see the Xiaomi Mi Scale as a local add-on
![Add-On Store](Screenshots/addon_store.png)
5. Install the add-on (takes a while as the container is built locally)
6. Edit the Configuration
Option | Type | Required | Description
--- | --- | --- | ---
HCI_DEV | string | No | Bluetooth hci device to use. Defaults to hci0
MISCALE_MAC | string | Yes | Mac address of your scale
MQTT_PREFIX | string | No | MQTT Topic Prefix. Defaults to miscale
MQTT_HOST | string | Yes | MQTT Server (defaults to 127.0.0.1)
MQTT_USERNAME | string | No | Username for MQTT server (comment out if not required)
MQTT_PASSWORD | string | No | Password for MQTT (comment out if not required)
MQTT_PORT | int | No | Defaults to 1883
TIME_INTERVAL | int | No | Time in sec between each query to the scale, to allow other applications to use the Bluetooth module. Defaults to 30
MQTT_DISCOVERY | bool | No | MQTT Discovery for Home Assistant Defaults to true
MQTT_DISCOVERY_PREFIX | string | No | MQTT Discovery Prefix for Home Assistant. Defaults to homeassistant
Auto-gender selection/config -- This is used to create the calculations such as BMI, Water/Bone Mass etc...
Up to 3 users possible as long as weights do not overlap!
Option | Type | Required | Description
--- | --- | --- | ---
USER1_GT | int | Yes | If the weight is greater than this number, we'll assume that we're weighing User #1
USER1_SEX | string | Yes | male / female
USER1_NAME | string | Yes | Name of the user
USER1_HEIGHT | int | Yes | Height (in cm) of the user
USER1_DOB | string | Yes | DOB (in yyyy-mm-dd format)
USER2_LT | int | No | If the weight is less than this number, we'll assume that we're weighing User #2
USER2_SEX | string | No | male / female
USER2_NAME | string | No | Name of the user
USER2_HEIGHT | int | No |Height (in cm) of the user
USER2_DOB | string | No | DOB (in yyyy-mm-dd format)
USER3_SEX | string | No | male / female
USER3_NAME | string | No | Name of the user
USER3_HEIGHT | int | No |Height (in cm) of the user
USER3_DOB | string | No | DOB (in yyyy-mm-dd format)
7. Start the add-on
## Home-Assistant Setup:
Under the `sensor` block, enter as many blocks as users configured in your environment variables:
```yaml
- platform: mqtt
name: "Example Name Weight"
state_topic: "miScale/USER_NAME/weight"
value_template: "{{ value_json['Weight'] }}"
unit_of_measurement: "kg"
json_attributes_topic: "miScale/USER_NAME/weight"
icon: mdi:scale-bathroom
- platform: mqtt
name: "Example Name BMI"
state_topic: "miScale/USER_NAME/weight"
value_template: "{{ value_json['BMI'] }}"
icon: mdi:human-pregnant
```
![Mi Scale](https://github.com/lolouk44/xiaomi_mi_scale/blob/master/Screenshots/HA_Lovelace_Card.png)
![Mi Scale](https://github.com/lolouk44/xiaomi_mi_scale/blob/master/Screenshots/HA_Lovelace_Card_Details.png)
## Acknowledgements:
Thanks to @syssi (https://gist.github.com/syssi/4108a54877406dc231d95514e538bde9) and @prototux (https://github.com/wiecosystem/Bluetooth) for their initial code
Special thanks to [@ned-kelly](https://github.com/ned-kelly) for his help turning a "simple" python script into a fully fledged docker container
Thanks to [@bpaulin](https://github.com/bpaulin) for his PRs and collaboration
# Xiaomi Mi Scale Add On for Home Assistant
Add-On for [HomeAssistant](https://www.home-assistant.io/) to read weight measurements from Xiaomi Body Scales.
## Supported Scales:
Name | Model | Picture
--- | --- | :---:
[Mi Smart Scale 2](https://www.mi.com/global/scale)                                                                                               | XMTZC04HM | ![Mi Scale_2](https://github.com/lolouk44/xiaomi_mi_scale/blob/master/Screenshots/Mi_Smart_Scale_2_Thumb.png)
[Mi Body Composition Scale](https://www.mi.com/global/mi-body-composition-scale/) | XMTZC02HM | ![Mi Scale](https://github.com/lolouk44/xiaomi_mi_scale/blob/master/Screenshots/Mi_Body_Composition_Scale_Thumb.png)
[Mi Body Composition Scale 2](https://c.mi.com/thread-2289389-1-0.html) | XMTZC05HM | ![Mi Body Composition Scale 2](https://github.com/lolouk44/xiaomi_mi_scale/blob/master/Screenshots/Mi_Body_Composition_Scale_2_Thumb.png)
## Setup
1. Retrieve the scale's MAC Address from the Xiaomi Mi Fit App:
![MAC Address](Screenshots/MAC_Address.png)
2. Clone this repository
`git clone https://github.com/lolouk44/xiaomi_mi_scale_ha_add_on`
3. Create a new directory xiaomi_mi_scale in the folder addons in your Home Assistant installation and place all files in it via SSH / Samba
![Add-On](Screenshots/addon.png)
4. Open Home Assistant and navigate to add-on store and clock the reload button on the top right corner. Now you should see the Xiaomi Mi Scale as a local add-on
![Add-On Store](Screenshots/addon_store.png)
5. Install the add-on (takes a while as the container is built locally)
6. Edit the Configuration
Option | Type | Required | Description
--- | --- | --- | ---
HCI_DEV | string | No | Bluetooth hci device to use. Defaults to hci0
MISCALE_MAC | string | Yes | Mac address of your scale
MQTT_PREFIX | string | No | MQTT Topic Prefix. Defaults to miscale
MQTT_HOST | string | Yes | MQTT Server (defaults to 127.0.0.1)
MQTT_USERNAME | string | No | Username for MQTT server (comment out if not required)
MQTT_PASSWORD | string | No | Password for MQTT (comment out if not required)
MQTT_PORT | int | No | Defaults to 1883
TIME_INTERVAL | int | No | Time in sec between each query to the scale, to allow other applications to use the Bluetooth module. Defaults to 30
MQTT_DISCOVERY | bool | No | MQTT Discovery for Home Assistant Defaults to true
MQTT_DISCOVERY_PREFIX | string | No | MQTT Discovery Prefix for Home Assistant. Defaults to homeassistant
Auto-gender selection/config -- This is used to create the calculations such as BMI, Water/Bone Mass etc...
Up to 3 users possible as long as weights do not overlap!
Option | Type | Required | Description
--- | --- | --- | ---
USER1_GT | int | Yes | If the weight is greater than this number, we'll assume that we're weighing User #1
USER1_SEX | string | Yes | male / female
USER1_NAME | string | Yes | Name of the user
USER1_HEIGHT | int | Yes | Height (in cm) of the user
USER1_DOB | string | Yes | DOB (in yyyy-mm-dd format)
USER2_LT | int | No | If the weight is less than this number, we'll assume that we're weighing User #2. Defaults to USER1_GT Value
USER2_SEX | string | No | male / female. Defaults to female
USER2_NAME | string | No | Name of the user. Defaults to Serena
USER2_HEIGHT | int | No |Height (in cm) of the user. Defaults to 95
USER2_DOB | string | No | DOB (in yyyy-mm-dd format). Defaults to 1990-01-01
USER3_SEX | string | No | male / female. Defaults to female
USER3_NAME | string | No | Name of the user. Defaults to Missy
USER3_HEIGHT | int | No |Height (in cm) of the user. Defaults to 150
USER3_DOB | string | No | DOB (in yyyy-mm-dd format). Defaults to 1990-01-01
7. Start the add-on
## Home-Assistant Setup:
Under the `sensor` block, enter as many blocks as users configured in your environment variables:
```yaml
- platform: mqtt
name: "Example Name Weight"
state_topic: "miScale/USER_NAME/weight"
value_template: "{{ value_json['Weight'] }}"
unit_of_measurement: "kg"
json_attributes_topic: "miScale/USER_NAME/weight"
icon: mdi:scale-bathroom
- platform: mqtt
name: "Example Name BMI"
state_topic: "miScale/USER_NAME/weight"
value_template: "{{ value_json['BMI'] }}"
icon: mdi:human-pregnant
```
![Mi Scale](https://github.com/lolouk44/xiaomi_mi_scale/blob/master/Screenshots/HA_Lovelace_Card.png)
![Mi Scale](https://github.com/lolouk44/xiaomi_mi_scale/blob/master/Screenshots/HA_Lovelace_Card_Details.png)
## Acknowledgements:
Thanks to @syssi (https://gist.github.com/syssi/4108a54877406dc231d95514e538bde9) and @prototux (https://github.com/wiecosystem/Bluetooth) for their initial code
Special thanks to [@ned-kelly](https://github.com/ned-kelly) for his help turning a "simple" python script into a fully fledged docker container
Thanks to [@bpaulin](https://github.com/bpaulin) for his PRs and collaboration

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,74 +1,74 @@
{
"name": "Xiaomi Mi Scale",
"version": "0.1.6",
"slug": "xiaomi_mi_scale",
"description": "Read weight measurements from Xiamomi scale via BLE",
"url": "https://github.com/lolouk44/xiaomi_mi_scale_ha_add_on",
"image": "lolouk44/xiaomi-mi-scale",
"arch": ["armhf", "armv7", "aarch64", "amd64", "i386"],
"startup": "before",
"boot": "auto",
"panel_admin": false,
"host_network": true,
"privileged": ["NET_ADMIN", "SYS_ADMIN"],
"options": {
"HCI_DEV": "hci0",
"MISCALE_MAC": "00:00:00:00:00:00",
"MQTT_PREFIX": "miScale",
"MQTT_HOST": "192.168.0.1",
"MQTT_USERNAME": "",
"MQTT_PASSWORD": "",
"MQTT_PORT": 1883,
"TIME_INTERVAL": 30,
"MQTT_DISCOVERY": true,
"MQTT_DISCOVERY_PREFIX": "homeassistant",
"USER1_GT": 70,
"USER1_SEX": "male",
"USER1_NAME": "Jo",
"USER1_HEIGHT": 175,
"USER1_DOB": "1990-01-01",
"USER2_LT": 35,
"USER2_SEX": "female",
"USER2_NAME": "Serena",
"USER2_HEIGHT": 95,
"USER2_DOB": "1990-01-01",
"USER3_SEX": "female",
"USER3_NAME": "Missy",
"USER3_HEIGHT": 150,
"USER3_DOB": "1990-01-01"
},
"schema": {
"HCI_DEV": "str",
"MISCALE_MAC": "str",
"MQTT_PREFIX": "str",
"MQTT_HOST": "str",
"MQTT_USERNAME": "str",
"MQTT_PASSWORD": "str",
"MQTT_PORT": "int",
"TIME_INTERVAL": "int",
"MQTT_DISCOVERY": "bool",
"MQTT_DISCOVERY_PREFIX": "str",
"USER1_GT": "int",
"USER1_SEX": "str",
"USER1_NAME": "str",
"USER1_HEIGHT": "int",
"USER1_DOB": "str",
"USER2_LT": "int?",
"USER2_SEX": "str?",
"USER2_NAME": "str?",
"USER2_HEIGHT": "int?",
"USER2_DOB": "str?",
"USER3_SEX": "str?",
"USER3_NAME": "str?",
"USER3_HEIGHT": "int?",
"USER3_DOB": "str?"
}
{
"name": "Xiaomi Mi Scale",
"version": "0.1.6",
"slug": "xiaomi_mi_scale",
"description": "Read weight measurements from Xiamomi scale via BLE",
"url": "https://github.com/lolouk44/xiaomi_mi_scale_ha_add_on",
"image": "lolouk44/xiaomi-mi-scale",
"arch": ["armhf", "armv7", "aarch64", "amd64", "i386"],
"startup": "before",
"boot": "auto",
"panel_admin": false,
"host_network": true,
"privileged": ["NET_ADMIN", "SYS_ADMIN"],
"options": {
"HCI_DEV": "hci0",
"MISCALE_MAC": "00:00:00:00:00:00",
"MQTT_PREFIX": "miScale",
"MQTT_HOST": "192.168.0.1",
"MQTT_USERNAME": "user",
"MQTT_PASSWORD": "passwd",
"MQTT_PORT": 1883,
"TIME_INTERVAL": 30,
"MQTT_DISCOVERY": true,
"MQTT_DISCOVERY_PREFIX": "homeassistant",
"USER1_GT": 70,
"USER1_SEX": "male",
"USER1_NAME": "Jo",
"USER1_HEIGHT": 175,
"USER1_DOB": "1990-01-01",
"USER2_LT": 35,
"USER2_SEX": "female",
"USER2_NAME": "Serena",
"USER2_HEIGHT": 95,
"USER2_DOB": "1990-01-01",
"USER3_SEX": "female",
"USER3_NAME": "Missy",
"USER3_HEIGHT": 150,
"USER3_DOB": "1990-01-01"
},
"schema": {
"HCI_DEV": "str?",
"MISCALE_MAC": "str",
"MQTT_PREFIX": "str?",
"MQTT_HOST": "str",
"MQTT_USERNAME": "str?",
"MQTT_PASSWORD": "str?",
"MQTT_PORT": "int?",
"TIME_INTERVAL": "int?",
"MQTT_DISCOVERY": "bool?",
"MQTT_DISCOVERY_PREFIX": "str?",
"USER1_GT": "int",
"USER1_SEX": "str",
"USER1_NAME": "str",
"USER1_HEIGHT": "int",
"USER1_DOB": "str",
"USER2_LT": "int?",
"USER2_SEX": "str?",
"USER2_NAME": "str?",
"USER2_HEIGHT": "int?",
"USER2_DOB": "str?",
"USER3_SEX": "str?",
"USER3_NAME": "str?",
"USER3_HEIGHT": "int?",
"USER3_DOB": "str?"
}
}

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -1,242 +1,334 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import print_function
import argparse
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.publish as publish
from datetime import datetime
import json
import Xiaomi_Scale_Body_Metrics
# First Log msg
sys.stdout.write(' \n')
sys.stdout.write('-------------------------------------\n')
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Starting Xiaomi mi Scale...\n")
# Configuraiton...
# Trying To Load Config From options.json (HA Add-On)
try:
with open('/data/options.json') as json_file:
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Loading Config From Add-On Options...\n")
data = json.load(json_file)
MISCALE_MAC = data["MISCALE_MAC"]
MQTT_USERNAME = None if(data["MQTT_USERNAME"] == "") else data["MQTT_USERNAME"]
MQTT_PASSWORD = None if(data["MQTT_PASSWORD"] == "") else data["MQTT_PASSWORD"]
MQTT_HOST = data["MQTT_HOST"]
MQTT_PORT = int(data["MQTT_PORT"])
MQTT_PREFIX = data["MQTT_PREFIX"]
TIME_INTERVAL = int(data["TIME_INTERVAL"])
MQTT_DISCOVERY = data["MQTT_DISCOVERY"]
MQTT_DISCOVERY_PREFIX = data["MQTT_DISCOVERY_PREFIX"]
HCI_DEV = data["HCI_DEV"][-1]
# User Variables...
USER1_GT = int(data["USER1_GT"])
USER1_SEX = data["USER1_SEX"]
USER1_NAME = data["USER1_NAME"]
USER1_HEIGHT = int(data["USER1_HEIGHT"])
USER1_DOB = data["USER1_DOB"]
USER2_LT = int(data["USER2_LT"])
USER2_SEX = data["USER2_SEX"]
USER2_NAME = data["USER2_NAME"]
USER2_HEIGHT = int(data["USER2_HEIGHT"])
USER2_DOB = data["USER2_DOB"]
USER3_SEX = data["USER3_SEX"]
USER3_NAME = data["USER3_NAME"]
USER3_HEIGHT = int(data["USER3_HEIGHT"])
USER3_DOB = data["USER3_DOB"]
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Config Loaded...\n")
# Failed to open options.json, Loading Config From Environment (Not HA Add-On)
except FileNotFoundError:
pass
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Loading Config From OS Environment...\n")
MISCALE_MAC = os.getenv('MISCALE_MAC', '')
MQTT_USERNAME = os.getenv('MQTT_USERNAME', 'username')
MQTT_PASSWORD = os.getenv('MQTT_PASSWORD', None)
MQTT_HOST = os.getenv('MQTT_HOST', '127.0.0.1')
MQTT_PORT = int(os.getenv('MQTT_PORT', 1883))
MQTT_PREFIX = os.getenv('MQTT_PREFIX', 'miscale')
TIME_INTERVAL = int(os.getenv('TIME_INTERVAL', 30))
MQTT_DISCOVERY = os.getenv('MQTT_DISCOVERY',True)
MQTT_DISCOVERY_PREFIX = os.getenv('MQTT_DISCOVERY_PREFIX','homeassistant')
HCI_DEV = os.getenv('HCI_DEV', 'hci0')[-1]
# 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
USER1_SEX = os.getenv('USER1_SEX', 'male')
USER1_NAME = os.getenv('USER1_NAME', 'David') # Name of the user
USER1_HEIGHT = int(os.getenv('USER1_HEIGHT', '175')) # Height (in cm) of the user
USER1_DOB = os.getenv('USER1_DOB', '1988-09-30') # DOB (in yyyy-mm-dd format)
USER2_LT = int(os.getenv('USER2_LT', '55')) # If the weight is less than this number, we'll assume that we're weighing User #2
USER2_SEX = os.getenv('USER2_SEX', 'female')
USER2_NAME = os.getenv('USER2_NAME', 'Joanne') # Name of the user
USER2_HEIGHT = int(os.getenv('USER2_HEIGHT', '155')) # Height (in cm) of the user
USER2_DOB = os.getenv('USER2_DOB', '1988-10-20') # DOB (in yyyy-mm-dd format)
USER3_SEX = os.getenv('USER3_SEX', 'male')
USER3_NAME = os.getenv('USER3_NAME', 'Unknown User') # Name of the user
USER3_HEIGHT = int(os.getenv('USER3_HEIGHT', '175')) # Height (in cm) of the user
USER3_DOB = os.getenv('USER3_DOB', '1988-01-01') # DOB (in yyyy-mm-dd format)
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Config Loaded...\n")
OLD_MEASURE = ''
def discovery():
for MQTTUser in (USER1_NAME,USER2_NAME,USER3_NAME):
message = '{"name": "' + MQTTUser + ' Weight",'
message+= '"state_topic": "miScale/' + MQTTUser + '/weight","value_template": "{{ value_json.Weight }}","unit_of_measurement": "kg",'
message+= '"json_attributes_topic": "miScale/' + MQTTUser + '/weight","icon": "mdi:scale-bathroom"}'
publish.single(
MQTT_DISCOVERY_PREFIX + '/sensor/' + MQTT_PREFIX + '/' + MQTTUser + '/config',
message,
retain=True,
hostname=MQTT_HOST,
port=MQTT_PORT,
auth={'username':MQTT_USERNAME, 'password':MQTT_PASSWORD}
)
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Discovery Completed...\n")
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 __init__(self):
DefaultDelegate.__init__(self)
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):
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)
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):
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)
def _publish(self, weight, unit, mitdatetime, hasImpedance, miimpedance):
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()) + '"'
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:
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Publishing data to topic {MQTT_PREFIX + '/' + user + '/weight'}: {message}\n")
publish.single(
MQTT_PREFIX + '/' + user + '/weight',
message,
# qos=1, #Removed qos=1 as incorrect connection details will result in the client waiting for ack from broker
retain=True,
hostname=MQTT_HOST,
port=MQTT_PORT,
auth={'username':MQTT_USERNAME, 'password':MQTT_PASSWORD}
)
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Data Published ...\n")
except Exception as error:
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Could not publish to MQTT: {error}\n")
raise
def main():
if MQTT_DISCOVERY:
discovery()
BluetoothFailCounter = 0
while True:
try:
scanner = btle.Scanner(HCI_DEV).withDelegate(ScanProcessor())
scanner.scan(5) # Adding passive=True to try and fix issues on RPi devices
except BTLEDisconnectError as error:
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - btle disconnected: {error}\n")
pass
except BTLEManagementError as error:
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Bluetooth connection error: {error}\n")
if BluetoothFailCounter >= 4:
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - 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 Exception as error:
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Error while running the script: {error}\n")
pass
else:
BluetoothFailCounter = 0
time.sleep(TIME_INTERVAL)
if __name__ == "__main__":
main()
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import print_function
import argparse
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.publish as publish
from datetime import datetime
import json
import Xiaomi_Scale_Body_Metrics
# First Log msg
sys.stdout.write(' \n')
sys.stdout.write('-------------------------------------\n')
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Starting Xiaomi mi Scale...\n")
# Configuraiton...
# Trying To Load Config From options.json (HA Add-On)
try:
with open('/data/options.json') as json_file:
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Loading Config From Add-On Options...\n")
data = json.load(json_file)
try:
MISCALE_MAC = data["MISCALE_MAC"]
except:
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - MAC Address not provided...\n")
raise
try:
MQTT_USERNAME = data["MQTT_USERNAME"]
except:
MQTT_USERNAME = None
pass
try:
MQTT_PASSWORD = data["MQTT_PASSWORD"]
except:
MQTT_PASSWORD = None
pass
try:
MQTT_HOST = data["MQTT_HOST"]
except:
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - MQTT Host not provided...\n")
raise
try:
MQTT_PORT = int(data["MQTT_PORT"])
except:
MQTT_PORT = 1883
pass
try:
MQTT_PREFIX = data["MQTT_PREFIX"]
except:
MQTT_PREFIX = "miScale"
pass
try:
TIME_INTERVAL = int(data["TIME_INTERVAL"])
except:
TIME_INTERVAL = 30
pass
try:
MQTT_DISCOVERY = data["MQTT_DISCOVERY"]
except:
MQTT_DISCOVERY = True
pass
try:
MQTT_DISCOVERY_PREFIX = data["MQTT_DISCOVERY_PREFIX"]
except:
MQTT_DISCOVERY_PREFIX = "homeassistant"
pass
try:
HCI_DEV = data["HCI_DEV"][-1]
except:
HCI_DEV = "hci0"[-1]
pass
try:
USER1_GT = int(data["USER1_GT"])
except:
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - USER1_GT not provided...\n")
raise
try:
USER1_SEX = data["USER1_SEX"]
except:
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - USER1_SEX not provided...\n")
raise
try:
USER1_NAME = data["USER1_NAME"]
except:
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - USER1_NAME not provided...\n")
raise
try:
USER1_HEIGHT = int(data["USER1_HEIGHT"])
except:
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - USER1_HEIGHT not provided...\n")
raise
try:
USER1_DOB = data["USER1_DOB"]
except:
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - USER1_DOB not provided...\n")
raise
try:
USER2_LT = int(data["USER2_LT"])
except:
USER2_LT = USER1_GT
pass
try:
USER2_SEX = data["USER2_SEX"]
except:
USER2_SEX = "female"
pass
try:
USER2_NAME = data["USER2_NAME"]
except:
USER2_NAME = "Serena"
pass
try:
USER2_HEIGHT = int(data["USER2_HEIGHT"])
except:
USER2_HEIGHT = 95
pass
try:
USER2_DOB = data["USER2_DOB"]
except:
USER2_DOB = "1990-01-01"
pass
try:
USER3_SEX = data["USER3_SEX"]
except:
USER3_SEX = "female"
pass
try:
USER3_NAME = data["USER3_NAME"]
except:
USER3_NAME = "Missy"
pass
try:
USER3_HEIGHT = int(data["USER3_HEIGHT"])
except:
USER3_HEIGHT = 150
pass
try:
USER3_DOB = data["USER3_DOB"]
except:
USER3_DOB = "1990-01-01"
pass
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Config Loaded...\n")
# Failed to open options.json, Loading Config From Environment (Not HA Add-On)
except FileNotFoundError:
pass
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Loading Config From OS Environment...\n")
MISCALE_MAC = os.getenv('MISCALE_MAC', '')
MQTT_USERNAME = os.getenv('MQTT_USERNAME', 'username')
MQTT_PASSWORD = os.getenv('MQTT_PASSWORD', None)
MQTT_HOST = os.getenv('MQTT_HOST', '127.0.0.1')
MQTT_PORT = int(os.getenv('MQTT_PORT', 1883))
MQTT_PREFIX = os.getenv('MQTT_PREFIX', 'miscale')
TIME_INTERVAL = int(os.getenv('TIME_INTERVAL', 30))
MQTT_DISCOVERY = os.getenv('MQTT_DISCOVERY',True)
MQTT_DISCOVERY_PREFIX = os.getenv('MQTT_DISCOVERY_PREFIX','homeassistant')
HCI_DEV = os.getenv('HCI_DEV', 'hci0')[-1]
# 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
USER1_SEX = os.getenv('USER1_SEX', 'male')
USER1_NAME = os.getenv('USER1_NAME', 'David') # Name of the user
USER1_HEIGHT = int(os.getenv('USER1_HEIGHT', '175')) # Height (in cm) of the user
USER1_DOB = os.getenv('USER1_DOB', '1988-09-30') # DOB (in yyyy-mm-dd format)
USER2_LT = int(os.getenv('USER2_LT', '55')) # If the weight is less than this number, we'll assume that we're weighing User #2
USER2_SEX = os.getenv('USER2_SEX', 'female')
USER2_NAME = os.getenv('USER2_NAME', 'Joanne') # Name of the user
USER2_HEIGHT = int(os.getenv('USER2_HEIGHT', '155')) # Height (in cm) of the user
USER2_DOB = os.getenv('USER2_DOB', '1988-10-20') # DOB (in yyyy-mm-dd format)
USER3_SEX = os.getenv('USER3_SEX', 'male')
USER3_NAME = os.getenv('USER3_NAME', 'Unknown User') # Name of the user
USER3_HEIGHT = int(os.getenv('USER3_HEIGHT', '175')) # Height (in cm) of the user
USER3_DOB = os.getenv('USER3_DOB', '1988-01-01') # DOB (in yyyy-mm-dd format)
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Config Loaded...\n")
OLD_MEASURE = ''
def discovery():
for MQTTUser in (USER1_NAME,USER2_NAME,USER3_NAME):
message = '{"name": "' + MQTTUser + ' Weight",'
message+= '"state_topic": "miScale/' + MQTTUser + '/weight","value_template": "{{ value_json.Weight }}","unit_of_measurement": "kg",'
message+= '"json_attributes_topic": "miScale/' + MQTTUser + '/weight","icon": "mdi:scale-bathroom"}'
publish.single(
MQTT_DISCOVERY_PREFIX + '/sensor/' + MQTT_PREFIX + '/' + MQTTUser + '/config',
message,
retain=True,
hostname=MQTT_HOST,
port=MQTT_PORT,
auth={'username':MQTT_USERNAME, 'password':MQTT_PASSWORD}
)
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Discovery Completed...\n")
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 __init__(self):
DefaultDelegate.__init__(self)
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):
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)
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):
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)
def _publish(self, weight, unit, mitdatetime, hasImpedance, miimpedance):
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()) + '"'
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:
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Publishing data to topic {MQTT_PREFIX + '/' + user + '/weight'}: {message}\n")
publish.single(
MQTT_PREFIX + '/' + user + '/weight',
message,
# qos=1, #Removed qos=1 as incorrect connection details will result in the client waiting for ack from broker
retain=True,
hostname=MQTT_HOST,
port=MQTT_PORT,
auth={'username':MQTT_USERNAME, 'password':MQTT_PASSWORD}
)
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Data Published ...\n")
except Exception as error:
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Could not publish to MQTT: {error}\n")
raise
def main():
if MQTT_DISCOVERY:
discovery()
BluetoothFailCounter = 0
while True:
try:
scanner = btle.Scanner(HCI_DEV).withDelegate(ScanProcessor())
scanner.scan(5) # Adding passive=True to try and fix issues on RPi devices
except BTLEDisconnectError as error:
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - btle disconnected: {error}\n")
pass
except BTLEManagementError as error:
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Bluetooth connection error: {error}\n")
if BluetoothFailCounter >= 4:
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - 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 Exception as error:
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Error while running the script: {error}\n")
pass
else:
BluetoothFailCounter = 0
time.sleep(TIME_INTERVAL)
if __name__ == "__main__":
main()

View File

@ -1,155 +1,155 @@
class bodyScales:
def __init__(self, age, height, sex, weight, scaleType='xiaomi'):
self.age = age
self.height = height
self.sex = sex
self.weight = weight
if scaleType == 'xiaomi':
self.scaleType = 'xiaomi'
else:
self.scaleType = 'holtek'
# Get BMI scale
def getBMIScale(self):
if self.scaleType == 'xiaomi':
# Amazfit/new mi fit
#return [18.5, 24, 28]
# Old mi fit // amazfit for body figure
return [18.5, 25.0, 28.0, 32.0]
elif self.scaleType == 'holtek':
return [18.5, 25.0, 30.0]
# Get fat percentage scale
def getFatPercentageScale(self):
# The included tables where quite strange, maybe bogus, replaced them with better ones...
if self.scaleType == 'xiaomi':
scales = [
{'min': 0, 'max': 12, 'female': [12.0, 21.0, 30.0, 34.0], 'male': [7.0, 16.0, 25.0, 30.0]},
{'min': 12, 'max': 14, 'female': [15.0, 24.0, 33.0, 37.0], 'male': [7.0, 16.0, 25.0, 30.0]},
{'min': 14, 'max': 16, 'female': [18.0, 27.0, 36.0, 40.0], 'male': [7.0, 16.0, 25.0, 30.0]},
{'min': 16, 'max': 18, 'female': [20.0, 28.0, 37.0, 41.0], 'male': [7.0, 16.0, 25.0, 30.0]},
{'min': 18, 'max': 40, 'female': [21.0, 28.0, 35.0, 40.0], 'male': [11.0, 17.0, 22.0, 27.0]},
{'min': 40, 'max': 60, 'female': [22.0, 29.0, 36.0, 41.0], 'male': [12.0, 18.0, 23.0, 28.0]},
{'min': 60, 'max': 100, 'female': [23.0, 30.0, 37.0, 42.0], 'male': [14.0, 20.0, 25.0, 30.0]},
]
elif self.scaleType == 'holtek':
scales = [
{'min': 0, 'max': 21, 'female': [18, 23, 30, 35], 'male': [8, 14, 21, 25]},
{'min': 21, 'max': 26, 'female': [19, 24, 30, 35], 'male': [10, 15, 22, 26]},
{'min': 26, 'max': 31, 'female': [20, 25, 31, 36], 'male': [11, 16, 21, 27]},
{'min': 31, 'max': 36, 'female': [21, 26, 33, 36], 'male': [13, 17, 25, 28]},
{'min': 36, 'max': 41, 'female': [22, 27, 34, 37], 'male': [15, 20, 26, 29]},
{'min': 41, 'max': 46, 'female': [23, 28, 35, 38], 'male': [16, 22, 27, 30]},
{'min': 46, 'max': 51, 'female': [24, 30, 36, 38], 'male': [17, 23, 29, 31]},
{'min': 51, 'max': 56, 'female': [26, 31, 36, 39], 'male': [19, 25, 30, 33]},
{'min': 56, 'max': 100, 'female': [27, 32, 37, 40], 'male': [21, 26, 31, 34]},
]
for scale in scales:
if self.age >= scale['min'] and self.age < scale['max']:
return scale[self.sex]
# Get muscle mass scale
def getMuscleMassScale(self):
if self.scaleType == 'xiaomi':
scales = [
{'min': {'male': 170, 'female': 160}, 'female': [36.5, 42.6], 'male': [49.4, 59.5]},
{'min': {'male': 160, 'female': 150}, 'female': [32.9, 37.6], 'male': [44.0, 52.5]},
{'min': {'male': 0, 'female': 0}, 'female': [29.1, 34.8], 'male': [38.5, 46.6]},
]
elif self.scaleType == 'holtek':
scales = [
{'min': {'male': 170, 'female': 170}, 'female': [36.5, 42.5], 'male': [49.5, 59.4]},
{'min': {'male': 160, 'female': 160}, 'female': [32.9, 37.5], 'male': [44.0, 52.4]},
{'min': {'male': 0, 'female': 0}, 'female': [29.1, 34.7], 'male': [38.5, 46.5]}
]
for scale in scales:
if self.height >= scale['min'][self.sex]:
return scale[self.sex]
# Get water percentage scale
def getWaterPercentageScale(self):
if self.scaleType == 'xiaomi':
if self.sex == 'male':
return [55.0, 65.1]
elif self.sex == 'female':
return [45.0, 60.1]
elif self.scaleType == 'holtek':
return [53, 67]
# Get visceral fat scale
def getVisceralFatScale(self):
# Actually the same in mi fit/amazfit and holtek's sdk
return [10.0, 15.0]
# Get bone mass scale
def getBoneMassScale(self):
if self.scaleType == 'xiaomi':
scales = [
{'male': {'min': 75.0, 'scale': [2.0, 4.2]}, 'female': {'min': 60.0, 'scale': [1.8, 3.9]}},
{'male': {'min': 60.0, 'scale': [1.9, 4.1]}, 'female': {'min': 45.0, 'scale': [1.5, 3.8]}},
{'male': {'min': 0.0, 'scale': [1.6, 3.9]}, 'female': {'min': 0.0, 'scale': [1.3, 3.6]}},
]
for scale in scales:
if self.weight >= scale[self.sex]['min']:
return scale[self.sex]['scale']
elif self.scaleType == 'holtek':
scales = [
{'female': {'min': 60, 'optimal': 2.5}, 'male': {'min': 75, 'optimal': 3.2}},
{'female': {'min': 45, 'optimal': 2.2}, 'male': {'min': 69, 'optimal': 2.9}},
{'female': {'min': 0, 'optimal': 1.8}, 'male': {'min': 0, 'optimal': 2.5}}
]
for scale in scales:
if self.weight >= scale[self.sex]['min']:
return [scale[self.sex]['optimal']-1, scale[self.sex]['optimal']+1]
# Get BMR scale
def getBMRScale(self):
if self.scaleType == 'xiaomi':
coefficients = {
'male': {30: 21.6, 50: 20.07, 100: 19.35},
'female': {30: 21.24, 50: 19.53, 100: 18.63}
}
elif self.scaleType == 'holtek':
coefficients = {
'female': {12: 34, 15: 29, 17: 24, 29: 22, 50: 20, 120: 19},
'male': {12: 36, 15: 30, 17: 26, 29: 23, 50: 21, 120: 20}
}
for age, coefficient in coefficients[self.sex].items():
if self.age < age:
return [self.weight * coefficient]
# Get protein scale (hardcoded in mi fit)
def getProteinPercentageScale(self):
# Actually the same in mi fit and holtek's sdk
return [16, 20]
# Get ideal weight scale (BMI scale converted to weights)
def getIdealWeightScale(self):
scale = []
for bmiScale in self.getBMIScale():
scale.append((bmiScale*self.height)*self.height/10000)
return scale
# Get Body Score scale
def getBodyScoreScale(self):
# very bad, bad, normal, good, better
return [50.0, 60.0, 80.0, 90.0]
# Return body type scale
def getBodyTypeScale(self):
return ['obese', 'overweight', 'thick-set', 'lack-exerscise', 'balanced', 'balanced-muscular', 'skinny', 'balanced-skinny', 'skinny-muscular']
class bodyScales:
def __init__(self, age, height, sex, weight, scaleType='xiaomi'):
self.age = age
self.height = height
self.sex = sex
self.weight = weight
if scaleType == 'xiaomi':
self.scaleType = 'xiaomi'
else:
self.scaleType = 'holtek'
# Get BMI scale
def getBMIScale(self):
if self.scaleType == 'xiaomi':
# Amazfit/new mi fit
#return [18.5, 24, 28]
# Old mi fit // amazfit for body figure
return [18.5, 25.0, 28.0, 32.0]
elif self.scaleType == 'holtek':
return [18.5, 25.0, 30.0]
# Get fat percentage scale
def getFatPercentageScale(self):
# The included tables where quite strange, maybe bogus, replaced them with better ones...
if self.scaleType == 'xiaomi':
scales = [
{'min': 0, 'max': 12, 'female': [12.0, 21.0, 30.0, 34.0], 'male': [7.0, 16.0, 25.0, 30.0]},
{'min': 12, 'max': 14, 'female': [15.0, 24.0, 33.0, 37.0], 'male': [7.0, 16.0, 25.0, 30.0]},
{'min': 14, 'max': 16, 'female': [18.0, 27.0, 36.0, 40.0], 'male': [7.0, 16.0, 25.0, 30.0]},
{'min': 16, 'max': 18, 'female': [20.0, 28.0, 37.0, 41.0], 'male': [7.0, 16.0, 25.0, 30.0]},
{'min': 18, 'max': 40, 'female': [21.0, 28.0, 35.0, 40.0], 'male': [11.0, 17.0, 22.0, 27.0]},
{'min': 40, 'max': 60, 'female': [22.0, 29.0, 36.0, 41.0], 'male': [12.0, 18.0, 23.0, 28.0]},
{'min': 60, 'max': 100, 'female': [23.0, 30.0, 37.0, 42.0], 'male': [14.0, 20.0, 25.0, 30.0]},
]
elif self.scaleType == 'holtek':
scales = [
{'min': 0, 'max': 21, 'female': [18, 23, 30, 35], 'male': [8, 14, 21, 25]},
{'min': 21, 'max': 26, 'female': [19, 24, 30, 35], 'male': [10, 15, 22, 26]},
{'min': 26, 'max': 31, 'female': [20, 25, 31, 36], 'male': [11, 16, 21, 27]},
{'min': 31, 'max': 36, 'female': [21, 26, 33, 36], 'male': [13, 17, 25, 28]},
{'min': 36, 'max': 41, 'female': [22, 27, 34, 37], 'male': [15, 20, 26, 29]},
{'min': 41, 'max': 46, 'female': [23, 28, 35, 38], 'male': [16, 22, 27, 30]},
{'min': 46, 'max': 51, 'female': [24, 30, 36, 38], 'male': [17, 23, 29, 31]},
{'min': 51, 'max': 56, 'female': [26, 31, 36, 39], 'male': [19, 25, 30, 33]},
{'min': 56, 'max': 100, 'female': [27, 32, 37, 40], 'male': [21, 26, 31, 34]},
]
for scale in scales:
if self.age >= scale['min'] and self.age < scale['max']:
return scale[self.sex]
# Get muscle mass scale
def getMuscleMassScale(self):
if self.scaleType == 'xiaomi':
scales = [
{'min': {'male': 170, 'female': 160}, 'female': [36.5, 42.6], 'male': [49.4, 59.5]},
{'min': {'male': 160, 'female': 150}, 'female': [32.9, 37.6], 'male': [44.0, 52.5]},
{'min': {'male': 0, 'female': 0}, 'female': [29.1, 34.8], 'male': [38.5, 46.6]},
]
elif self.scaleType == 'holtek':
scales = [
{'min': {'male': 170, 'female': 170}, 'female': [36.5, 42.5], 'male': [49.5, 59.4]},
{'min': {'male': 160, 'female': 160}, 'female': [32.9, 37.5], 'male': [44.0, 52.4]},
{'min': {'male': 0, 'female': 0}, 'female': [29.1, 34.7], 'male': [38.5, 46.5]}
]
for scale in scales:
if self.height >= scale['min'][self.sex]:
return scale[self.sex]
# Get water percentage scale
def getWaterPercentageScale(self):
if self.scaleType == 'xiaomi':
if self.sex == 'male':
return [55.0, 65.1]
elif self.sex == 'female':
return [45.0, 60.1]
elif self.scaleType == 'holtek':
return [53, 67]
# Get visceral fat scale
def getVisceralFatScale(self):
# Actually the same in mi fit/amazfit and holtek's sdk
return [10.0, 15.0]
# Get bone mass scale
def getBoneMassScale(self):
if self.scaleType == 'xiaomi':
scales = [
{'male': {'min': 75.0, 'scale': [2.0, 4.2]}, 'female': {'min': 60.0, 'scale': [1.8, 3.9]}},
{'male': {'min': 60.0, 'scale': [1.9, 4.1]}, 'female': {'min': 45.0, 'scale': [1.5, 3.8]}},
{'male': {'min': 0.0, 'scale': [1.6, 3.9]}, 'female': {'min': 0.0, 'scale': [1.3, 3.6]}},
]
for scale in scales:
if self.weight >= scale[self.sex]['min']:
return scale[self.sex]['scale']
elif self.scaleType == 'holtek':
scales = [
{'female': {'min': 60, 'optimal': 2.5}, 'male': {'min': 75, 'optimal': 3.2}},
{'female': {'min': 45, 'optimal': 2.2}, 'male': {'min': 69, 'optimal': 2.9}},
{'female': {'min': 0, 'optimal': 1.8}, 'male': {'min': 0, 'optimal': 2.5}}
]
for scale in scales:
if self.weight >= scale[self.sex]['min']:
return [scale[self.sex]['optimal']-1, scale[self.sex]['optimal']+1]
# Get BMR scale
def getBMRScale(self):
if self.scaleType == 'xiaomi':
coefficients = {
'male': {30: 21.6, 50: 20.07, 100: 19.35},
'female': {30: 21.24, 50: 19.53, 100: 18.63}
}
elif self.scaleType == 'holtek':
coefficients = {
'female': {12: 34, 15: 29, 17: 24, 29: 22, 50: 20, 120: 19},
'male': {12: 36, 15: 30, 17: 26, 29: 23, 50: 21, 120: 20}
}
for age, coefficient in coefficients[self.sex].items():
if self.age < age:
return [self.weight * coefficient]
# Get protein scale (hardcoded in mi fit)
def getProteinPercentageScale(self):
# Actually the same in mi fit and holtek's sdk
return [16, 20]
# Get ideal weight scale (BMI scale converted to weights)
def getIdealWeightScale(self):
scale = []
for bmiScale in self.getBMIScale():
scale.append((bmiScale*self.height)*self.height/10000)
return scale
# Get Body Score scale
def getBodyScoreScale(self):
# very bad, bad, normal, good, better
return [50.0, 60.0, 80.0, 90.0]
# Return body type scale
def getBodyTypeScale(self):
return ['obese', 'overweight', 'thick-set', 'lack-exerscise', 'balanced', 'balanced-muscular', 'skinny', 'balanced-skinny', 'skinny-muscular']

View File

@ -1,24 +1,24 @@
#!/bin/bash
export MISCALE_MAC=00:00:00:00:00:00 # Mac address of your scale
export MQTT_PREFIX=miScale
export USER1_GT=70 # If the weight is greater than this number, we'll assume that we're weighing User #1
export USER1_SEX=male
export USER1_NAME=Jo # Name of the user
export USER1_HEIGHT=175 # Height (in cm) of the user
export USER1_DOB=1990-01-01 # DOB (in yyyy-mm-dd format)
export USER2_LT=35 # If the weight is less than this number, we'll assume that we're weighing User #2
export USER2_SEX=female
export USER2_NAME=Sarah # Name of the user
export USER2_HEIGHT=95 # Height (in cm) of the user
export USER2_DOB=1990-01-01 # DOB (in yyyy-mm-dd format)
export USER3_SEX=female
export USER3_NAME=Missy # Name of the user
export USER3_HEIGHT=150 # Height (in cm) of the user
export USER3_DOB=1990-01-01 # DOB (in yyyy-mm-dd format)
MY_PWD="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
#!/bin/bash
export MISCALE_MAC=00:00:00:00:00:00 # Mac address of your scale
export MQTT_PREFIX=miScale
export USER1_GT=70 # If the weight is greater than this number, we'll assume that we're weighing User #1
export USER1_SEX=male
export USER1_NAME=Jo # Name of the user
export USER1_HEIGHT=175 # Height (in cm) of the user
export USER1_DOB=1990-01-01 # DOB (in yyyy-mm-dd format)
export USER2_LT=35 # If the weight is less than this number, we'll assume that we're weighing User #2
export USER2_SEX=female
export USER2_NAME=Sarah # Name of the user
export USER2_HEIGHT=95 # Height (in cm) of the user
export USER2_DOB=1990-01-01 # DOB (in yyyy-mm-dd format)
export USER3_SEX=female
export USER3_NAME=Missy # Name of the user
export USER3_HEIGHT=150 # Height (in cm) of the user
export USER3_DOB=1990-01-01 # DOB (in yyyy-mm-dd format)
MY_PWD="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
python3 $MY_PWD/Xiaomi_Scale.py

5
repository.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Lolouk44 Add-Ons",
"url": "https://github.com/lolouk44/hassio-addons",
"maintainer": "lolouk44"
}