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 # 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. Add-On for [HomeAssistant](https://www.home-assistant.io/) to read weight measurements from Xiaomi Body Scales.
## Supported Scales: ## Supported Scales:
Name | Model | Picture 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 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](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) [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 ## Setup
1. Retrieve the scale's MAC Address from the Xiaomi Mi Fit App: 1. Retrieve the scale's MAC Address from the Xiaomi Mi Fit App:
![MAC Address](Screenshots/MAC_Address.png) ![MAC Address](Screenshots/MAC_Address.png)
2. Clone this repository 2. Clone this repository
`git clone https://github.com/lolouk44/xiaomi_mi_scale_ha_add_on` `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 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) ![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 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) ![Add-On Store](Screenshots/addon_store.png)
5. Install the add-on (takes a while as the container is built locally) 5. Install the add-on (takes a while as the container is built locally)
6. Edit the Configuration 6. Edit the Configuration
Option | Type | Required | Description Option | Type | Required | Description
--- | --- | --- | --- --- | --- | --- | ---
HCI_DEV | string | No | Bluetooth hci device to use. Defaults to hci0 HCI_DEV | string | No | Bluetooth hci device to use. Defaults to hci0
MISCALE_MAC | string | Yes | Mac address of your scale MISCALE_MAC | string | Yes | Mac address of your scale
MQTT_PREFIX | string | No | MQTT Topic Prefix. Defaults to miscale MQTT_PREFIX | string | No | MQTT Topic Prefix. Defaults to miscale
MQTT_HOST | string | Yes | MQTT Server (defaults to 127.0.0.1) 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_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_PASSWORD | string | No | Password for MQTT (comment out if not required)
MQTT_PORT | int | No | Defaults to 1883 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 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 | bool | No | MQTT Discovery for Home Assistant Defaults to true
MQTT_DISCOVERY_PREFIX | string | No | MQTT Discovery Prefix for Home Assistant. Defaults to homeassistant 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... 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! Up to 3 users possible as long as weights do not overlap!
Option | Type | Required | Description 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_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_SEX | string | Yes | male / female
USER1_NAME | string | Yes | Name of the user USER1_NAME | string | Yes | Name of the user
USER1_HEIGHT | int | Yes | Height (in cm) of the user USER1_HEIGHT | int | Yes | Height (in cm) of the user
USER1_DOB | string | Yes | DOB (in yyyy-mm-dd format) 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_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 USER2_SEX | string | No | male / female. Defaults to female
USER2_NAME | string | No | Name of the user USER2_NAME | string | No | Name of the user. Defaults to Serena
USER2_HEIGHT | int | No |Height (in cm) of the user USER2_HEIGHT | int | No |Height (in cm) of the user. Defaults to 95
USER2_DOB | string | No | DOB (in yyyy-mm-dd format) USER2_DOB | string | No | DOB (in yyyy-mm-dd format). Defaults to 1990-01-01
USER3_SEX | string | No | male / female USER3_SEX | string | No | male / female. Defaults to female
USER3_NAME | string | No | Name of the user USER3_NAME | string | No | Name of the user. Defaults to Missy
USER3_HEIGHT | int | No |Height (in cm) of the user USER3_HEIGHT | int | No |Height (in cm) of the user. Defaults to 150
USER3_DOB | string | No | DOB (in yyyy-mm-dd format) USER3_DOB | string | No | DOB (in yyyy-mm-dd format). Defaults to 1990-01-01
7. Start the add-on 7. Start the add-on
## Home-Assistant Setup: ## Home-Assistant Setup:
Under the `sensor` block, enter as many blocks as users configured in your environment variables: Under the `sensor` block, enter as many blocks as users configured in your environment variables:
```yaml ```yaml
- platform: mqtt - platform: mqtt
name: "Example Name Weight" name: "Example Name Weight"
state_topic: "miScale/USER_NAME/weight" state_topic: "miScale/USER_NAME/weight"
value_template: "{{ value_json['Weight'] }}" value_template: "{{ value_json['Weight'] }}"
unit_of_measurement: "kg" unit_of_measurement: "kg"
json_attributes_topic: "miScale/USER_NAME/weight" json_attributes_topic: "miScale/USER_NAME/weight"
icon: mdi:scale-bathroom icon: mdi:scale-bathroom
- platform: mqtt - platform: mqtt
name: "Example Name BMI" name: "Example Name BMI"
state_topic: "miScale/USER_NAME/weight" state_topic: "miScale/USER_NAME/weight"
value_template: "{{ value_json['BMI'] }}" value_template: "{{ value_json['BMI'] }}"
icon: mdi:human-pregnant 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.png)
![Mi Scale](https://github.com/lolouk44/xiaomi_mi_scale/blob/master/Screenshots/HA_Lovelace_Card_Details.png) ![Mi Scale](https://github.com/lolouk44/xiaomi_mi_scale/blob/master/Screenshots/HA_Lovelace_Card_Details.png)
## Acknowledgements: ## Acknowledgements:
Thanks to @syssi (https://gist.github.com/syssi/4108a54877406dc231d95514e538bde9) and @prototux (https://github.com/wiecosystem/Bluetooth) for their initial code 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 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 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", "name": "Xiaomi Mi Scale",
"version": "0.1.6", "version": "0.1.6",
"slug": "xiaomi_mi_scale", "slug": "xiaomi_mi_scale",
"description": "Read weight measurements from Xiamomi scale via BLE", "description": "Read weight measurements from Xiamomi scale via BLE",
"url": "https://github.com/lolouk44/xiaomi_mi_scale_ha_add_on", "url": "https://github.com/lolouk44/xiaomi_mi_scale_ha_add_on",
"image": "lolouk44/xiaomi-mi-scale", "image": "lolouk44/xiaomi-mi-scale",
"arch": ["armhf", "armv7", "aarch64", "amd64", "i386"], "arch": ["armhf", "armv7", "aarch64", "amd64", "i386"],
"startup": "before", "startup": "before",
"boot": "auto", "boot": "auto",
"panel_admin": false, "panel_admin": false,
"host_network": true, "host_network": true,
"privileged": ["NET_ADMIN", "SYS_ADMIN"], "privileged": ["NET_ADMIN", "SYS_ADMIN"],
"options": { "options": {
"HCI_DEV": "hci0", "HCI_DEV": "hci0",
"MISCALE_MAC": "00:00:00:00:00:00", "MISCALE_MAC": "00:00:00:00:00:00",
"MQTT_PREFIX": "miScale", "MQTT_PREFIX": "miScale",
"MQTT_HOST": "192.168.0.1", "MQTT_HOST": "192.168.0.1",
"MQTT_USERNAME": "", "MQTT_USERNAME": "user",
"MQTT_PASSWORD": "", "MQTT_PASSWORD": "passwd",
"MQTT_PORT": 1883, "MQTT_PORT": 1883,
"TIME_INTERVAL": 30, "TIME_INTERVAL": 30,
"MQTT_DISCOVERY": true, "MQTT_DISCOVERY": true,
"MQTT_DISCOVERY_PREFIX": "homeassistant", "MQTT_DISCOVERY_PREFIX": "homeassistant",
"USER1_GT": 70, "USER1_GT": 70,
"USER1_SEX": "male", "USER1_SEX": "male",
"USER1_NAME": "Jo", "USER1_NAME": "Jo",
"USER1_HEIGHT": 175, "USER1_HEIGHT": 175,
"USER1_DOB": "1990-01-01", "USER1_DOB": "1990-01-01",
"USER2_LT": 35, "USER2_LT": 35,
"USER2_SEX": "female", "USER2_SEX": "female",
"USER2_NAME": "Serena", "USER2_NAME": "Serena",
"USER2_HEIGHT": 95, "USER2_HEIGHT": 95,
"USER2_DOB": "1990-01-01", "USER2_DOB": "1990-01-01",
"USER3_SEX": "female", "USER3_SEX": "female",
"USER3_NAME": "Missy", "USER3_NAME": "Missy",
"USER3_HEIGHT": 150, "USER3_HEIGHT": 150,
"USER3_DOB": "1990-01-01" "USER3_DOB": "1990-01-01"
}, },
"schema": { "schema": {
"HCI_DEV": "str", "HCI_DEV": "str?",
"MISCALE_MAC": "str", "MISCALE_MAC": "str",
"MQTT_PREFIX": "str", "MQTT_PREFIX": "str?",
"MQTT_HOST": "str", "MQTT_HOST": "str",
"MQTT_USERNAME": "str", "MQTT_USERNAME": "str?",
"MQTT_PASSWORD": "str", "MQTT_PASSWORD": "str?",
"MQTT_PORT": "int", "MQTT_PORT": "int?",
"TIME_INTERVAL": "int", "TIME_INTERVAL": "int?",
"MQTT_DISCOVERY": "bool", "MQTT_DISCOVERY": "bool?",
"MQTT_DISCOVERY_PREFIX": "str", "MQTT_DISCOVERY_PREFIX": "str?",
"USER1_GT": "int", "USER1_GT": "int",
"USER1_SEX": "str", "USER1_SEX": "str",
"USER1_NAME": "str", "USER1_NAME": "str",
"USER1_HEIGHT": "int", "USER1_HEIGHT": "int",
"USER1_DOB": "str", "USER1_DOB": "str",
"USER2_LT": "int?", "USER2_LT": "int?",
"USER2_SEX": "str?", "USER2_SEX": "str?",
"USER2_NAME": "str?", "USER2_NAME": "str?",
"USER2_HEIGHT": "int?", "USER2_HEIGHT": "int?",
"USER2_DOB": "str?", "USER2_DOB": "str?",
"USER3_SEX": "str?", "USER3_SEX": "str?",
"USER3_NAME": "str?", "USER3_NAME": "str?",
"USER3_HEIGHT": "int?", "USER3_HEIGHT": "int?",
"USER3_DOB": "str?" "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 #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import print_function from __future__ import print_function
import argparse import argparse
import binascii import binascii
import time import time
import os import os
import sys import sys
import subprocess import subprocess
from bluepy import btle from bluepy import btle
from bluepy.btle import Scanner, BTLEDisconnectError, BTLEManagementError, DefaultDelegate from bluepy.btle import Scanner, BTLEDisconnectError, BTLEManagementError, DefaultDelegate
import paho.mqtt.publish as publish import paho.mqtt.publish as publish
from datetime import datetime from datetime import datetime
import json import json
import Xiaomi_Scale_Body_Metrics import Xiaomi_Scale_Body_Metrics
# First Log msg # First Log msg
sys.stdout.write(' \n') sys.stdout.write(' \n')
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") sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Starting Xiaomi mi Scale...\n")
# Configuraiton... # Configuraiton...
# Trying To Load Config From options.json (HA Add-On) # Trying To Load Config From options.json (HA Add-On)
try: try:
with open('/data/options.json') as json_file: 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") 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) data = json.load(json_file)
MISCALE_MAC = data["MISCALE_MAC"] try:
MQTT_USERNAME = None if(data["MQTT_USERNAME"] == "") else data["MQTT_USERNAME"] MISCALE_MAC = data["MISCALE_MAC"]
MQTT_PASSWORD = None if(data["MQTT_PASSWORD"] == "") else data["MQTT_PASSWORD"] except:
MQTT_HOST = data["MQTT_HOST"] sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - MAC Address not provided...\n")
MQTT_PORT = int(data["MQTT_PORT"]) raise
MQTT_PREFIX = data["MQTT_PREFIX"] try:
TIME_INTERVAL = int(data["TIME_INTERVAL"]) MQTT_USERNAME = data["MQTT_USERNAME"]
MQTT_DISCOVERY = data["MQTT_DISCOVERY"] except:
MQTT_DISCOVERY_PREFIX = data["MQTT_DISCOVERY_PREFIX"] MQTT_USERNAME = None
HCI_DEV = data["HCI_DEV"][-1] pass
try:
# User Variables... MQTT_PASSWORD = data["MQTT_PASSWORD"]
USER1_GT = int(data["USER1_GT"]) except:
USER1_SEX = data["USER1_SEX"] MQTT_PASSWORD = None
USER1_NAME = data["USER1_NAME"] pass
USER1_HEIGHT = int(data["USER1_HEIGHT"]) try:
USER1_DOB = data["USER1_DOB"] MQTT_HOST = data["MQTT_HOST"]
except:
USER2_LT = int(data["USER2_LT"]) sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - MQTT Host not provided...\n")
USER2_SEX = data["USER2_SEX"] raise
USER2_NAME = data["USER2_NAME"] try:
USER2_HEIGHT = int(data["USER2_HEIGHT"]) MQTT_PORT = int(data["MQTT_PORT"])
USER2_DOB = data["USER2_DOB"] except:
MQTT_PORT = 1883
USER3_SEX = data["USER3_SEX"] pass
USER3_NAME = data["USER3_NAME"] try:
USER3_HEIGHT = int(data["USER3_HEIGHT"]) MQTT_PREFIX = data["MQTT_PREFIX"]
USER3_DOB = data["USER3_DOB"] except:
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Config Loaded...\n") MQTT_PREFIX = "miScale"
pass
# Failed to open options.json, Loading Config From Environment (Not HA Add-On) try:
except FileNotFoundError: TIME_INTERVAL = int(data["TIME_INTERVAL"])
pass except:
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Loading Config From OS Environment...\n") TIME_INTERVAL = 30
MISCALE_MAC = os.getenv('MISCALE_MAC', '') pass
MQTT_USERNAME = os.getenv('MQTT_USERNAME', 'username') try:
MQTT_PASSWORD = os.getenv('MQTT_PASSWORD', None) MQTT_DISCOVERY = data["MQTT_DISCOVERY"]
MQTT_HOST = os.getenv('MQTT_HOST', '127.0.0.1') except:
MQTT_PORT = int(os.getenv('MQTT_PORT', 1883)) MQTT_DISCOVERY = True
MQTT_PREFIX = os.getenv('MQTT_PREFIX', 'miscale') pass
TIME_INTERVAL = int(os.getenv('TIME_INTERVAL', 30)) try:
MQTT_DISCOVERY = os.getenv('MQTT_DISCOVERY',True) MQTT_DISCOVERY_PREFIX = data["MQTT_DISCOVERY_PREFIX"]
MQTT_DISCOVERY_PREFIX = os.getenv('MQTT_DISCOVERY_PREFIX','homeassistant') except:
HCI_DEV = os.getenv('HCI_DEV', 'hci0')[-1] MQTT_DISCOVERY_PREFIX = "homeassistant"
pass
# User Variables... try:
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 HCI_DEV = data["HCI_DEV"][-1]
USER1_SEX = os.getenv('USER1_SEX', 'male') except:
USER1_NAME = os.getenv('USER1_NAME', 'David') # Name of the user HCI_DEV = "hci0"[-1]
USER1_HEIGHT = int(os.getenv('USER1_HEIGHT', '175')) # Height (in cm) of the user pass
USER1_DOB = os.getenv('USER1_DOB', '1988-09-30') # DOB (in yyyy-mm-dd format) try:
USER1_GT = int(data["USER1_GT"])
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 except:
USER2_SEX = os.getenv('USER2_SEX', 'female') sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - USER1_GT not provided...\n")
USER2_NAME = os.getenv('USER2_NAME', 'Joanne') # Name of the user raise
USER2_HEIGHT = int(os.getenv('USER2_HEIGHT', '155')) # Height (in cm) of the user try:
USER2_DOB = os.getenv('USER2_DOB', '1988-10-20') # DOB (in yyyy-mm-dd format) USER1_SEX = data["USER1_SEX"]
except:
USER3_SEX = os.getenv('USER3_SEX', 'male') sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - USER1_SEX not provided...\n")
USER3_NAME = os.getenv('USER3_NAME', 'Unknown User') # Name of the user raise
USER3_HEIGHT = int(os.getenv('USER3_HEIGHT', '175')) # Height (in cm) of the user try:
USER3_DOB = os.getenv('USER3_DOB', '1988-01-01') # DOB (in yyyy-mm-dd format) USER1_NAME = data["USER1_NAME"]
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Config Loaded...\n") except:
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - USER1_NAME not provided...\n")
OLD_MEASURE = '' raise
try:
def discovery(): USER1_HEIGHT = int(data["USER1_HEIGHT"])
for MQTTUser in (USER1_NAME,USER2_NAME,USER3_NAME): except:
message = '{"name": "' + MQTTUser + ' Weight",' sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - USER1_HEIGHT not provided...\n")
message+= '"state_topic": "miScale/' + MQTTUser + '/weight","value_template": "{{ value_json.Weight }}","unit_of_measurement": "kg",' raise
message+= '"json_attributes_topic": "miScale/' + MQTTUser + '/weight","icon": "mdi:scale-bathroom"}' try:
publish.single( USER1_DOB = data["USER1_DOB"]
MQTT_DISCOVERY_PREFIX + '/sensor/' + MQTT_PREFIX + '/' + MQTTUser + '/config', except:
message, sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - USER1_DOB not provided...\n")
retain=True, raise
hostname=MQTT_HOST, try:
port=MQTT_PORT, USER2_LT = int(data["USER2_LT"])
auth={'username':MQTT_USERNAME, 'password':MQTT_PASSWORD} except:
) USER2_LT = USER1_GT
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Discovery Completed...\n") pass
try:
USER2_SEX = data["USER2_SEX"]
class ScanProcessor(): except:
def GetAge(self, d1): USER2_SEX = "female"
d1 = datetime.strptime(d1, "%Y-%m-%d") pass
d2 = datetime.strptime(datetime.today().strftime('%Y-%m-%d'),'%Y-%m-%d') try:
return abs((d2 - d1).days)/365 USER2_NAME = data["USER2_NAME"]
except:
def __init__(self): USER2_NAME = "Serena"
DefaultDelegate.__init__(self) pass
try:
def handleDiscovery(self, dev, isNewDev, isNewData): USER2_HEIGHT = int(data["USER2_HEIGHT"])
global OLD_MEASURE except:
if dev.addr == MISCALE_MAC.lower() and isNewDev: USER2_HEIGHT = 95
for (sdid, desc, data) in dev.getScanData(): pass
### Xiaomi V1 Scale ### try:
if data.startswith('1d18') and sdid == 22: USER2_DOB = data["USER2_DOB"]
measunit = data[4:6] except:
measured = int((data[8:10] + data[6:8]), 16) * 0.01 USER2_DOB = "1990-01-01"
unit = '' pass
if measunit.startswith(('03', 'b3')): unit = 'lbs' try:
if measunit.startswith(('12', 'b2')): unit = 'jin' USER3_SEX = data["USER3_SEX"]
if measunit.startswith(('22', 'a2')): unit = 'kg' ; measured = measured / 2 except:
if unit: USER3_SEX = "female"
if OLD_MEASURE != round(measured, 2): pass
self._publish(round(measured, 2), unit, str(datetime.today().strftime('%Y-%m-%d-%H:%M:%S')), "", "") try:
OLD_MEASURE = round(measured, 2) USER3_NAME = data["USER3_NAME"]
except:
### Xiaomi V2 Scale ### USER3_NAME = "Missy"
if data.startswith('1b18') and sdid == 22: pass
data2 = bytes.fromhex(data[4:]) try:
ctrlByte1 = data2[1] USER3_HEIGHT = int(data["USER3_HEIGHT"])
isStabilized = ctrlByte1 & (1<<5) except:
hasImpedance = ctrlByte1 & (1<<1) USER3_HEIGHT = 150
pass
measunit = data[4:6] try:
measured = int((data[28:30] + data[26:28]), 16) * 0.01 USER3_DOB = data["USER3_DOB"]
unit = '' except:
if measunit == "03": unit = 'lbs' USER3_DOB = "1990-01-01"
if measunit == "02": unit = 'kg' ; measured = measured / 2 pass
#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") sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Config Loaded...\n")
miimpedance = str(int((data[24:26] + data[22:24]), 16))
if unit and isStabilized: # Failed to open options.json, Loading Config From Environment (Not HA Add-On)
if OLD_MEASURE != round(measured, 2) + int(miimpedance): except FileNotFoundError:
self._publish(round(measured, 2), unit, str(datetime.today().strftime('%Y-%m-%d-%H:%M:%S')), hasImpedance, miimpedance) pass
OLD_MEASURE = round(measured, 2) + int(miimpedance) 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')
def _publish(self, weight, unit, mitdatetime, hasImpedance, miimpedance): MQTT_PASSWORD = os.getenv('MQTT_PASSWORD', None)
if int(weight) > USER1_GT: MQTT_HOST = os.getenv('MQTT_HOST', '127.0.0.1')
user = USER1_NAME MQTT_PORT = int(os.getenv('MQTT_PORT', 1883))
height = USER1_HEIGHT MQTT_PREFIX = os.getenv('MQTT_PREFIX', 'miscale')
age = self.GetAge(USER1_DOB) TIME_INTERVAL = int(os.getenv('TIME_INTERVAL', 30))
sex = USER1_SEX MQTT_DISCOVERY = os.getenv('MQTT_DISCOVERY',True)
elif int(weight) < USER2_LT: MQTT_DISCOVERY_PREFIX = os.getenv('MQTT_DISCOVERY_PREFIX','homeassistant')
user = USER2_NAME HCI_DEV = os.getenv('HCI_DEV', 'hci0')[-1]
height = USER2_HEIGHT
age = self.GetAge(USER2_DOB) # User Variables...
sex = USER2_SEX 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
else: USER1_SEX = os.getenv('USER1_SEX', 'male')
user = USER3_NAME USER1_NAME = os.getenv('USER1_NAME', 'David') # Name of the user
height = USER3_HEIGHT USER1_HEIGHT = int(os.getenv('USER1_HEIGHT', '175')) # Height (in cm) of the user
age = self.GetAge(USER3_DOB) USER1_DOB = os.getenv('USER1_DOB', '1988-09-30') # DOB (in yyyy-mm-dd format)
sex = USER3_SEX
lib = Xiaomi_Scale_Body_Metrics.bodyMetrics(weight, height, age, sex, 0) 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
message = '{' USER2_SEX = os.getenv('USER2_SEX', 'female')
message += '"Weight":"' + "{:.2f}".format(weight) + '"' USER2_NAME = os.getenv('USER2_NAME', 'Joanne') # Name of the user
message += ',"BMI":"' + "{:.2f}".format(lib.getBMI()) + '"' USER2_HEIGHT = int(os.getenv('USER2_HEIGHT', '155')) # Height (in cm) of the user
message += ',"Basal Metabolism":"' + "{:.2f}".format(lib.getBMR()) + '"' USER2_DOB = os.getenv('USER2_DOB', '1988-10-20') # DOB (in yyyy-mm-dd format)
message += ',"Visceral Fat":"' + "{:.2f}".format(lib.getVisceralFat()) + '"'
USER3_SEX = os.getenv('USER3_SEX', 'male')
if hasImpedance: USER3_NAME = os.getenv('USER3_NAME', 'Unknown User') # Name of the user
lib = Xiaomi_Scale_Body_Metrics.bodyMetrics(weight, height, age, sex, int(miimpedance)) USER3_HEIGHT = int(os.getenv('USER3_HEIGHT', '175')) # Height (in cm) of the user
bodyscale = ['Obese', 'Overweight', 'Thick-set', 'Lack-exerscise', 'Balanced', 'Balanced-muscular', 'Skinny', 'Balanced-skinny', 'Skinny-muscular'] USER3_DOB = os.getenv('USER3_DOB', '1988-01-01') # DOB (in yyyy-mm-dd format)
message += ',"Lean Body Mass":"' + "{:.2f}".format(lib.getLBMCoefficient()) + '"' sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Config Loaded...\n")
message += ',"Body Fat":"' + "{:.2f}".format(lib.getFatPercentage()) + '"'
message += ',"Water":"' + "{:.2f}".format(lib.getWaterPercentage()) + '"' OLD_MEASURE = ''
message += ',"Bone Mass":"' + "{:.2f}".format(lib.getBoneMass()) + '"'
message += ',"Muscle Mass":"' + "{:.2f}".format(lib.getMuscleMass()) + '"' def discovery():
message += ',"Protein":"' + "{:.2f}".format(lib.getProteinPercentage()) + '"' for MQTTUser in (USER1_NAME,USER2_NAME,USER3_NAME):
message += ',"Body Type":"' + str(bodyscale[lib.getBodyType()]) + '"' message = '{"name": "' + MQTTUser + ' Weight",'
message += ',"Metabolic Age":"' + "{:.0f}".format(lib.getMetabolicAge()) + '"' 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"}'
message += ',"TimeStamp":"' + mitdatetime + '"' publish.single(
message += '}' MQTT_DISCOVERY_PREFIX + '/sensor/' + MQTT_PREFIX + '/' + MQTTUser + '/config',
try: message,
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Publishing data to topic {MQTT_PREFIX + '/' + user + '/weight'}: {message}\n") retain=True,
publish.single( hostname=MQTT_HOST,
MQTT_PREFIX + '/' + user + '/weight', port=MQTT_PORT,
message, auth={'username':MQTT_USERNAME, 'password':MQTT_PASSWORD}
# qos=1, #Removed qos=1 as incorrect connection details will result in the client waiting for ack from broker )
retain=True, sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Discovery Completed...\n")
hostname=MQTT_HOST,
port=MQTT_PORT,
auth={'username':MQTT_USERNAME, 'password':MQTT_PASSWORD} class ScanProcessor():
) def GetAge(self, d1):
sys.stdout.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Data Published ...\n") d1 = datetime.strptime(d1, "%Y-%m-%d")
except Exception as error: d2 = datetime.strptime(datetime.today().strftime('%Y-%m-%d'),'%Y-%m-%d')
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Could not publish to MQTT: {error}\n") return abs((d2 - d1).days)/365
raise
def __init__(self):
def main(): DefaultDelegate.__init__(self)
if MQTT_DISCOVERY:
discovery() def handleDiscovery(self, dev, isNewDev, isNewData):
BluetoothFailCounter = 0 global OLD_MEASURE
while True: if dev.addr == MISCALE_MAC.lower() and isNewDev:
try: for (sdid, desc, data) in dev.getScanData():
scanner = btle.Scanner(HCI_DEV).withDelegate(ScanProcessor()) ### Xiaomi V1 Scale ###
scanner.scan(5) # Adding passive=True to try and fix issues on RPi devices if data.startswith('1d18') and sdid == 22:
except BTLEDisconnectError as error: measunit = data[4:6]
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - btle disconnected: {error}\n") measured = int((data[8:10] + data[6:8]), 16) * 0.01
pass unit = ''
except BTLEManagementError as error: if measunit.startswith(('03', 'b3')): unit = 'lbs'
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Bluetooth connection error: {error}\n") if measunit.startswith(('12', 'b2')): unit = 'jin'
if BluetoothFailCounter >= 4: if measunit.startswith(('22', 'a2')): unit = 'kg' ; measured = measured / 2
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - 5+ Bluetooth connection errors. Resetting Bluetooth...\n") if unit:
cmd = 'hciconfig hci0 reset' if OLD_MEASURE != round(measured, 2):
ps = subprocess.Popen(cmd, shell=True) self._publish(round(measured, 2), unit, str(datetime.today().strftime('%Y-%m-%d-%H:%M:%S')), "", "")
time.sleep(30) OLD_MEASURE = round(measured, 2)
BluetoothFailCounter = 0
else: ### Xiaomi V2 Scale ###
BluetoothFailCounter+=1 if data.startswith('1b18') and sdid == 22:
pass data2 = bytes.fromhex(data[4:])
except Exception as error: ctrlByte1 = data2[1]
sys.stderr.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - Error while running the script: {error}\n") isStabilized = ctrlByte1 & (1<<5)
pass hasImpedance = ctrlByte1 & (1<<1)
else:
BluetoothFailCounter = 0 measunit = data[4:6]
time.sleep(TIME_INTERVAL) measured = int((data[28:30] + data[26:28]), 16) * 0.01
unit = ''
if __name__ == "__main__": if measunit == "03": unit = 'lbs'
main() 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: class bodyScales:
def __init__(self, age, height, sex, weight, scaleType='xiaomi'): def __init__(self, age, height, sex, weight, scaleType='xiaomi'):
self.age = age self.age = age
self.height = height self.height = height
self.sex = sex self.sex = sex
self.weight = weight self.weight = weight
if scaleType == 'xiaomi': if scaleType == 'xiaomi':
self.scaleType = 'xiaomi' self.scaleType = 'xiaomi'
else: else:
self.scaleType = 'holtek' self.scaleType = 'holtek'
# Get BMI scale # Get BMI scale
def getBMIScale(self): def getBMIScale(self):
if self.scaleType == 'xiaomi': if self.scaleType == 'xiaomi':
# Amazfit/new mi fit # Amazfit/new mi fit
#return [18.5, 24, 28] #return [18.5, 24, 28]
# Old mi fit // amazfit for body figure # Old mi fit // amazfit for body figure
return [18.5, 25.0, 28.0, 32.0] return [18.5, 25.0, 28.0, 32.0]
elif self.scaleType == 'holtek': elif self.scaleType == 'holtek':
return [18.5, 25.0, 30.0] return [18.5, 25.0, 30.0]
# Get fat percentage scale # Get fat percentage scale
def getFatPercentageScale(self): def getFatPercentageScale(self):
# The included tables where quite strange, maybe bogus, replaced them with better ones... # The included tables where quite strange, maybe bogus, replaced them with better ones...
if self.scaleType == 'xiaomi': if self.scaleType == 'xiaomi':
scales = [ scales = [
{'min': 0, 'max': 12, 'female': [12.0, 21.0, 30.0, 34.0], 'male': [7.0, 16.0, 25.0, 30.0]}, {'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': 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': 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': 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': 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': 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]}, {'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': elif self.scaleType == 'holtek':
scales = [ scales = [
{'min': 0, 'max': 21, 'female': [18, 23, 30, 35], 'male': [8, 14, 21, 25]}, {'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': 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': 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': 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': 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': 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': 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': 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]}, {'min': 56, 'max': 100, 'female': [27, 32, 37, 40], 'male': [21, 26, 31, 34]},
] ]
for scale in scales: for scale in scales:
if self.age >= scale['min'] and self.age < scale['max']: if self.age >= scale['min'] and self.age < scale['max']:
return scale[self.sex] return scale[self.sex]
# Get muscle mass scale # Get muscle mass scale
def getMuscleMassScale(self): def getMuscleMassScale(self):
if self.scaleType == 'xiaomi': if self.scaleType == 'xiaomi':
scales = [ scales = [
{'min': {'male': 170, 'female': 160}, 'female': [36.5, 42.6], 'male': [49.4, 59.5]}, {'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': 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]}, {'min': {'male': 0, 'female': 0}, 'female': [29.1, 34.8], 'male': [38.5, 46.6]},
] ]
elif self.scaleType == 'holtek': elif self.scaleType == 'holtek':
scales = [ scales = [
{'min': {'male': 170, 'female': 170}, 'female': [36.5, 42.5], 'male': [49.5, 59.4]}, {'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': 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]} {'min': {'male': 0, 'female': 0}, 'female': [29.1, 34.7], 'male': [38.5, 46.5]}
] ]
for scale in scales: for scale in scales:
if self.height >= scale['min'][self.sex]: if self.height >= scale['min'][self.sex]:
return scale[self.sex] return scale[self.sex]
# Get water percentage scale # Get water percentage scale
def getWaterPercentageScale(self): def getWaterPercentageScale(self):
if self.scaleType == 'xiaomi': if self.scaleType == 'xiaomi':
if self.sex == 'male': if self.sex == 'male':
return [55.0, 65.1] return [55.0, 65.1]
elif self.sex == 'female': elif self.sex == 'female':
return [45.0, 60.1] return [45.0, 60.1]
elif self.scaleType == 'holtek': elif self.scaleType == 'holtek':
return [53, 67] return [53, 67]
# Get visceral fat scale # Get visceral fat scale
def getVisceralFatScale(self): def getVisceralFatScale(self):
# Actually the same in mi fit/amazfit and holtek's sdk # Actually the same in mi fit/amazfit and holtek's sdk
return [10.0, 15.0] return [10.0, 15.0]
# Get bone mass scale # Get bone mass scale
def getBoneMassScale(self): def getBoneMassScale(self):
if self.scaleType == 'xiaomi': if self.scaleType == 'xiaomi':
scales = [ scales = [
{'male': {'min': 75.0, 'scale': [2.0, 4.2]}, 'female': {'min': 60.0, 'scale': [1.8, 3.9]}}, {'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': 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]}}, {'male': {'min': 0.0, 'scale': [1.6, 3.9]}, 'female': {'min': 0.0, 'scale': [1.3, 3.6]}},
] ]
for scale in scales: for scale in scales:
if self.weight >= scale[self.sex]['min']: if self.weight >= scale[self.sex]['min']:
return scale[self.sex]['scale'] return scale[self.sex]['scale']
elif self.scaleType == 'holtek': elif self.scaleType == 'holtek':
scales = [ scales = [
{'female': {'min': 60, 'optimal': 2.5}, 'male': {'min': 75, 'optimal': 3.2}}, {'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': 45, 'optimal': 2.2}, 'male': {'min': 69, 'optimal': 2.9}},
{'female': {'min': 0, 'optimal': 1.8}, 'male': {'min': 0, 'optimal': 2.5}} {'female': {'min': 0, 'optimal': 1.8}, 'male': {'min': 0, 'optimal': 2.5}}
] ]
for scale in scales: for scale in scales:
if self.weight >= scale[self.sex]['min']: if self.weight >= scale[self.sex]['min']:
return [scale[self.sex]['optimal']-1, scale[self.sex]['optimal']+1] return [scale[self.sex]['optimal']-1, scale[self.sex]['optimal']+1]
# Get BMR scale # Get BMR scale
def getBMRScale(self): def getBMRScale(self):
if self.scaleType == 'xiaomi': if self.scaleType == 'xiaomi':
coefficients = { coefficients = {
'male': {30: 21.6, 50: 20.07, 100: 19.35}, 'male': {30: 21.6, 50: 20.07, 100: 19.35},
'female': {30: 21.24, 50: 19.53, 100: 18.63} 'female': {30: 21.24, 50: 19.53, 100: 18.63}
} }
elif self.scaleType == 'holtek': elif self.scaleType == 'holtek':
coefficients = { coefficients = {
'female': {12: 34, 15: 29, 17: 24, 29: 22, 50: 20, 120: 19}, '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} 'male': {12: 36, 15: 30, 17: 26, 29: 23, 50: 21, 120: 20}
} }
for age, coefficient in coefficients[self.sex].items(): for age, coefficient in coefficients[self.sex].items():
if self.age < age: if self.age < age:
return [self.weight * coefficient] return [self.weight * coefficient]
# Get protein scale (hardcoded in mi fit) # Get protein scale (hardcoded in mi fit)
def getProteinPercentageScale(self): def getProteinPercentageScale(self):
# Actually the same in mi fit and holtek's sdk # Actually the same in mi fit and holtek's sdk
return [16, 20] return [16, 20]
# Get ideal weight scale (BMI scale converted to weights) # Get ideal weight scale (BMI scale converted to weights)
def getIdealWeightScale(self): def getIdealWeightScale(self):
scale = [] scale = []
for bmiScale in self.getBMIScale(): for bmiScale in self.getBMIScale():
scale.append((bmiScale*self.height)*self.height/10000) scale.append((bmiScale*self.height)*self.height/10000)
return scale return scale
# Get Body Score scale # Get Body Score scale
def getBodyScoreScale(self): def getBodyScoreScale(self):
# very bad, bad, normal, good, better # very bad, bad, normal, good, better
return [50.0, 60.0, 80.0, 90.0] return [50.0, 60.0, 80.0, 90.0]
# Return body type scale # Return body type scale
def getBodyTypeScale(self): def getBodyTypeScale(self):
return ['obese', 'overweight', 'thick-set', 'lack-exerscise', 'balanced', 'balanced-muscular', 'skinny', 'balanced-skinny', 'skinny-muscular'] return ['obese', 'overweight', 'thick-set', 'lack-exerscise', 'balanced', 'balanced-muscular', 'skinny', 'balanced-skinny', 'skinny-muscular']

View File

@ -1,24 +1,24 @@
#!/bin/bash #!/bin/bash
export MISCALE_MAC=00:00:00:00:00:00 # Mac address of your scale export MISCALE_MAC=00:00:00:00:00:00 # Mac address of your scale
export MQTT_PREFIX=miScale 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_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_SEX=male
export USER1_NAME=Jo # Name of the user export USER1_NAME=Jo # Name of the user
export USER1_HEIGHT=175 # Height (in cm) 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 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_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_SEX=female
export USER2_NAME=Sarah # Name of the user export USER2_NAME=Sarah # Name of the user
export USER2_HEIGHT=95 # Height (in cm) 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 USER2_DOB=1990-01-01 # DOB (in yyyy-mm-dd format)
export USER3_SEX=female export USER3_SEX=female
export USER3_NAME=Missy # Name of the user export USER3_NAME=Missy # Name of the user
export USER3_HEIGHT=150 # Height (in cm) 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) export USER3_DOB=1990-01-01 # DOB (in yyyy-mm-dd format)
MY_PWD="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" MY_PWD="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
python3 $MY_PWD/Xiaomi_Scale.py 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"
}