homeos/app/Console/Commands/PresencePollCommand.php

76 lines
2.2 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Events\PresenceChanged;
use App\Models\Person;
use App\Services\UnifiClient;
use Illuminate\Console\Command;
/**
* Presence sweep. "home" immediately on association; "away" only after a debounce
* (iPhone WLAN sleep causes false-aways otherwise). Never hangs a security automation
* on a single raw signal.
*/
class PresencePollCommand extends Command
{
protected $signature = 'presence:poll';
protected $description = 'Poll UniFi for client presence and update persons.';
private const AWAY_DEBOUNCE_MINUTES = 8;
public function handle(UnifiClient $unifi): int
{
if (! $unifi->isConfigured()) {
$this->warn('UniFi is not configured (UNIFI_* env).');
return self::SUCCESS;
}
try {
$connected = array_flip($unifi->connectedMacs());
} catch (\Throwable $e) {
report($e);
$this->error('UniFi poll failed: '.$e->getMessage());
return self::FAILURE;
}
$this->info('UniFi active clients: '.count($connected));
$debounce = now()->subMinutes(self::AWAY_DEBOUNCE_MINUTES);
foreach (Person::whereNotNull('mac')->get() as $person) {
$home = isset($connected[strtolower($person->mac)]);
if ($home) {
$person->last_seen_home_at = now();
if ($person->presence !== 'home') {
$this->markPresence($person, 'home');
} else {
$person->save();
}
continue;
}
// Not associated: flip to away only once the debounce has elapsed.
$seenLongAgo = $person->last_seen_home_at === null || $person->last_seen_home_at->lt($debounce);
if ($person->presence !== 'away' && $seenLongAgo) {
$this->markPresence($person, 'away');
}
}
return self::SUCCESS;
}
private function markPresence(Person $person, string $presence): void
{
$person->presence = $presence;
$person->presence_changed_at = now();
$person->save();
PresenceChanged::dispatch($person->uuid);
}
}