Dynamically adjusting schedy termperature based on proximity

I’ve recently set up schedy ( Schedy — hass-apps 0.20200319.0 documentation ) to control my home thermostats. I’ve always wanted my thermostats to adjust down (I only use heating) based on how far away I am to home. I’ve found the natural logarithm of the distance works well, i.e. if I’m 100km away it can adjust down the temperature by 4 degrees. This is my probaby sub-optimal way of achieving it:

  1. Enable the proximity integration in home assistant’s configuration.yaml:
proximity:
  home:
    devices:
      - person.alge
    tolerance: 50
    unit_of_measurement: km
  1. Create an AppDaemon app which publishes an adjustment based on the proximity:
    (place this in apps directory:)
    tempadjust.py (place in apps directory in appdaemon config):
import hassapi as hass
import math

"""

Adjust temperature based on proximity. 

Uses the "proximity" integration (or any other entity which has state in km),
and outputs an adjustment value based on the logarithm of the distance.

Arguments:
 - event: Entity name when publishing event (e.g. 'tempadjuster.adjustment')
 - proximity: Entity name for proximity in km (e.g. 'proximity.home')
 - km_offset: optional integer: Added to proximity km before calulation (e.g. km_offset=-10 to force proximity of within 10 km to not cause any adjustment)
 - km_multiplier: optional float: Multiplied with proximity km after offsetting (e.g. km_multiplier=2.0 to make adjustments more aggressive) 
 - adjustment_multiplier: optional float: Multiplied with calculated adjustment (e.g. adjustment_multiplier=0.5 to make adjustments less aggressive) 
"""

class TempAdjuster(hass.Hass):
    def initialize(self):
        self.entity = self.args["event"]
        self.proximity = self.args["proximity_entity"]
        self.km_offset = int(self.args.get('km_offset', 0))
        self.km_multiplier = float(self.args.get('km_multiplier', 1.0))
        self.adjustment_multiplier = float(self.args.get('adjustment_multiplier', 1.0))

        if (self.get_state(self.proximity) is None):
            self.log(f"Unable to look up proximity entity [{self.proximity}]", level="ERROR")
            return

        self.listen_state(self.updateState, self.proximity)
        self.updateState()

    def updateState(self, kwargs=None):
        proximity_km=(int(self.get_state(self.proximity)) + self.km_offset) * self.km_multiplier
        adjustment = 0
        if (proximity_km >= 1):
            adjustment = -int(math.log(proximity_km)) * self.adjustment_multiplier
        self.log(f"proximity is {proximity_km}, adjustment is{adjustment}", level="INFO")
        self.set_state(self.entity, state=int(adjustment))

and add the app with parameters in apps.yaml:

tempadjuster:
  module: tempadjuster
  class: TempAdjuster
  interval: 30
  proximity_entity: "proximity.home"
  km_offset: -5
  event: "tempadjuster.adjustment"
  1. Use this as a dynamic expression in the schedy schedule:
schedy_heating: 
  module: hass_apps_loader
  class: SchedyApp

  actor_type: thermostat

  schedule_prepend:
  - x: "Add(int(state('tempadjuster.adjustment')))"
  watched_entities:
  - "tempadjuster.adjustment"

  rooms:
  [....]

Reason for posting this:

  1. Is there a better way of achieving this?
  2. Maybe someone finds it useful. (=

Personally, I wrote my own nodeRed code to dynamically control my thermostats. It adjusts the set point based off current outdoor temperature / the time (i like it colder when sleeping) / and home occupancy(proximity… – Added a 2nd Zone to HA and called it Almost home. This zone triggers the temp to adjust between away and home mode)
Here’s a sample of it selecting a setpoint – I’d be happy to go in depth if you like nodeRed and advanced functionality of your thermostat

if (flow.get(["current.outsideTemp"]) < 20)
{
	if (flow.get(["current.occupancy"]) == "home")
	{
		flow.set(["target.setUp"], 60);
		flow.set(["target.setDown"], 61);
	}
	else
	{
		flow.set(["target.setUp"], 54);
		flow.set(["target.setDown"], 55);
	}
	flow.set(["target.modeUp"], "heat");
	flow.set(["target.modeDown"], "heat");
}
else if ((flow.get(["current.outsideTemp"]) > 20) && (flow.get(["current.outsideTemp"]) < 41))
{
	if (flow.get(["current.occupancy"]) == "home")
	{
		flow.set(["target.setUp"], 65);
		flow.set(["target.setDown"], 66);
	}
	else
	{
		flow.set(["target.setUp"], 60);
		flow.set(["target.setDown"], 61);
	}
	flow.set(["target.modeUp"], "heat");
	flow.set(["target.modeDown"], "heat");
}
else if ((flow.get(["current.outsideTemp"]) > 40) && (flow.get(["current.outsideTemp"]) < 51))
{
	if (flow.get(["current.occupancy"]) == "home")
	{
		flow.set(["target.setUp"], 67);
		flow.set(["target.setDown"], 68);
	}
	else
	{
		flow.set(["target.setUp"], 63);
		flow.set(["target.setDown"], 64);
	}
	flow.set(["target.modeUp"], "heat");
	flow.set(["target.modeDown"], "heat");
}
else if ((flow.get(["current.outsideTemp"]) > 50) && (flow.get(["current.outsideTemp"]) < 66))
{
	if (flow.get(["current.occupancy"]) == "home")
	{
		flow.set(["target.setUp"], 67);
		flow.set(["target.setDown"], 68);
	}
	else
	{
		flow.set(["target.setUp"], 65);
		flow.set(["target.setDown"], 66);
	}
	flow.set(["target.modeUp"], "heat");
	flow.set(["target.modeDown"], "heat");
}

Here’s a picture of nodeRed, it makes more sense visually.