Calculating a Value in a DTH

I am looking for some help in updating a handler to calculate a new value (which I will display on a tile) based on a value it retrieves from an API (already works) and a value input in prefs.

I have added the new attributes for the pref and new value to be calculated and I’ve coded for the input of the pref. I know how to get the item displayed on a tile but I don’t know how to code to calculate the new value.

This is a little more than I am used to doing in the code. Can someone give me some pointers?

I have looked at several examples of other DTHs and each one seems to be doing it differently and so far I can’t figure it out. I have also looked at the Developer Doc and not being a programmer I am struggling a little.

Thanks

The best approach would be to create an event to set the attribute to your calculated value when the dth handles/detects changes to dependant data.

Can you share your code so it is easier to help you ?

The previous helper above shared how the modified value should be updating the tile but as you indicated you know how to do that, it seems you are facing a difficulty to compute the value itself

Thanks. I’ll share the code I have as soon as a I get a chance.

Okay, here’s the code. Firstly, this is not my code but I’ve taken it and have made updates for my own use.

Note that although height was already in the code, it is not sent in the API (thus I commented it out). So, I have added height as a pref input. That is the simple part. What I want to do is take that height which is entered in prefs and perform a calculation on it using weight (I’m calculating BMI). I’ll then show the calculated BMI (and possibly height) on a tile.

/**
 *  Withings Scale (v.0.0.1)
 *
 * MIT License
 *
 * Copyright (c) 2019 fison67@nate.com
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */
 
import groovy.json.JsonSlurper
import java.text.DateFormat
import groovy.transform.Field


@Field 
LANGUAGE_MAP = [
	"last_measure_date": [
    	"Korean": "측정시간",
        "English": "Measure Date & Time"
    ],
    "weight": [
        "Korean": "몸무게",
        "English": "Weight"
    ],
    "fat_free_mass": [
        "Korean": "제지방량",
        "English": "Fat Free Mass"
    ],
    "fat_ratio": [
        "Korean": "체지방율",
        "English": "Fat Ratio"
    ],
    "fat_mass_weight": [
        "Korean": "체지방량",
        "English": "Fat Mass Weight"
    ],
    "heart_rate": [
        "Korean": "심박수",
        "English": "Heart Rate"
    ],
    "height": [
        "Korean": "신장",
        "English": "Height"
    ],
    "bmi": [
        "Korean": "체질량 지수",
        "English": "BMI"
    ]
]


metadata {
	definition (name: "Withings Scale", namespace: "fison67", author: "fison67", ocfdevicetype: "x.com.st.d.healthtracker") {
      	capability "Sensor"
        capability "Refresh"	
        capability "Temperature Measurement"
        capability "Carbon Dioxide Measurement"
        
        attribute "status", "number"
        attribute "weight", "number"
        attribute "fat_free_mass", "number"
        attribute "fat_ratio", "number"
        attribute "fat_mass_weight", "number"
        attribute "heart_rate", "number"
        attribute "bmi", "string"
        
        attribute "body_temperature", "number"
        attribute "skin_temperature", "number"
        attribute "blood_pressure_max", "number"
        attribute "blood_pressure_min", "number"
        
        attribute "lastCheckin", "Date"
        attribute "lastMeasureDate", "Date"
        
	}


	simulator {
	}
    
    preferences {
        input name: "language", title:"Select a language" , type: "enum", required: true, options: ["English", "Korean"], defaultValue: "English", description:"Language for DTH"
  		input name: "height", title:"Enter height in inches" , type: "number", required: false, defaultValue: 0, description:"Height"
  //      input name: "pollingTime", title:"Polling Time[Hour]" , type: "number", required: true, defaultValue: 1, description:"Polling Hour", range: "1..12"
	}

	tiles {
		multiAttributeTile(name:"status", type: "generic", width: 6, height: 4, canChangeIcon: true){
			tileAttribute ("device.status", key: "PRIMARY_CONTROL") {
               attributeState("status", label:'${currentValue} lbs', backgroundColor:"#00A0DC")
            }
            
            tileAttribute("device.lastCheckin", key: "SECONDARY_CONTROL") {
    			attributeState("default", label:'\nLast Update: ${currentValue}')
            }
		}
        
        valueTile("last_measure_date_label", "last_measure_date_label", decoration: "flat", width: 3, height: 1) {
            state "default", label:'${currentValue}'
        }   
        valueTile("lastMeasureDate", "device.lastMeasureDate", width: 3, height: 1, unit: "") {
            state("val", label:'${currentValue}', defaultState: true
            )
        }
        valueTile("weight_label", "weight_label", decoration: "flat", width: 3, height: 1) {
            state "default", label:'${currentValue}'
        }   
        valueTile("weight", "device.weight", width: 3, height: 1, unit: "lbs") {
            state("val", label:'${currentValue} lbs', defaultState: true
            )
        }
        valueTile("fat_free_mass_label", "fat_free_mass_label", decoration: "flat", width: 3, height: 1) {
            state "default", label:'${currentValue}'
        }    
        valueTile("fat_free_mass", "device.fat_free_mass", width: 3, height: 1, unit: "lbs") {
            state("val", label:'${currentValue} lbs', defaultState: true
            )
        }
        valueTile("fat_ratio_label", "fat_ratio_label", decoration: "flat", width: 3, height: 1) {
            state "default", label:'${currentValue}'
        }    
        valueTile("fat_ratio", "device.fat_ratio", width: 3, height: 1, unit: "%") {
            state("val", label:'${currentValue} %', defaultState: true
            )
        }
        valueTile("fat_mass_weight_label", "fat_mass_weight_label", decoration: "flat", width: 3, height: 1) {
            state "default", label:'${currentValue}'
        }    
        valueTile("fat_mass_weight", "device.fat_mass_weight", width: 3, height: 1, unit: "lbs") {
            state("val", label:'${currentValue} lbs', defaultState: true
            )
        }
        valueTile("heart_rate_label", "heart_rate_label", decoration: "flat", width: 3, height: 1) {
            state "default", label:'${currentValue}'
        }    
        valueTile("heart_rate", "device.heart_rate", width: 3, height: 1, unit: "bpm") {
            state("val", label:'${currentValue} bpm', defaultState: true
            )
        }
        valueTile("height_label", "height_label", decoration: "flat", width: 3, height: 1) {
            state "default", label:'${currentValue}'
        }
        valueTile("height", "device.height", width: 3, height: 1, unit: "inches") {
            state("val", label:'${height} inches', defaultState: true
            )
        }
        valueTile("bmi_label", "bmi_label", decoration: "flat", width: 3, height: 1) {
            state "default", label:'${currentValue}'
        }
        valueTile("bmi", "device.bmi", width: 3, height: 1, unit: "") {
            state("val", label:'${currentValue}', defaultState: true
            )
        }
        standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
            state "default", label:"", action:"refresh", icon:"st.secondary.refresh"
        }
        
	}

}

// parse events into attributes
def parse(String description) {
	log.debug "Parsing '${description}'"
}

def setID(id){
	state._device_id = id
}

def refresh(){
	log.debug "Refresh"
    _getData()
}

def _getData(){
	def accessToken = parent.getAccountAccessToken("user.metrics")    
   	def dateInfo = getDateArray(5)
    def first = dateInfo[0], end = dateInfo[1]
    def params = [
    	uri: "https://wbsapi.withings.net/measure?action=getmeas&access_token=${accessToken}&category=1&startdate=${first}&enddate=${end}"
    ]
    httpGet(params) { resp ->
        def result =  new JsonSlurper().parseText(resp.data.text)
        if(result.status == 0){
        //	log.debug result
            
            def type1Check = false, type4Check = false, type5Check = false, type6Check = false, type8Check = false, type9Check = false, type10Check = false, type11Check = false, type12Check = false, type71Check = false, type73Check = false
            def list = result.body.measuregrps
            list.each { item ->
            //	def date = item.date
                
            	def subList = item.measures
                subList.each { subItem ->
                	if(item.deviceid == state._device_id){
                        def unitVal = 1
                        def _tmp = subItem.unit
                        while(_tmp < 0){
                        	_tmp++
                            unitVal = unitVal * 10
                        }
                        
                    	if(subItem.type == 1 && type1Check == false){
                            sendEvent(name: "status", value: (subItem.value / unitVal * 2.20462262185))
                            sendEvent(name: "weight", value: (subItem.value / unitVal * 2.20462262185))
                            log.debug "Weight >> " + (subItem.value / unitVal * 2.20462262185)
                            
    						def time = new Date( ((long)item.date) * 1000 ).format("MM-dd-yy, h:mm a", location.timeZone)
                            sendEvent(name: "lastMeasureDate", value: time, displayed: false)
                            type1Check = true
//                        }else if(subItem.type == 4 && type4Check == false){
//                            sendEvent(name: "height", value: subItem.value / unitVal * 39.37007874)
//                            log.debug "Height >> " + (subItem.value / unitVal * 39.37007874)
//                            type4Check = true
                        }else if(subItem.type == 5 && type5Check == false){
                            sendEvent(name: "fat_free_mass", value: (subItem.value / unitVal * 2.20462262185))
                            log.debug "Fat Free Mass >> " + (subItem.value / unitVal * 2.20462262185)
                            type5Check = true
                        }else if(subItem.type == 6 && type6Check == false){
                            sendEvent(name: "fat_ratio", value: (subItem.value / unitVal))
                            log.debug "Fat Ratio >> " + (subItem.value / unitVal)
                            type6Check = true
                        }else if(subItem.type == 8 && type8Check == false){
                            sendEvent(name: "fat_mass_weight", value: (subItem.value / unitVal * 2.20462262185))
                            log.debug "Fat Mass Weight >> " + (subItem.value / unitVal * 2.20462262185)
                            type8Check = true
                        }else if(subItem.type == 9 && type9Check == false){
                            sendEvent(name: "blood_pressure_max", value: (subItem.value / 1000))
                            log.debug "Blood Pressure Max >> " + (subItem.value / 1000)
                            type9Check = true
                        }else if(subItem.type == 10 && type10Check == false){
                            sendEvent(name: "blood_pressure_min", value: (subItem.value / unitVal))
                            log.debug "Blood Pressure Min >> " + (subItem.value / unitVal)
                            type10Check = true
                        }else if(subItem.type == 11 && type11Check == false){
                            sendEvent(name: "heart_rate", value: (subItem.value / unitVal ))
                            log.debug "Heart Rate >> " + (subItem.value / unitVal)
                            type11Check = true
                        }else if(subItem.type == 12 && type12Check == false){
                            sendEvent(name: "temperature", value: (subItem.value / unitVal))
                            log.debug "Temperature >> " + (subItem.value / unitVal)
                            type12Check = true
                        }else if(subItem.type == 71 && type71Check == false){
                            sendEvent(name: "body_temperature", value: (subItem.value / unitVal))
                            log.debug "Body Temperature >> " + (subItem.value / unitVal)
                            type71Check = true
                        }else if(subItem.type == 73 && type73Check == false){
                            sendEvent(name: "skin_temperature", value: (subItem.value / unitVal))
                            log.debug "Skin Temperature >> " + (subItem.value / unitVal)
                            type73Check = true
                        }
                    }
            	}
            }
        }else{
        	log.debug result
            parent.getAccessTokenByRefreshToken("user.metrics")
        }
        def time = new Date().format("MM-dd-yy, h:mm a", location.timeZone)
        sendEvent(name: "lastCheckin", value: time, displayed: false)
    }
    
}

def updated() {
	setLanguage()
	unschedule()
//    log.debug "Request data every ${pollingTime} hour " 
    schedule("* * * * * ?", _getData)
}

def getDateArray(day){
 	def first
    def end
    def now = new Date()
    use (groovy.time.TimeCategory) {
        first =  (int)((now - day.days).getTime() / 1000)
        end =  (int)((now + day.days).getTime() / 1000)
    }
    return [first, end]
}

def setLanguage(){
    log.debug "Langauge >> ${language}"
	
    sendEvent(name:"last_measure_date_label", value: LANGUAGE_MAP["last_measure_date"][language] )
    sendEvent(name:"weight_label", value: LANGUAGE_MAP["weight"][language] )
    sendEvent(name:"fat_free_mass_label", value: LANGUAGE_MAP["fat_free_mass"][language] )
    sendEvent(name:"fat_ratio_label", value: LANGUAGE_MAP["fat_ratio"][language] )
	sendEvent(name:"fat_mass_weight_label", value: LANGUAGE_MAP["fat_mass_weight"][language] )
    sendEvent(name:"heart_rate_label", value: LANGUAGE_MAP["heart_rate"][language] )
	sendEvent(name:"height_label", value: LANGUAGE_MAP["height"][language] )
    sendEvent(name:"bmi_label", value: LANGUAGE_MAP["bmi"][language] )
}

ok so basically you get a value from a json and want to compute something with a preference value (height).

So the result of a input preference/json is still a text that contains a number can be converted using .toFloat() method.

Example: height.toFloat()*1.4/3.0.

Hope that helps