Add PowerShell script for Home Assistant config checks in Docker and Supervisor modes

This commit is contained in:
Carlo Costanzo
2026-04-11 12:34:24 -04:00
parent fe43b175c2
commit 29b23ab8f6
4 changed files with 169 additions and 42 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -12,7 +12,7 @@
# - Treat 2+ minutes in a room as "being cleaned" and dequeue immediately (queue = remaining rooms).
# - Phase changes happen only after verified completion at dock (`task_status: completed`).
# - Guarded fallback: if docked with empty queue for 10 minutes but no `completed`, advance with `fallback_advance` log.
# - Use `dreame_vacuum.vacuum_clean_segment` with the integration's segment ids.
# - This installed Dreame build still needs native `dreame_vacuum.vacuum_clean_segment`; do not rely on HA `vacuum.clean_area`.
# - Jinja2 loop scoping: use a `namespace` when building lists (otherwise the queue can appear empty and get cleared).
# - If docked+completed still has queue entries, treat queue as stale and clear it before phase advance.
# - Mop phases use `sweeping_and_mopping` instead of mop-only.
@@ -133,8 +133,6 @@ script:
{{ bath_ids }}
{% endif %}
segments_to_clean: "{{ queue_ints if queue_ints | length > 0 else phase_segments }}"
segment_ids: "{{ segments_to_clean | list }}"
# 0. Reseed the current phase when queue is empty.
- choose:
- conditions:
@@ -178,11 +176,11 @@ script:
- service: script.send_to_logbook
data:
topic: "VACUUM"
message: "Vacuum is already cleaning; queue/phase updated but not issuing a new segment clean action."
message: "Vacuum is already cleaning; queue/phase updated but not issuing a new segment-clean action."
- stop: "Already cleaning."
default: []
# 4. Start cleaning
# 4. Start cleaning with the native Dreame segment service.
- service: select.select_option
target:
entity_id: select.l10s_vacuum_cleaning_mode
@@ -197,8 +195,7 @@ script:
target:
entity_id: vacuum.l10s_vacuum
data:
# Clean the queued room segments directly.
segments: "{{ segment_ids }}"
segments: "{{ segments_to_clean }}"
## 3. Automations

View File

@@ -8,82 +8,116 @@
:root { color-scheme: light; }
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: linear-gradient(180deg, #f5f7fb 0%, #e8eef8 100%);
color: #122033;
font-family: "Segoe UI", -apple-system, BlinkMacSystemFont, sans-serif;
background:
radial-gradient(circle at top left, rgba(126, 34, 206, 0.16), transparent 28rem),
radial-gradient(circle at bottom right, rgba(22, 101, 52, 0.14), transparent 26rem),
linear-gradient(180deg, #eef2ff 0%, #f6f8fc 48%, #eef6f2 100%);
color: #18243a;
}
main {
max-width: 32rem;
max-width: 36rem;
margin: 0 auto;
min-height: 100vh;
padding: 2rem 1.25rem;
padding: 2rem 1.25rem 2.5rem;
display: flex;
flex-direction: column;
justify-content: center;
gap: 1rem;
}
.card {
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(18, 32, 51, 0.08);
border-radius: 18px;
box-shadow: 0 18px 40px rgba(18, 32, 51, 0.12);
padding: 1.25rem;
background: rgba(255, 255, 255, 0.92);
border: 1px solid rgba(126, 34, 206, 0.14);
border-radius: 24px;
box-shadow: 0 22px 55px rgba(24, 36, 58, 0.14);
padding: 1.5rem;
backdrop-filter: blur(10px);
}
.eyebrow {
display: inline-flex;
align-items: center;
padding: 0.35rem 0.7rem;
border-radius: 999px;
background: rgba(126, 34, 206, 0.1);
color: #6b21a8;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
margin-bottom: 0.9rem;
}
h1 {
margin: 0 0 0.5rem;
font-size: 1.35rem;
line-height: 1.2;
font-size: clamp(1.6rem, 5vw, 2rem);
line-height: 1.1;
}
p {
margin: 0;
line-height: 1.5;
line-height: 1.55;
}
.title {
margin-top: 0.75rem;
font-size: 0.95rem;
color: #41556f;
margin-top: 1rem;
padding: 0.95rem 1rem;
border-radius: 16px;
background: linear-gradient(180deg, rgba(126, 34, 206, 0.06), rgba(255, 255, 255, 0.92));
border: 1px solid rgba(126, 34, 206, 0.12);
font-size: 0.98rem;
color: #4a5670;
word-break: break-word;
}
.actions {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-top: 1rem;
gap: 0.85rem;
margin-top: 1.15rem;
}
.button {
display: block;
text-decoration: none;
text-align: center;
padding: 0.9rem 1rem;
border-radius: 12px;
font-weight: 600;
padding: 1rem 1.1rem;
border-radius: 16px;
font-weight: 700;
letter-spacing: 0.01em;
transition: transform 120ms ease, box-shadow 120ms ease, opacity 120ms ease;
box-shadow: 0 10px 24px rgba(24, 36, 58, 0.1);
}
.button:hover {
transform: translateY(-1px);
}
.button.primary {
background: #166534;
background: linear-gradient(135deg, #7c3aed 0%, #5b21b6 100%);
color: #fff;
}
.button.secondary {
background: #fff;
color: #122033;
border: 1px solid rgba(18, 32, 51, 0.14);
color: #18243a;
border: 1px solid rgba(24, 36, 58, 0.12);
}
.button.muted {
background: rgba(255, 255, 255, 0.7);
color: #52627d;
border: 1px dashed rgba(24, 36, 58, 0.18);
box-shadow: none;
}
.hint {
font-size: 0.88rem;
color: #5c708a;
margin-top: 1rem;
font-size: 0.9rem;
color: #5f6f87;
}
</style>
</head>
<body>
<main>
<section class="card">
<div class="eyebrow">OneNote launch</div>
<h1>Opening OneNote</h1>
<p>Joanna is handing this note to the OneNote app now.</p>
<p>Joanna found the note and prepared the cleanest handoff path.</p>
<p id="note-title" class="title"></p>
<div class="actions">
<a id="app-link" class="button primary" href="#">Open in OneNote App</a>
<a id="open-link" class="button primary" href="https://www.onenote.com/">Open in OneNote</a>
<a id="web-link" class="button secondary" href="https://www.onenote.com/">Open Web Copy</a>
</div>
<p class="hint">If the app does not open automatically in a second or two, tap the OneNote button.</p>
<p id="hint" class="hint">If the desktop app is available, it will open automatically. Otherwise use the button above.</p>
</section>
</main>
<script>
@@ -92,9 +126,22 @@
var clientUrl = String(params.get('client') || '').trim();
var webUrl = String(params.get('web') || '').trim() || 'https://www.onenote.com/';
var title = String(params.get('title') || '').trim();
var appLink = document.getElementById('app-link');
var openLink = document.getElementById('open-link');
var webLink = document.getElementById('web-link');
var hintNode = document.getElementById('hint');
var titleNode = document.getElementById('note-title');
var isLaunchableClientUrl = function (value) {
if (!/^onenote:/i.test(value)) {
return false;
}
try {
var inner = new URL(String(value).replace(/^onenote:/i, ''));
var host = String(inner.hostname || '').toLowerCase();
return host === 'd.docs.live.net' || host.endsWith('.sharepoint.com');
} catch (err) {
return false;
}
};
if (title) {
titleNode.textContent = title;
@@ -103,13 +150,16 @@
}
webLink.href = webUrl;
if (!/^onenote:/i.test(clientUrl)) {
appLink.style.display = 'none';
window.location.replace(webUrl);
if (!isLaunchableClientUrl(clientUrl)) {
openLink.href = webUrl;
openLink.textContent = 'Open in OneNote';
webLink.className = 'button muted';
webLink.textContent = 'Web fallback ready';
hintNode.textContent = 'This note opens best through the browser handoff. Use the main button.';
return;
}
appLink.href = clientUrl;
openLink.href = clientUrl;
var fallbackTimer = window.setTimeout(function () {
window.location.replace(webUrl);
}, 1400);

80
tools/ha_check_config.ps1 Normal file
View File

@@ -0,0 +1,80 @@
param(
[string]$TargetHost = 'docker_10',
[string]$ContainerName = 'home-assistant',
[string]$ConfigPath = '/config',
[ValidateSet('auto', 'container', 'supervised')]
[string]$Mode = 'auto'
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$ssh = Get-Command ssh -ErrorAction Stop
function Quote-BashArg {
param(
[Parameter(Mandatory = $true)]
[string]$Value
)
return "'" + $Value.Replace("'", "'`"`'`"`'") + "'"
}
$containerNameQ = Quote-BashArg -Value $ContainerName
$configPathQ = Quote-BashArg -Value $ConfigPath
$containerCheck = @"
if ! command -v docker >/dev/null 2>&1; then
echo Docker CLI not found on host. >&2
exit 127
fi
if ! docker ps --format '{{.Names}}' | grep -Fx $containerNameQ >/dev/null 2>&1; then
echo Container $containerNameQ is not running. >&2
exit 1
fi
echo Running Home Assistant config check in container $containerNameQ...
docker exec $containerNameQ python -m homeassistant --script check_config --config $configPathQ
"@
$supervisedCheck = @'
if ! command -v ha >/dev/null 2>&1; then
echo Home Assistant Supervisor CLI not found on host. >&2
exit 127
fi
echo Running Home Assistant config check via Supervisor CLI...
ha core check
'@
switch ($Mode) {
'supervised' { $remoteCommand = $supervisedCheck }
'container' { $remoteCommand = $containerCheck }
'auto' {
$remoteCommand = @"
if command -v ha >/dev/null 2>&1; then
echo Running Home Assistant config check via Supervisor CLI...
ha core check
elif command -v docker >/dev/null 2>&1; then
$containerCheck
else
echo Neither Home Assistant Supervisor CLI nor Docker CLI is available on host. >&2
exit 127
fi
"@
}
}
$remoteCommand = ($remoteCommand -replace "`r`n", "`n").Trim()
$sshArgs = @(
'-o'
'RemoteCommand=none'
'-o'
'RequestTTY=no'
$TargetHost
'bash'
'-lc'
(Quote-BashArg -Value $remoteCommand)
)
& $ssh.Source @sshArgs
exit $LASTEXITCODE