Skip to content
These pages describe Stewart 1.x. Read the docs for the latest release, and how to upgrade.

Heating by presence

Sets the thermostats to away once everyone has been gone for 15 minutes, and back to home as soon as someone returns. A quick trip to the shop changes nothing.

You need a group or person that is home or not_home (such as a group of person entities), and thermostats with away and home presets.

apps/PresenceHeating.php
<?php
declare(strict_types=1);
namespace App;
use Stewart\Contracts\App;
use Stewart\Contracts\Automation;
use Stewart\Contracts\HaContext;
use Stewart\Contracts\Service\ServiceTarget;
use Stewart\Contracts\Time\Duration;
#[Automation(id: 'presence-heating')]
final readonly class PresenceHeating implements App
{
/** @param list<string> $thermostats */
public function __construct(
private HaContext $ha,
private string $household = 'group.family',
private array $thermostats = ['climate.living_room', 'climate.bedroom'],
private int $awayAfterMinutes = 15,
) {}
public function initialize(): void
{
$this->ha->watchStateChanges($this->household)
->whenChangedTo('not_home', for: Duration::minutes($this->awayAfterMinutes))
->subscribe(fn() => $this->setPreset('away'));
$this->ha->watchStateChanges($this->household)
->whenChangedTo('home', from: 'not_home')
->subscribe(fn() => $this->setPreset('home'));
}
private function setPreset(string $preset): void
{
$this->ha->callAction('climate.set_preset_mode', ['preset_mode' => $preset], ServiceTarget::forEntities(...$this->thermostats));
}
}
apps:
presence-heating:
options:
household: group.family
thermostats: [climate.living_room, climate.bedroom, climate.office]
awayAfterMinutes: 15
  • whenChangedTo('not_home', for: …) fires only after the household has been away for the whole time.
  • whenChangedTo('home', from: 'not_home') fires on the way back, but not when Home Assistant restarts and the group goes from unknown to home.
  • One service call targets every thermostat at once.
tests/PresenceHeatingTest.php
<?php
declare(strict_types=1);
namespace App\Tests;
use App\PresenceHeating;
use PHPUnit\Framework\TestCase;
use Stewart\Contracts\Time\Duration;
use Stewart\Testing\App\AppHarness;
final class PresenceHeatingTest extends TestCase
{
private AppHarness $harness;
protected function setUp(): void
{
$this->harness = new AppHarness();
$this->harness->ha->seedState('group.family', 'home');
$this->harness->startApp(PresenceHeating::class);
}
public function testTheHeatingGoesToAwayOnceEveryoneHasLeft(): void
{
$this->harness->ha->pushState('group.family', 'not_home');
$this->harness->advanceTime(Duration::minutes(15));
$call = $this->harness->ha->getLastCall();
self::assertSame(['preset_mode' => 'away'], $call->data);
self::assertSame(['climate.living_room', 'climate.bedroom'], $call->listTargetedEntityIds());
}
public function testAQuickTripToTheShopChangesNothing(): void
{
$this->harness->ha->pushState('group.family', 'not_home');
$this->harness->advanceTime(Duration::minutes(10));
$this->harness->ha->pushState('group.family', 'home');
self::assertSame(['preset_mode' => 'home'], $this->harness->ha->getLastCall()->data);
self::assertCount(1, $this->harness->ha->calls);
}
}
  • Thermostats without presets: call climate.set_temperature with a temperature instead.
  • Heat up before you arrive: use a zone trigger for “entering a zone around home”.