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.
<?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')); }
public function dispose(): void {}
private function setPreset(string $preset): void { $this->ha->callService('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: 15How it works
Section titled “How it works”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 fromunknowntohome.- One service call targets every thermostat at once.
<?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); }}Make it yours
Section titled “Make it yours”- Thermostats without presets: call
climate.set_temperaturewith atemperatureinstead. - Heat up before you arrive: use a zone trigger for “entering a zone around home”.