homeos/app/Support/Mqtt/RingNormalizer.php

62 lines
2.2 KiB
PHP

<?php
namespace App\Support\Mqtt;
/**
* Normalizes a ring-mqtt state payload into protocol-agnostic entity updates
* ({key, type, state}), the same shape the Shelly path produces. Vendor specifics stay here (H3).
*
* Covers the common Ring doorbell/camera signals (ding, motion, battery, contact, lock). The
* bridge sends plain "ON"/"OFF"/"open"/"closed" for binary sensors and JSON for the info topic.
*/
class RingNormalizer
{
/**
* @return array<int, array{key:string,type:string,state:array<string,mixed>}>
*/
public static function normalize(string $entity, string $rawPayload): array
{
return match ($entity) {
'ding' => [['key' => 'ding', 'type' => 'ding', 'state' => ['on' => self::isOn($rawPayload)]]],
'motion' => [['key' => 'motion', 'type' => 'motion', 'state' => ['on' => self::isOn($rawPayload)]]],
'contact', 'contactSensor', 'contact_sensor' => [['key' => 'contact', 'type' => 'contact', 'state' => ['open' => self::isOpen($rawPayload)]]],
'lock' => [['key' => 'lock', 'type' => 'lock', 'state' => ['locked' => self::isLocked($rawPayload)]]],
'info' => self::fromInfo($rawPayload),
default => [],
};
}
/** The info topic carries a JSON blob with battery/signal attributes. */
private static function fromInfo(string $rawPayload): array
{
$data = json_decode($rawPayload, true);
if (! is_array($data)) {
return [];
}
$updates = [];
$battery = $data['batteryLevel'] ?? $data['battery_level'] ?? null;
if ($battery !== null && is_numeric($battery)) {
$updates[] = ['key' => 'battery', 'type' => 'battery', 'state' => ['percent' => (int) $battery]];
}
return $updates;
}
private static function isOn(string $payload): bool
{
return in_array(strtoupper(trim($payload)), ['ON', 'TRUE', '1'], true);
}
private static function isOpen(string $payload): bool
{
return in_array(strtolower(trim($payload)), ['open', 'on', 'true', '1'], true);
}
private static function isLocked(string $payload): bool
{
return strtolower(trim($payload)) === 'locked';
}
}