mirror of
https://github.com/CCOSTAN/Home-AssistantConfig.git
synced 2026-05-03 21:46:45 +00:00
Add PowerShell script for Home Assistant config checks in Docker and Supervisor modes
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 3.6 KiB |
@@ -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
|
||||
|
||||
@@ -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
80
tools/ha_check_config.ps1
Normal 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
|
||||
Reference in New Issue
Block a user