Add brightness, media player, siren control + 5 scene keywords

- Add scene keywords: good morning work, master bedroom scene, porch lights (3 variants)
- Add Tier 0a: brightness control handler ("dim X to Y%", "set X to Y%")
- Add Tier 0b: media player on/off/pause for Apple TV and Pioneer receiver
- Add Tier 0c: per-camera siren activate/silence via switch
This commit is contained in:
2026-06-01 14:16:30 +00:00
parent 82e0262a4f
commit 0caff5db57
+151
View File
@@ -159,6 +159,14 @@ $sceneKeywords = [
'front porch lights' => 'scene.outdoors_front_porch_lights', 'front porch lights' => 'scene.outdoors_front_porch_lights',
'porch scene' => 'scene.outdoors_front_porch_lights', 'porch scene' => 'scene.outdoors_front_porch_lights',
'office dawn' => 'scene.office_ocean_dawn', 'office dawn' => 'scene.office_ocean_dawn',
'good morning work' => 'scene.good_morning_work',
'morning work' => 'scene.good_morning_work',
'work morning' => 'scene.good_morning_work',
'master bedroom scene' => 'scene.master_bedroom_new_scene',
'bedroom scene' => 'scene.master_bedroom_new_scene',
'ceiling fan scene' => 'scene.master_bedroom_new_scene',
'porch lights on' => 'scene.outdoors_front_porch_lights',
'porch lights' => 'scene.outdoors_front_porch_lights',
]; ];
$msgLower = strtolower(trim($message)); $msgLower = strtolower(trim($message));
@@ -222,6 +230,13 @@ if (!$reply && preg_match('/(turn|switch|put|set)\s+(on|off)/i', $message, $acti
$searchMsg = preg_replace('/\b(turn|switch|put|set|the|my|all|please|jarvis|on|off|lights?|lamps?|plugs?|strips?)\b/i', ' ', $msgLower); $searchMsg = preg_replace('/\b(turn|switch|put|set|the|my|all|please|jarvis|on|off|lights?|lamps?|plugs?|strips?)\b/i', ' ', $msgLower);
$searchMsg = trim(preg_replace('/\s+/', ' ', $searchMsg)); $searchMsg = trim(preg_replace('/\s+/', ' ', $searchMsg));
// Empty search means generic light command (e.g. "turn on the lights") — all lights
if ($searchMsg === '' && $preferLight) {
$bestEid = '__all_lights__';
$bestName = 'All lights';
$bestScore = 1;
}
foreach ($haEntityMap as $eid => $info) { foreach ($haEntityMap as $eid => $info) {
if (($info['state'] ?? '') === 'unavailable') continue; if (($info['state'] ?? '') === 'unavailable') continue;
$nameLower = strtolower($info['name']); $nameLower = strtolower($info['name']);
@@ -324,6 +339,142 @@ if (!$reply && preg_match('/(is|are|what.s|status|state).*(on|off|light|switch|p
} }
// ── Tier 0a: Brightness control ──────────────────────────────────────────
if (!$reply && preg_match('/\b(dim|brighten|brightness|set.*to\s+\d+\s*%|percent)\b/i', $message) && !empty($haEntityMap)) {
$pctMatch = null;
if (preg_match('/(\d+)\s*%/', $message, $pm)) {
$pctMatch = max(1, min(100, (int)$pm[1]));
} elseif (preg_match('/\b(full|max|maximum|hundred)\b/i', $message)) {
$pctMatch = 100;
} elseif (preg_match('/\b(half|fifty)\b/i', $message)) {
$pctMatch = 50;
} elseif (preg_match('/\b(low|dim|minimum|min|quarter)\b/i', $message)) {
$pctMatch = 20;
}
if ($pctMatch !== null) {
$searchMsg = preg_replace('/\b(dim|brighten|brightness|set|the|my|to|at|percent|%|\d+|jarvis|light|lights?)\b/i', ' ', $msgLower);
$searchMsg = trim(preg_replace('/\s+/', ' ', $searchMsg));
$bestEid = null; $bestScore = 0; $bestName = '';
foreach ($haEntityMap as $eid => $info) {
if ($info['domain'] !== 'light') continue;
if (($info['state'] ?? '') === 'unavailable') continue;
$nameLower = strtolower($info['name']);
$score = 0;
if ($searchMsg === '') { $bestEid = '__all_lights__'; $bestName = 'All lights'; $bestScore = 1; break; }
$words = array_filter(explode(' ', $searchMsg));
foreach ($words as $w) { if (strlen($w) > 2 && strpos($nameLower, $w) !== false) $score += 10; }
if ($score > $bestScore) { $bestScore = $score; $bestEid = $eid; $bestName = $info['name']; }
}
if ($bestEid && $bestScore >= 0) {
$haUrl = defined('HA_URL') ? HA_URL : 'http://10.48.200.97:8123';
$haToken = defined('HA_TOKEN') ? HA_TOKEN : '';
$brightness = (int)round($pctMatch * 2.55);
if ($bestEid === '__all_lights__') {
$lightIds = array_keys(array_filter($haEntityMap, fn($e) => $e['domain'] === 'light'));
$payload = ['entity_id' => $lightIds, 'brightness' => $brightness];
} else {
$payload = ['entity_id' => $bestEid, 'brightness' => $brightness];
}
$ch = curl_init($haUrl . '/api/services/light/turn_on');
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_POST=>true,
CURLOPT_POSTFIELDS=>json_encode($payload),
CURLOPT_HTTPHEADER=>['Authorization: Bearer '.$haToken,'Content-Type: application/json'],
CURLOPT_TIMEOUT=>8, CURLOPT_CONNECTTIMEOUT=>3]);
$haCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_exec($ch); $haCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
if ($haCode === 200) {
$label = ($bestEid === '__all_lights__') ? 'All lights' : $bestName;
$reply = "{$label} set to {$pctMatch}%, {$userAddr}.";
$source = 'ha:brightness';
}
}
}
}
// ── Tier 0b: Media player control ────────────────────────────────────────
if (!$reply && preg_match('/\b(apple tv|appletv|pioneer|receiver|tv|television)\b/i', $message)
&& preg_match('/\b(turn|switch|power|on|off|pause|play|resume|stop|mute)\b/i', $message) && !empty($haEntityMap)) {
$mediaTurnOn = (bool) preg_match('/\b(on|play|resume|start)\b/i', $message);
$mediaTurnOff = (bool) preg_match('/\b(off|stop|shutdown|power off)\b/i', $message);
$mediaPause = (bool) preg_match('/\b(pause|mute)\b/i', $message);
$isAppleTV = (bool) preg_match('/\b(apple.?tv|appletv)\b/i', $message);
$isPioneer = (bool) preg_match('/\b(pioneer|receiver)\b/i', $message);
$isTv = (bool) preg_match('/\b(tv|television)\b/i', $message);
$targetEid = null; $targetName = '';
if ($isAppleTV) {
$targetEid = 'media_player.apple_tv_4k';
$targetName = 'Apple TV 4K';
} elseif ($isPioneer) {
$targetEid = 'media_player.vsx_822_2';
$targetName = 'Pioneer Receiver';
} elseif ($isTv) {
$targetEid = 'media_player.apple_tv_4k';
$targetName = 'Apple TV';
}
if ($targetEid) {
$haUrl = defined('HA_URL') ? HA_URL : 'http://10.48.200.97:8123';
$haToken = defined('HA_TOKEN') ? HA_TOKEN : '';
if ($mediaPause) {
$svc = 'media_player/media_pause';
} elseif ($mediaTurnOff) {
$svc = 'media_player/turn_off';
} else {
$svc = 'media_player/turn_on';
}
$ch = curl_init($haUrl . '/api/services/' . $svc);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_POST=>true,
CURLOPT_POSTFIELDS=>json_encode(['entity_id'=>$targetEid]),
CURLOPT_HTTPHEADER=>['Authorization: Bearer '.$haToken,'Content-Type: application/json'],
CURLOPT_TIMEOUT=>8, CURLOPT_CONNECTTIMEOUT=>3]);
curl_exec($ch); $haCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
if ($haCode === 200) {
if ($mediaPause) $verb = 'paused';
elseif ($mediaTurnOff) $verb = 'powered off';
else $verb = 'powered on';
$reply = "{$targetName} {$verb}, {$userAddr}.";
$source = 'ha:media';
}
}
}
// ── Tier 0c: Camera siren toggle ─────────────────────────────────────────
if (!$reply && preg_match('/\b(siren|alarm|honk|sound (the )?alarm)\b/i', $message)
&& preg_match('/\b(camera|back yard|backyard|carport|driveway|front yard)\b/i', $message)) {
$sirenOn = !preg_match('/\b(off|stop|disable|silence)\b/i', $message);
$haService = $sirenOn ? 'turn_on' : 'turn_off';
$sirenMap = [
'back yard' => ['switch.camera1_siren_on_event_2', 'Back Yard'],
'backyard' => ['switch.camera1_siren_on_event_2', 'Back Yard'],
'carport' => ['switch.camera1_siren_on_event_3', 'Carport'],
'front yard' => ['switch.front_yard_siren_on_event', 'Front Yard'],
'driveway' => ['switch.down_hill_siren_on_event', 'Driveway'],
];
$targetSiren = null; $targetLabel = 'Camera';
foreach ($sirenMap as $kw => [$eid, $lbl]) {
if (strpos($msgLower, $kw) !== false) { $targetSiren = $eid; $targetLabel = $lbl; break; }
}
if (!$targetSiren) {
$targetSiren = 'switch.camera1_siren_on_event_2';
$targetLabel = 'Back Yard';
}
$haUrl = defined('HA_URL') ? HA_URL : 'http://10.48.200.97:8123';
$haToken = defined('HA_TOKEN') ? HA_TOKEN : '';
$ch = curl_init($haUrl . '/api/services/switch/' . $haService);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_POST=>true,
CURLOPT_POSTFIELDS=>json_encode(['entity_id'=>$targetSiren]),
CURLOPT_HTTPHEADER=>['Authorization: Bearer '.$haToken,'Content-Type: application/json'],
CURLOPT_TIMEOUT=>8, CURLOPT_CONNECTTIMEOUT=>3]);
curl_exec($ch); $haCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
if ($haCode === 200) {
$verb = $sirenOn ? 'activated' : 'silenced';
$reply = "{$targetLabel} camera siren {$verb}, {$userAddr}.";
$source = 'ha:siren';
}
}
// ── Email + Planner voice intents (action items, create task/appt from email) ─ // ── Email + Planner voice intents (action items, create task/appt from email) ─
if (!$reply && preg_match('/\b(email|emails|inbox|gmail|outlook|mail|unread|messages)\b/i', $message)) { if (!$reply && preg_match('/\b(email|emails|inbox|gmail|outlook|mail|unread|messages)\b/i', $message)) {
$lc = strtolower($message); $lc = strtolower($message);