Skip to content

Commit

Permalink
0.1.17b - BLE Scan Passive Option
Browse files Browse the repository at this point in the history
  • Loading branch information
lolouk44 committed Feb 10, 2021
1 parent e55acfa commit bb689ee
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 62 deletions.
27 changes: 9 additions & 18 deletions mi-scale/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,10 @@

Add-On for [HomeAssistant](https://www.home-assistant.io/) to read weight measurements from Xiaomi Body Scales.

## BREAKING CHANGE:
Please note there was a breaking change in 0.1.8. The MQTT message json attributes are now in lower snake_case to be compliant with Home-Assistant Attributes.
This means Home-Assistant sensor configuration needs to be adjusted.
For example
`value_template: "{{ value_json['Weight'] }}"`
Needs to be replaced with
`value_template: "{{ value_json['weight'] }}"`
(note the lowercase `w` in `weight`)

## Supported Scales:
Name | Model | Picture
--- | --- | :---:
[Mi Smart Scale 2](https://www.mi.com/global/scale)                                                                                               | XMTZCO1HM, XMTZC04HM | ![Mi Scale_2](https://raw.githubusercontent.com/lolouk44/xiaomi_mi_scale/master/Screenshots/Mi_Smart_Scale_2_Thumb.png)
[Mi Smart Scale 2](https://www.mi.com/global/scale)                                                                                               | XMTZC04HM | ![Mi Scale_2](https://raw.githubusercontent.com/lolouk44/xiaomi_mi_scale/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://raw.githubusercontent.com/lolouk44/xiaomi_mi_scale/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://raw.githubusercontent.com/lolouk44/xiaomi_mi_scale/master/Screenshots/Mi_Body_Composition_Scale_2_Thumb.png)

Expand All @@ -35,7 +26,7 @@ Name | Model | Picture
Option | Type | Required | Description
--- | --- | --- | ---
HCI_DEV | string | No | Bluetooth hci device to use. Defaults to hci0
RPI_BLUEPY_FIX | bool | No | Activates potential fix for Bluepy issues on RaspberryPi "scan(5, passive=true)"
BLUEPY_PASSIVE_SCAN | bool | No | Activates potential fix for Bluepy issues on RaspberryPi "scan(5, passive=true)". Defaults to true
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)
Expand All @@ -49,22 +40,21 @@ MQTT_DISCOVERY_PREFIX | string | No | MQTT Discovery Prefix for Home Assistant.

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!

Here is the logic used to assign a measured weight to a user:
```
if [measured value in kg] is greater than USER1_GT, assign it to USER1
else if [measured value in kg] is less than USER2_LT, assign it to USER2
else assign it to USER3 (e.g. USER2_LT < [measured value in kg] < USER1_GT)
if [measured value] is greater than USER1_GT, assign it to USER1
else if [measured value] is less than USER2_LT, assign it to USER2
else assign it to USER3 (e.g. USER2_LT < [measured value] < USER1_GT)
```

Option | Type | Required | Description
--- | --- | --- | ---
USER1_GT | int | Yes | If the weight (in kg) 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_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 (in kg) is less than this number, we'll assume that we're weighing User #2. Defaults to USER1_GT Value
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
Expand All @@ -80,6 +70,7 @@ USER3_DOB | string | No | DOB (in yyyy-mm-dd format). Defaults to 1990-01-01

## Home-Assistant Setup:
Under the `sensor` block, enter as many blocks as users configured in your environment variables:
(Note: only weight entities are automatically added via the MQTT Discovery)

```yaml
- platform: mqtt
Expand Down Expand Up @@ -108,4 +99,4 @@ Thanks to @syssi (https://gist.github.com/syssi/4108a54877406dc231d95514e538bde9

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), [@AiiR42](https://github.com/AiiR42) for their PRs and collaboration
Thanks to [@bpaulin](https://github.com/bpaulin), [@fabir-git](https://github.com/fabir-git)for their PRs and collaboration
10 changes: 5 additions & 5 deletions mi-scale/config.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
{
"name": "Xiaomi Mi Scale",
"version": "0.1.17a",
"version": "0.1.17b",
"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-ha-add-on",
"arch": ["armhf", "armv7", "aarch64", "amd64", "i386"],
"startup": "before",
"startup": "application",
"boot": "auto",
"panel_admin": false,
"host_network": true,
"privileged": ["NET_ADMIN", "SYS_ADMIN"],

"options": {
"HCI_DEV": "hci0",
"RPI_BLUEPY_FIX": "false",
"BLUEPY_PASSIVE_SCAN": false,
"MISCALE_MAC": "00:00:00:00:00:00",
"MQTT_PREFIX": "miscale",
"MQTT_HOST": "192.168.0.1",
Expand Down Expand Up @@ -45,7 +45,7 @@
},
"schema": {
"HCI_DEV": "str?",
"RPI_BLUEPY_FIX": "bool?",
"BLUEPY_PASSIVE_SCAN": "bool?",
"MISCALE_MAC": "str",
"MQTT_PREFIX": "str?",
"MQTT_HOST": "str",
Expand Down Expand Up @@ -73,4 +73,4 @@
"USER3_HEIGHT": "int?",
"USER3_DOB": "str?"
}
}
}
66 changes: 27 additions & 39 deletions mi-scale/src/Xiaomi_Scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@
HCI_DEV = "hci0"[-1]
pass
try:
RPI_BLUEPY_FIX = data["RPI_BLUEPY_FIX"]
BLUEPY_PASSIVE_SCAN = data["BLUEPY_PASSIVE_SCAN"]
except:
RPI_BLUEPY_FIX = False
pass
BLUEPY_PASSIVE_SCAN = False
pass
try:
USER1_GT = int(data["USER1_GT"])
except:
Expand Down Expand Up @@ -167,10 +167,6 @@
MQTT_PREFIX = os.getenv('MQTT_PREFIX', 'miscale')
TIME_INTERVAL = int(os.getenv('TIME_INTERVAL', 30))
MQTT_DISCOVERY = os.getenv('MQTT_DISCOVERY',True)
if MQTT_DISCOVERY.lower() in ['true', '1', 'y', 'yes']:
MQTT_DISCOVERY = True
else:
MQTT_DISCOVERY = False
MQTT_DISCOVERY_PREFIX = os.getenv('MQTT_DISCOVERY_PREFIX','homeassistant')
HCI_DEV = os.getenv('HCI_DEV', 'hci0')[-1]

Expand Down Expand Up @@ -198,8 +194,8 @@
def discovery():
for MQTTUser in (USER1_NAME,USER2_NAME,USER3_NAME):
message = '{"name": "' + MQTTUser + ' Weight",'
message+= '"state_topic": "' + MQTT_PREFIX + '/' + MQTTUser + '/weight","value_template": "{{ value_json.weight }}",'
message+= '"json_attributes_topic": "' + MQTT_PREFIX + '/' + MQTTUser + '/weight","icon": "mdi:scale-bathroom"}'
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,
Expand Down Expand Up @@ -229,12 +225,12 @@ def handleDiscovery(self, dev, isNewDev, isNewData):
measunit = data[4:6]
measured = int((data[8:10] + data[6:8]), 16) * 0.01
unit = ''
if measunit.startswith(('03', 'a3')): unit = 'lbs'
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.now()), "", "")
self._publish(round(measured, 2), unit, str(datetime.today().strftime('%Y-%m-%d-%H:%M:%S')), "", "")
OLD_MEASURE = round(measured, 2)

### Xiaomi V2 Scale ###
Expand All @@ -253,20 +249,17 @@ def handleDiscovery(self, dev, isNewDev, isNewData):
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.now()), hasImpedance, 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 unit == "lbs": calcweight = round(weight * 0.4536, 2)
if unit == "jin": calcweight = round(weight * 0.5, 2)
if unit == "kg": calcweight = weight
if int(calcweight) > USER1_GT:
if int(weight) > USER1_GT:
user = USER1_NAME
height = USER1_HEIGHT
age = self.GetAge(USER1_DOB)
sex = USER1_SEX
elif int(calcweight) < USER2_LT:
elif int(weight) < USER2_LT:
user = USER2_NAME
height = USER2_HEIGHT
age = self.GetAge(USER2_DOB)
Expand All @@ -276,28 +269,26 @@ def _publish(self, weight, unit, mitdatetime, hasImpedance, miimpedance):
height = USER3_HEIGHT
age = self.GetAge(USER3_DOB)
sex = USER3_SEX

lib = Xiaomi_Scale_Body_Metrics.bodyMetrics(calcweight, height, age, sex, 0)
lib = Xiaomi_Scale_Body_Metrics.bodyMetrics(weight, height, age, sex, 0)
message = '{'
message += '"weight":' + "{:.2f}".format(weight)
message += ',"weight_unit":"' + str(unit) + '"'
message += ',"bmi":' + "{:.2f}".format(lib.getBMI())
message += ',"basal_metabolism":' + "{:.2f}".format(lib.getBMR())
message += ',"visceral_fat":' + "{:.2f}".format(lib.getVisceralFat())
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(calcweight, height, age, sex, int(miimpedance))
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 += ',"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 += ',"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")
Expand All @@ -322,7 +313,7 @@ def main():
while True:
try:
scanner = btle.Scanner(HCI_DEV).withDelegate(ScanProcessor())
if RPI_BLUEPY_FIX.lower() in ['true', '1', 'y', 'yes']:
if BLUEPY_PASSIVE_SCAN:
scanner.scan(5, passive=True) #passive=True to try and fix issues for bluepy on RPi devices
else:
scanner.scan(5)
Expand All @@ -333,10 +324,7 @@ def main():
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 hci' + HCI_DEV + ' down'
ps = subprocess.Popen(cmd, shell=True)
time.sleep(1)
cmd = 'hciconfig hci' + HCI_DEV + ' up'
cmd = 'hciconfig hci0 reset'
ps = subprocess.Popen(cmd, shell=True)
time.sleep(30)
BluetoothFailCounter = 0
Expand Down

0 comments on commit bb689ee

Please sign in to comment.