@@ -59,6 +59,11 @@ List<String> getActionsToCreate() {
5959 if (hasActionsToCreateList() == true ) { return ACTIONS_TO_CREATE }
6060 else {return []}
6161}
62+ Boolean hasActionsToCreateEnabledTimesList () { return ACTIONS_TO_CREATE_ENABLED_TIMES != null }
63+ List<String > getActionsToCreateEnabledTimes () {
64+ if (hasActionsToCreateEnabledTimesList() == true ) { return ACTIONS_TO_CREATE_ENABLED_TIMES }
65+ else {return []}
66+ }
6267
6368Boolean deviceIsComponent () {return COMP == true }
6469Boolean deviceIsComponentInputSwitch () {return INPUTSWITCH == true }
@@ -70,6 +75,7 @@ Boolean hasCapabilitySwitch() { return device.hasCapability('Switch') == true }
7075Boolean hasCapabilityPresence () { return device. hasCapability(' PresenceSensor' ) == true }
7176Boolean hasCapabilityValve () { return device. hasCapability(' Valve' ) == true }
7277Boolean hasCapabilityCover () { return device. hasCapability(' WindowShade' ) == true }
78+ Boolean hasCapabilityThermostatHeatingSetpoint () { return device. hasCapability(' ThermostatHeatingSetpoint' ) == true }
7379Boolean hasCapabilityCoverOrCoverChild () { return device. hasCapability(' WindowShade' ) == true || getCoverChildren()?. size() > 0 }
7480
7581@CompileStatic
@@ -153,6 +159,13 @@ if (device != null) {
153159 if (hasCapabilityCover() == true ) {
154160 input(name : ' cover_invert_status' , type :' bool' , title : ' Invert open/closed state. If enabled open=0, 100=closed.' , required : true , defaultValue : false )
155161 }
162+ if (hasCapabilityThermostatHeatingSetpoint() == true ) {
163+ input(name : ' trv_temperature_offset' , type :' number' , title : ' Temperature offset' , required : true , defaultValue : 0 )
164+ input(name : ' trv_target_t_auto' , type :' bool' , title : ' Automatic temperature control' , required : true , defaultValue : true )
165+ input(name : ' trv_ext_t_enabled' , type :' bool' , title : ' If temperature correction from external temp sensor is enabled' , required : true , defaultValue : true )
166+ input(name : ' trv_display_brightness' , type :' enum' , title : ' Display brightness' , required : true , options : [1 :' 1' , 2 :' 2' , 3 :' 3' , 4 :' 4' , 5 :' 5' , 6 :' 6' , 7 :' 7' ])
167+ input(name : ' trv_temp_units' , type :' enum' , title : ' Temperature Units' , required : true , options : [' C' :' Celcius' , ' F' :' Fahrenheit' ])
168+ }
156169
157170 input(name : ' logEnable' , type : ' bool' , title : ' Enable Logging' , required : false , defaultValue : true )
158171 input(name : ' debugLogEnable' , type : ' bool' , title : ' Enable debug logging' , required : false , defaultValue : true )
@@ -412,8 +425,11 @@ void getPreferencesFromShellyDevice() {
412425void getPreferencesFromShellyDeviceGen1 () {
413426 LinkedHashMap gen1SettingsResponse = (LinkedHashMap )sendGen1Command(' settings' )
414427 logTrace(" Gen 1 Settings ${ prettyJson(gen1SettingsResponse)} " )
428+ // Get preferences from Shelly
415429 LinkedHashMap prefs = [:]
416430 LinkedHashMap motion = (LinkedHashMap )gen1SettingsResponse?. motion
431+ LinkedHashMap display = (LinkedHashMap )gen1SettingsResponse?. display
432+ List<LinkedHashMap > thermostats = (List<LinkedHashMap > )gen1SettingsResponse?. thermostats
417433 if (motion != null ) {
418434 prefs[' gen1_motion_sensitivity' ] = motion?. sensitivity as Integer
419435 prefs[' gen1_motion_blind_time_minutes' ] = motion?. blind_time_minutes as Integer
@@ -422,6 +438,25 @@ void getPreferencesFromShellyDeviceGen1() {
422438 prefs[' gen1_tamper_sensitivity' ] = gen1SettingsResponse?. tamper_sensitivity as Integer
423439 }
424440 if (gen1SettingsResponse?. set_volume != null ) {prefs[' gen1_set_volume' ] = gen1SettingsResponse?. set_volume as Integer }
441+ if (display != null ) {
442+ if (display?. brightness != null ) {prefs[' trv_display_brightness' ] = display?. brightness as Integer }
443+ }
444+ if (thermostats != null ) {
445+ thermostats. eachWithIndex{ tstat , index ->
446+ LinkedHashMap target_t = (LinkedHashMap )tstat?. target_t
447+ if (target_t?. enabled != null ) {prefs[' trv_target_t_auto' ] = target_t?. enabled as Boolean }
448+ if (target_t?. units != null ) {prefs[' trv_temp_units' ] = target_t?. units}
449+
450+ LinkedHashMap ext_t = (LinkedHashMap )tstat?. ext_t
451+ if (ext_t?. enabled != null ) {prefs[' trv_ext_t_enabled' ] = ext_t?. enabled as Boolean }
452+
453+ if (tstat?. temperature_offset != null ) {prefs[' trv_temperature_offset' ] = tstat?. temperature_offset as BigDecimal }
454+ }
455+ }
456+
457+ if (prefs. size() > 0 ) { setHubitatDevicePreferences(prefs) }
458+
459+
425460
426461 // Process ADC(s)
427462 List adcs = (List )gen1SettingsResponse?. adcs
@@ -524,7 +559,6 @@ void getPreferencesFromShellyDeviceGen1() {
524559 }
525560 }
526561
527- if (prefs. size() > 0 ) { setHubitatDevicePreferences(prefs) }
528562 refresh()
529563}
530564
@@ -695,6 +729,24 @@ void sendPreferencesToShellyDevice() {
695729 sendGen1Command(' settings' , queryString)
696730 }
697731
732+ if (getDeviceSettings(). trv_temperature_offset != null ) {
733+ String queryString = " temperature_offset=${ getDeviceSettings().trv_temperature_offset} " . toString()
734+ sendGen1Command(' settings' , queryString)
735+ }
736+ if (getDeviceSettings(). trv_target_t_auto != null ) {
737+ String queryString = " target_t_enabled=${ getDeviceSettings().trv_target_t_auto == true ? 1 : 0} " . toString()
738+ sendGen1Command(' settings/thermostat/0' , queryString)
739+ }
740+ if (getDeviceSettings(). trv_ext_t_enabled != null ) {
741+ String queryString = " ext_t_enabled=${ getDeviceSettings().trv_ext_t_enabled == true ? 1 : 0} " . toString()
742+ sendGen1Command(' settings/thermostat/0' , queryString)
743+ }
744+ if (getDeviceSettings(). trv_temp_units != null ) {
745+ String u = " ${ getDeviceSettings().trv_temp_units} " . toString()[0 ]
746+ String queryString = " temperature_unit=${ u} " . toString()
747+ sendGen1Command(' settings' , queryString)
748+ }
749+
698750 runInSeconds(' parentGetPreferencesFromShellyDevice' , 6 )
699751}
700752/* #endregion */
@@ -1018,14 +1070,6 @@ void setGasPPM(Integer ppm) {
10181070 sendDeviceEvent([name : ' ppm' , value : ppm])
10191071}
10201072
1021- @CompileStatic
1022- void setValvePosition (Boolean open , Integer valve = 0 ) {
1023- if (open == true ) {
1024- sendDeviceEvent([name : ' valve' , value : ' open' ])
1025- } else {
1026- sendDeviceEvent([name : ' valve' , value : ' closed' ])
1027- }
1028- }
10291073/* #endregion */
10301074/* #region Generic Getters and Setters */
10311075
@@ -1073,6 +1117,11 @@ Integer getIntegerDeviceSetting(String settingName) {
10731117 return thisDeviceHasSetting(settingName) ? getDeviceSettings()[settingName] as Integer : null
10741118}
10751119
1120+ @CompileStatic
1121+ String getEnumDeviceSetting (String settingName ) {
1122+ return thisDeviceHasSetting(settingName) ? " ${ getDeviceSettings()[settingName]} " . toString() : null
1123+ }
1124+
10761125
10771126@CompileStatic
10781127Boolean hasChildren () {
@@ -1386,6 +1435,10 @@ void setHubitatDevicePreferences(LinkedHashMap<String, Object> preferences, Devi
13861435 } else {
13871436 setDeviceSetting(i, [type : preferenceMap[i]. type, value : v], dev)
13881437 }
1438+ } else if (dev == null && k. startsWith(' trv_' )) {
1439+ if (k == ' trv_temperature_offset' ) {setDeviceSetting(k, [type :' number' , value : v as BigDecimal ])}
1440+ if (k in [' trv_target_t_auto' , ' trv_ext_t_enabled' ]) {setDeviceSetting(k, [type :' bool' , value : v])}
1441+ if (k in [' trv_display_brightness' , ' trv_temp_units' ]) {setDeviceSetting(k, [type :' enum' , value : " ${ v} " . toString()])}
13891442 } else if (k == ' id' || k == ' name' ) {
13901443 logTrace(" Skipping settings as configuration of ${ k} from Hubitat, please configure from Shelly device web UI if needed." )
13911444 } else if (isInput == true && (k in [' type' , ' factory_reset' ])) {
@@ -1451,6 +1504,11 @@ void setValveState(String position, Integer id = 0) {
14511504 }
14521505}
14531506
1507+ @CompileStatic
1508+ void setValvePositionState (Integer level , Integer id = 0 ) {
1509+ if (level != null ) {sendDeviceEvent([name : ' valvePosition' , value : level, unit : ' %' ])}
1510+ }
1511+
14541512@CompileStatic
14551513void setSwitchLevelState (Integer level , Integer id = 0 ) {
14561514 if (level != null ) {
@@ -1513,12 +1571,6 @@ Boolean getSwitchState() {
15131571 return thisDevice(). currentValue(' switch' , true ) == ' on'
15141572}
15151573
1516- @CompileStatic
1517- void setHeatingSetpoint (BigDecimal temperature ) {
1518- Integer id = getDeviceDataValue(' tstatId' ) as Integer
1519- parentSendGen1CommandAsync(" settings/thermostat/${ id} /?target_t_enabled=1&target_t=${ temperature} " )
1520- }
1521-
15221574void on () {
15231575 if (deviceIsInputSwitch(thisDevice()) == true ) {
15241576 logWarn(' Cannot change state of an input on a Shelly device from Hubitat!' )
@@ -1608,6 +1660,7 @@ void setLastUpdated() {
16081660 if (hasCapabilityBattery() == true ) {sendDeviceEvent([name : ' lastUpdated' , value : nowFormatted()])}
16091661}
16101662
1663+ // Rollers
16111664@CompileStatic
16121665void open () {
16131666 if (isGen1Device() == true ) {
@@ -1624,7 +1677,6 @@ void close() {
16241677 } else {
16251678 parentPostCommandSync(coverCloseCommand(getIntegerDeviceDataValue(' coverId' )))
16261679 }
1627-
16281680}
16291681
16301682@CompileStatic
@@ -1651,6 +1703,35 @@ void stopPositionChange() {
16511703 }
16521704}
16531705
1706+ // TRV
1707+ @CompileStatic
1708+ void setValvePosition (BigDecimal position ) {
1709+ parentSendGen1CommandAsync(" thermostat/0/?pos=${ position as Integer} " )
1710+ }
1711+
1712+ @CompileStatic
1713+ void setExternalTemperature (BigDecimal temperature ) {
1714+ String units = getEnumDeviceSetting(' trv_temp_units' )
1715+ if (isCelciusScale() == false && units == ' C' ) {
1716+ temperature = fToC(temperature)
1717+ } else if (isCelciusScale() == true && units == ' F' ) {
1718+ temperature = cToF(temperature)
1719+ }
1720+ temperature = temperature. setScale(1 , BigDecimal . ROUND_HALF_UP )
1721+ parentSendGen1CommandAsync(" ext_t?temp=${ temperature} " )
1722+ }
1723+
1724+ @CompileStatic
1725+ void setHeatingSetpoint (BigDecimal temperature ) {
1726+ String units = getEnumDeviceSetting(' trv_temp_units' )
1727+ if (isCelciusScale() == false && units == ' C' ) {
1728+ temperature = fToC(temperature)
1729+ } else if (isCelciusScale() == true && units == ' F' ) {
1730+ temperature = cToF(temperature)
1731+ }
1732+ temperature = temperature. setScale(1 , BigDecimal . ROUND_HALF_UP )
1733+ parentSendGen1CommandAsync(" thermostat/0?target_t_enabled=1&target_t=${ temperature} " )
1734+ }
16541735
16551736void sendEventToShellyBluetoothHelper (String loc , Object value , String dni ) {
16561737 sendLocationEvent(name :loc, value :value, data :dni)
@@ -2532,28 +2613,28 @@ void getStatusGen1Callback(AsyncResponse response, Map data = null) {
25322613 if (hasCapabilityBatteryGen1() == true ) {
25332614 LinkedHashMap battery = (LinkedHashMap )json?. bat
25342615 Integer percent = battery?. value as Integer
2535- setBatteryPercent(percent)
2616+ if (percent != null ) { setBatteryPercent(percent)}
25362617 }
25372618 if (hasCapabilityLuxGen1() == true ) {
25382619 Integer lux = ((LinkedHashMap )json?. lux)?. value as Integer
2539- setIlluminance(lux)
2620+ if (lux != null ) { setIlluminance(lux)}
25402621 }
25412622 if (hasCapabilityTempGen1() == true ) {
25422623 BigDecimal temp = (BigDecimal )(((LinkedHashMap )json?. tmp)?. value)
25432624 String tempUnits = (((LinkedHashMap )json?. tmp)?. units). toString()
2544- if (tempUnits == ' C' ) {
2625+ if (tempUnits == ' C' && temp != null ) {
25452626 setTemperatureC(temp)
2546- } else if (tempUnits == ' F' ) {
2627+ } else if (tempUnits == ' F' && temp != null ) {
25472628 setTemperatureF(temp)
25482629 }
25492630 }
25502631 if (hasCapabilityHumGen1() == true ) {
25512632 BigDecimal hum = (BigDecimal )(((LinkedHashMap )json?. hum)?. value)
2552- if (hum != null ){setHumidityPercent(hum)}
2633+ if (hum != null ) {setHumidityPercent(hum)}
25532634 }
25542635 if (hasCapabilityFloodGen1() == true ) {
25552636 Boolean flood = (Boolean )json?. flood
2556- if (flood != null ){setFloodOn(flood)}
2637+ if (flood != null ) {setFloodOn(flood)}
25572638 }
25582639 if (hasCapabilitySwitch() == true || hasChildSwitches() == true ) {
25592640 List<LinkedHashMap > relays = (List<LinkedHashMap > )json?. relays
@@ -2570,9 +2651,17 @@ void getStatusGen1Callback(AsyncResponse response, Map data = null) {
25702651 List<LinkedHashMap<String , String > > valves = (List<LinkedHashMap<String , String > > )json?. valves
25712652 if (valves?. size() > 0 ) {
25722653 valves. eachWithIndex{ valve , index ->
2573- String position = valve?. state
2654+ String valveState = valve?. state
2655+ logTrace(" Valve status: ${ valveState} " )
2656+ setValveState(valveState. startsWith(' open' ) ? ' open' : ' closed' )
2657+ }
2658+ }
2659+ List<LinkedHashMap<String , String > > thermostats = (List<LinkedHashMap<String , String > > )json?. thermostats
2660+ if (thermostats?. size() > 0 ) {
2661+ thermostats. eachWithIndex{ tstat , index ->
2662+ BigDecimal position = tstat?. pos as BigDecimal
25742663 logTrace(" Valve status: ${ position} " )
2575- setValveState (position. startsWith( ' open ' ) ? ' open ' : ' closed ' )
2664+ setValvePositionState (position as Integer )
25762665 }
25772666 }
25782667 }
@@ -2671,6 +2760,26 @@ void getStatusGen1Callback(AsyncResponse response, Map data = null) {
26712760 if (isOn != null ) {setSwitchState(isOn)}
26722761 }
26732762 }
2763+ if (json?. thermostats != null ) {
2764+ List<LinkedHashMap > thermostats = (List<LinkedHashMap > )json?. thermostats
2765+ thermostats. eachWithIndex{ tstat , index ->
2766+ if (tstat?. pos != null ) {setValvePositionState(tstat. pos as Integer )}
2767+ if (tstat?. tmp != null ) {
2768+ LinkedHashMap tmp = (LinkedHashMap )tstat. tmp
2769+ if (tmp?. units != null && tmp?. value != null ) {
2770+ if (isCelciusScale() == true && tmp?. units == ' C' ) {
2771+ setTemperatureC(tmp?. value as BigDecimal )
2772+ } else if (isCelciusScale() == false && tmp?. units == ' C' ) {
2773+ setTemperatureF(cToF(tmp?. value as BigDecimal ))
2774+ } else if (isCelciusScale() == true && tmp?. units == ' F' ) {
2775+ setTemperatureC(fToC(tmp?. value as BigDecimal ))
2776+ } else if (isCelciusScale() == false && tmp?. units == ' F' ) {
2777+ setTemperatureF(tmp?. value as BigDecimal )
2778+ }
2779+ }
2780+ }
2781+ }
2782+ }
26742783 }
26752784}
26762785
@@ -2946,7 +3055,7 @@ void setDeviceActionsGen1() {
29463055 v. each{ m ->
29473056 Integer index = ((Map )m)?. index as Integer
29483057 String name = " ${ k} " . toString()
2949- Boolean hasEnabledTimes = actionHasEnabledTimes(v)
3058+ Boolean hasEnabledTimes = actionHasEnabledTimes(k, v)
29503059
29513060 Boolean create = false
29523061 String additionalParams = ' '
@@ -2987,8 +3096,10 @@ void setDeviceActionsGen1() {
29873096}
29883097
29893098@CompileStatic
2990- Boolean actionHasEnabledTimes (List<LinkedHashMap > action ) {
2991- if (action != null && action?. size() > 0 ) {
3099+ Boolean actionHasEnabledTimes (String actionName , List<LinkedHashMap > action ) {
3100+ if (hasActionsToCreateEnabledTimesList() == true && actionName in getActionsToCreateEnabledTimes()) {
3101+ return true
3102+ } else if (action != null && action?. size() > 0 ) {
29923103 Map a = action[0 ]
29933104 if (a?. urls != null ) {
29943105 List urls = (List )a. urls
@@ -3274,7 +3385,7 @@ void deleteHubitatWebhooksGen1() {
32743385 Integer index = ((Map )m)?. index as Integer
32753386 String queryString = " index=${ index} &enabled=false" . toString()
32763387 queryString + = " &name=${ k} " . toString()
3277- Boolean hasTimes = actionHasEnabledTimes(v)
3388+ Boolean hasTimes = actionHasEnabledTimes(k, v)
32783389 if (hasTimes) {
32793390 queryString + = " &urls[0][url]=" . toString()
32803391 queryString + = " &urls[0][int]=" . toString()
0 commit comments