mirror of
https://github.com/myronblair/jarvis
synced 2026-06-30 17:50:23 -05:00
feat: HA scene control via voice
- ha.php: GET scenes action returns live scene list from HA; POST scene_activate triggers by entity_id - chat.php: ha_scene intent fetches all scenes live, fuzzy token-matches against message, calls scene.turn_on; falls back to listing available scenes if no match - KB intents: 14 patterns covering good night/morning/goodbye/kitchen lights/ocean dawn/porch + generic activate/run/trigger/set scene Scenes available: Good Night, Good Morning, Good Morning Work, Goodbye, Kitchen Lights On/Off, Front Porch Lights, Office Ocean Dawn, Master Bedroom Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1647,6 +1647,57 @@ if (!$reply) {
|
||||
if ($matched && $matched['action'] === 'action') {
|
||||
switch ($matched['intent']) {
|
||||
|
||||
case 'ha_scene': {
|
||||
// Fetch all HA scenes and fuzzy-match against the message
|
||||
$haScenes = @json_decode(@file_get_contents(
|
||||
HA_URL . '/api/states',
|
||||
false,
|
||||
stream_context_create(['http' => ['timeout' => 5, 'header' => 'Authorization: Bearer ' . HA_TOKEN]])
|
||||
), true) ?? [];
|
||||
|
||||
$scenes = array_filter($haScenes, fn($s) => str_starts_with($s['entity_id'] ?? '', 'scene.'));
|
||||
$msgLow = strtolower($message);
|
||||
|
||||
// Score each scene against the message
|
||||
$best = null; $bestScore = 0;
|
||||
foreach ($scenes as $s) {
|
||||
$name = strtolower($s['attributes']['friendly_name'] ?? '');
|
||||
$id = strtolower(str_replace(['scene.', '_'], ['', ' '], $s['entity_id']));
|
||||
// Token overlap score
|
||||
$tokens = array_filter(explode(' ', "$name $id"));
|
||||
$score = 0;
|
||||
foreach ($tokens as $tok) {
|
||||
if (strlen($tok) > 2 && str_contains($msgLow, $tok)) $score++;
|
||||
}
|
||||
// Bonus for exact phrase match
|
||||
if ($name && str_contains($msgLow, $name)) $score += 5;
|
||||
if ($score > $bestScore) { $bestScore = $score; $best = $s; }
|
||||
}
|
||||
|
||||
if ($best && $bestScore >= 1) {
|
||||
$sceneName = $best['attributes']['friendly_name'] ?? $best['entity_id'];
|
||||
@file_get_contents(
|
||||
HA_URL . '/api/services/scene/turn_on',
|
||||
false,
|
||||
stream_context_create(['http' => [
|
||||
'method' => 'POST',
|
||||
'header' => "Authorization: Bearer " . HA_TOKEN . "
|
||||
Content-Type: application/json",
|
||||
'content' => json_encode(['entity_id' => $best['entity_id']]),
|
||||
'timeout' => 5,
|
||||
]])
|
||||
);
|
||||
$reply = "Activating {$sceneName}, {$userAddr}.";
|
||||
} else {
|
||||
// List available scenes
|
||||
$names = array_map(fn($s) => $s['attributes']['friendly_name'] ?? $s['entity_id'], $scenes);
|
||||
$list = implode(', ', array_values($names));
|
||||
$reply = "I didn't catch which scene, {$userAddr}. Available scenes: {$list}.";
|
||||
}
|
||||
$source = 'intent:ha_scene';
|
||||
break;
|
||||
}
|
||||
|
||||
case 'restart_agent':
|
||||
// Extract target hostname from message
|
||||
$msgLow = strtolower($message);
|
||||
|
||||
@@ -38,6 +38,34 @@ if (!$configured) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Scene list
|
||||
if ($method === 'GET' && $action === 'scenes') {
|
||||
$states = haRequest('/states') ?? [];
|
||||
$scenes = [];
|
||||
foreach ($states as $s) {
|
||||
if (str_starts_with($s['entity_id'] ?? '', 'scene.')) {
|
||||
$scenes[] = [
|
||||
'entity_id' => $s['entity_id'],
|
||||
'name' => $s['attributes']['friendly_name'] ?? $s['entity_id'],
|
||||
];
|
||||
}
|
||||
}
|
||||
echo json_encode(['scenes' => $scenes]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Scene activate
|
||||
if ($method === 'POST' && $action === 'scene_activate') {
|
||||
$entity_id = $data['entity_id'] ?? '';
|
||||
if ($entity_id && str_starts_with($entity_id, 'scene.')) {
|
||||
$result = haRequest('/services/scene/turn_on', 'POST', ['entity_id' => $entity_id]);
|
||||
echo json_encode(['success' => true, 'entity_id' => $entity_id]);
|
||||
} else {
|
||||
echo json_encode(['error' => 'Invalid scene entity_id']);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// Live service call (toggle device) — always direct to HA, never cached
|
||||
if ($method === 'POST' && $action === 'service') {
|
||||
$domain = $data['domain'] ?? '';
|
||||
|
||||
Reference in New Issue
Block a user