diff --git a/src/Xiaomi_Scale_Body_Metrics.py b/src/Xiaomi_Scale_Body_Metrics.py index cd41884..079aa32 100644 --- a/src/Xiaomi_Scale_Body_Metrics.py +++ b/src/Xiaomi_Scale_Body_Metrics.py @@ -1,6 +1,5 @@ from math import floor - -# Reverse engineered from the Mi Body Composition Scale's library, could also be used on some other scales such as iHealth +from body_scales import bodyScales class bodyMetrics: def __init__(self, weight, height, age, sex, impedance): @@ -9,16 +8,25 @@ class bodyMetrics: self.age = age self.sex = sex self.impedance = impedance + self.scales = bodyScales(age, height, sex, weight) # Check for potential out of boundaries if self.height > 220: - raise Exception("Height is too high (limit: >220cm)") + print("Height is too high (limit: >220cm) or scale is sleeping") + sys.stderr.write('Height is over 220cm or scale is sleeping\n') + exit() elif weight < 10 or weight > 200: - raise Exception("Weight is either too low or too high (limits: <10kg and >200kg)") + print("Weight is either too low or too high (limits: <10kg and >200kg) or scale is sleeping") + sys.stderr.write('Weight is above 10kg or below 200kg or scale is sleeping\n') + exit() elif age > 99: - raise Exception("Age is too high (limit >99 years)") + print("Age is too high (limit >99 years) or scale is sleeping") + sys.stderr.write('Age is above 99 years or scale is sleeping\n') + exit() elif impedance > 3000: - raise Exception("Impedance is too high (limit >3000ohm)") + print("Impedance is above 3000ohm or scale is sleeping") + sys.stderr.write('Impedance is above 3000ohm or scale is sleeping\n') + exit() # Set the value to a boundary if it overflows def checkValueOverflow(self, value, minimum, maximum): @@ -55,18 +63,6 @@ class bodyMetrics: bmr = 5000 return self.checkValueOverflow(bmr, 500, 10000) - # Get BMR scale - def getBMRScale(self): - 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] - break - # Get fat percentage def getFatPercentage(self): # Set a constant to remove from LBM @@ -99,25 +95,6 @@ class bodyMetrics: fatPercentage = 75 return self.checkValueOverflow(fatPercentage, 5, 75) - # Get fat percentage scale - def getFatPercentageScale(self): - # The included tables where quite strange, maybe bogus, replaced them with better ones... - scales = [ - {'min': 0, 'max': 20, 'female': [18, 23, 30, 35], 'male': [8, 14, 21, 25]}, - {'min': 21, 'max': 25, 'female': [19, 24, 30, 35], 'male': [10, 15, 22, 26]}, - {'min': 26, 'max': 30, 'female': [20, 25, 31, 36], 'male': [11, 16, 21, 27]}, - {'min': 31, 'max': 35, 'female': [21, 26, 33, 36], 'male': [13, 17, 25, 28]}, - {'min': 46, 'max': 40, 'female': [22, 27, 34, 37], 'male': [15, 20, 26, 29]}, - {'min': 41, 'max': 45, 'female': [23, 28, 35, 38], 'male': [16, 22, 27, 30]}, - {'min': 46, 'max': 50, 'female': [24, 30, 36, 38], 'male': [17, 23, 29, 31]}, - {'min': 51, 'max': 55, '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 water percentage def getWaterPercentage(self): waterPercentage = (100 - self.getFatPercentage()) * 0.7 @@ -132,10 +109,6 @@ class bodyMetrics: waterPercentage = 75 return self.checkValueOverflow(waterPercentage * coefficient, 35, 75) - # Get water percentage scale - def getWaterPercentageScale(self): - return [53, 67] - # Get bone mass def getBoneMass(self): if self.sex == 'female': @@ -157,18 +130,6 @@ class bodyMetrics: boneMass = 8 return self.checkValueOverflow(boneMass, 0.5 , 8) - # Get bone mass scale - def getBoneMassScale(self): - 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 muscle mass def getMuscleMass(self): muscleMass = self.weight - ((self.getFatPercentage() * 0.01) * self.weight) - self.getBoneMass() @@ -181,18 +142,6 @@ class bodyMetrics: return self.checkValueOverflow(muscleMass, 10 ,120) - # Get muscle mass scale - def getMuscleMassScale(self): - scales = [ - {'min': 170, 'female': [36.5, 42.5], 'male': [49.5, 59.4]}, - {'min': 160, 'female': [32.9, 37.5], 'male': [44.0, 52.4]}, - {'min': 0, 'female': [29.1, 34.7], 'male': [38.5, 46.5]} - ] - - for scale in scales: - if self.height >= scale['min']: - return scale[self.sex] - # Get Visceral Fat def getVisceralFat(self): if self.sex == 'female': @@ -213,65 +162,61 @@ class bodyMetrics: return self.checkValueOverflow(vfal, 1 ,50) - # Get visceral fat scale - def getVisceralFatScale(self): - return [10, 15] - # Get BMI def getBMI(self): return self.checkValueOverflow(self.weight/((self.height/100)*(self.height/100)), 10, 90) - # Get BMI scale - def getBMIScale(self): - # Replaced library's version by mi fit scale, it seems better - return [18.5, 25, 28, 32] - # Get ideal weight (just doing a reverse BMI, should be something better) - def getIdealWeight(self): - return self.checkValueOverflow((22*self.height)*self.height/10000, 5.5, 198) - - # 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 + def getIdealWeight(self, orig=True): + # Uses mi fit algorithm (or holtek's one) + if orig and self.sex == 'female': + return (self.height - 70) * 0.6 + elif orig and self.sex == 'male': + return (self.height - 80) * 0.7 + else: + return self.checkValueOverflow((22*self.height)*self.height/10000, 5.5, 198) # Get fat mass to ideal (guessing mi fit formula) def getFatMassToIdeal(self): - mass = (self.weight * (self.getFatPercentage() / 100)) - (self.weight * (self.getFatPercentageScale()[2] / 100)) + mass = (self.weight * (self.getFatPercentage() / 100)) - (self.weight * (self.scales.getFatPercentageScale()[2] / 100)) if mass < 0: return {'type': 'to_gain', 'mass': mass*-1} else: return {'type': 'to_lose', 'mass': mass} # Get protetin percentage (warn: guessed formula) - def getProteinPercentage(self): - proteinPercentage = 100 - (floor(self.getFatPercentage() * 100) / 100) - proteinPercentage -= floor(self.getWaterPercentage() * 100) / 100 - proteinPercentage -= floor((self.getBoneMass()/self.weight*100) * 100) / 100 - return proteinPercentage + def getProteinPercentage(self, orig=True): + # Use original algorithm from mi fit (or legacy guess one) + if orig: + proteinPercentage = (self.getMuscleMass() / self.weight) * 100 + proteinPercentage -= self.getWaterPercentage() + else: + proteinPercentage = 100 - (floor(self.getFatPercentage() * 100) / 100) + proteinPercentage -= floor(self.getWaterPercentage() * 100) / 100 + proteinPercentage -= floor((self.getBoneMass()/self.weight*100) * 100) / 100 - # Get protein scale (hardcoded in mi fit) - def getProteinPercentageScale(self): - return [16, 20] + return self.checkValueOverflow(proteinPercentage, 5, 32) # Get body type (out of nine possible) def getBodyType(self): - if self.getFatPercentage() > self.getFatPercentageScale()[2]: + if self.getFatPercentage() > self.scales.getFatPercentageScale()[2]: factor = 0 - elif self.getFatPercentage() < self.getFatPercentageScale()[1]: + elif self.getFatPercentage() < self.scales.getFatPercentageScale()[1]: factor = 2 else: factor = 1 - if self.getMuscleMass() > self.getMuscleMassScale()[1]: + if self.getMuscleMass() > self.scales.getMuscleMassScale()[1]: return 2 + (factor * 3) - elif self.getMuscleMass() < self.getMuscleMassScale()[0]: + elif self.getMuscleMass() < self.scales.getMuscleMassScale()[0]: return (factor * 3) else: return 1 + (factor * 3) - # Return body type scale - def getBodyTypeScale(self): - return ['obese', 'overweight', 'thick-set', 'lack-exerscise', 'balanced', 'balanced-muscular', 'skinny', 'balanced-skinny', 'skinny-muscular'] + # Get Metabolic Age + def getMetabolicAge(self): + if self.sex == 'female': + metabolicAge = (self.height * -1.1165) + (self.weight * 1.5784) + (self.age * 0.4615) + (self.impedance * 0.0415) + 83.2548 + else: + metabolicAge = (self.height * -0.7471) + (self.weight * 0.9161) + (self.age * 0.4184) + (self.impedance * 0.0517) + 54.2267 + return self.checkValueOverflow(metabolicAge, 15, 80)