Files
Nikolay1andClaude Opus 4.8 3d0aa8ec29 docs(site): sync landing to 30 tools + add Room Library scenario
Tool count 25 → 30 (meta description + tools card). Add a 4th scenario
card 'Compose a new project' (compose_rooms → allocation manifest + ETS
XML/CSV + device BOM; dry-run, new projects only, R1). Clarify the
read-only thesis: never mutates your source .knxproj or touches the bus.
Update 'three'→'four scenarios' heading + anchor. R2 (docking + device
selection) flagged as planned.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 21:55:00 +02:00

502 lines
35 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>nickol-knx-mcp — design-time KNX/ETS6 assistant (MCP server)</title>
<meta name="description" content="A design-time KNX/ETS6 assistant exposed as an MCP server. Design a project from a spec into a full implementation kit (ETS XML/CSV, report, Home Assistant YAML, test protocol, handover pack); audit & repair an existing .knxproj with concrete fix proposals; generate the Home Assistant layer; or compose a new project from parametrised room templates. Exact vendor device models parsed from ETS app-programs. No live bus access. 30 tools. Includes a full demo house + smart HA brain." />
<style>
:root{
--bg:#0d0f12;--panel:#14171c;--card:#181c22;--card2:#1c2027;--line:#242a33;
--text:#e7eaef;--muted:#8b939e;--faint:#5c646e;
--green:#7bd88f;--amber:#ffcb7d;--blue:#83b9ff;--orange:#ff8a65;--indigo:#9fa8ff;--cyan:#5fd0de;--violet:#c4a7f0;
--r:14px;
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;line-height:1.55;-webkit-font-smoothing:antialiased}
a{color:var(--blue);text-decoration:none}
a:hover{text-decoration:underline}
.wrap{max-width:1060px;margin:0 auto;padding:0 20px}
code,.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.92em}
/* nav */
nav{position:sticky;top:0;z-index:20;background:rgba(13,15,18,.82);backdrop-filter:blur(10px);border-bottom:1px solid var(--line)}
nav .wrap{display:flex;align-items:center;gap:18px;height:56px}
nav .brand{font-weight:700;display:flex;align-items:center;gap:8px}
nav .brand .dot{width:9px;height:9px;border-radius:50%;background:var(--green);box-shadow:0 0 8px #7bd88f88}
nav .links{display:flex;gap:16px;margin-left:auto;font-size:14px;color:var(--muted);align-items:center;flex-wrap:wrap}
nav .links a{color:var(--muted)}
nav .gh{background:#222831;border:1px solid var(--line);color:var(--text)!important;padding:6px 12px;border-radius:9px;font-weight:600}
@media(max-width:720px){.nav-hide{display:none}}
/* hero */
header.hero{padding:64px 0 40px;border-bottom:1px solid var(--line);background:
radial-gradient(700px 300px at 80% -10%, #1c2a2033, transparent),
radial-gradient(600px 260px at 0% 0%, #1a2230aa, transparent)}
.pills{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:18px}
.pill{font-size:12px;font-weight:600;padding:4px 11px;border-radius:20px;border:1px solid var(--line);background:#161a20;color:var(--muted)}
.pill.beta{color:var(--amber);border-color:#3a2f1a}
.pill.mit{color:var(--green);border-color:#1f3327}
h1{font-size:42px;line-height:1.1;margin:6px 0 12px;letter-spacing:-.5px}
h1 .grad{background:linear-gradient(90deg,#7bd88f,#5fd0de);-webkit-background-clip:text;background-clip:text;color:transparent}
.tagline{font-size:18px;color:var(--muted);max-width:680px}
.cta{display:flex;gap:12px;margin-top:26px;flex-wrap:wrap}
.btn{padding:11px 18px;border-radius:11px;font-weight:600;border:1px solid var(--line);background:var(--card);color:var(--text)!important;display:inline-flex;gap:8px;align-items:center}
.btn.primary{background:linear-gradient(90deg,#2f6b45,#357a4e);border-color:#3a7a52}
.btn:hover{text-decoration:none;border-color:#3a444f}
section{padding:52px 0;border-bottom:1px solid var(--line)}
.eyebrow{color:var(--green);font-weight:700;font-size:13px;letter-spacing:.6px;text-transform:uppercase}
h2{font-size:28px;margin:8px 0 6px;letter-spacing:-.3px}
.lead{color:var(--muted);max-width:760px}
.grid{display:grid;gap:16px}
.g2{grid-template-columns:1fr 1fr}
.g3{grid-template-columns:repeat(3,1fr)}
.g4{grid-template-columns:repeat(4,1fr)}
@media(max-width:760px){.g2,.g3,.g4{grid-template-columns:1fr}}
.card{background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:18px}
.card h3{margin:0 0 6px;font-size:16px}
.card p{margin:0;color:var(--muted);font-size:14px}
.stat{font-size:34px;font-weight:800}
.stat.green{color:var(--green)}.stat.blue{color:var(--blue)}.stat.amber{color:var(--amber)}.stat.violet{color:var(--violet)}
.stat-l{color:var(--muted);font-size:13px;margin-top:2px}
table{width:100%;border-collapse:collapse;font-size:14px;margin-top:10px}
th,td{text-align:left;padding:9px 10px;border-bottom:1px solid var(--line);vertical-align:top}
th{color:var(--muted);font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.4px}
.ok{color:var(--green)}.warn{color:var(--amber)}
/* architecture stack */
.stack{display:flex;flex-direction:column;gap:8px;margin-top:8px}
.layer{display:flex;align-items:center;gap:14px;background:var(--card);border:1px solid var(--line);border-radius:12px;padding:13px 15px}
.layer .ll{font-weight:700;width:120px;color:var(--text);flex-shrink:0}
.layer .ld{color:var(--muted);font-size:14px}
.layer.knx{background:#15191f;border-style:dashed}
.layer .tag{margin-left:auto;font-size:11px;color:var(--faint);font-family:ui-monospace,monospace}
/* ---------- dashboard mock ---------- */
.dash{margin-top:22px;background:#111317;border:1px solid var(--line);border-radius:18px;overflow:hidden}
.dash-bar{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--line);flex-wrap:wrap;gap:8px}
.dash-title{font-weight:700;display:flex;align-items:center;gap:8px}
.tabs{display:flex;gap:4px;flex-wrap:wrap}
.tab{font-size:13px;color:var(--muted);padding:6px 12px;border-radius:9px;cursor:pointer;border:1px solid transparent;user-select:none}
.tab.active{color:#fff;background:#262c34;border-color:#2f3742}
.dash-body{padding:16px}
.view{display:none}
.view.active{display:block}
.badges{display:flex;gap:7px;flex-wrap:wrap;margin-bottom:14px}
.badge{display:flex;align-items:center;gap:6px;background:#171a1f;border:1px solid var(--line);border-radius:20px;padding:4px 11px 4px 5px;font-size:12.5px;font-weight:600}
.bic{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px}
.dgrid{display:grid;gap:11px;grid-template-columns:repeat(3,1fr)}
@media(max-width:760px){.dgrid{grid-template-columns:1fr}}
.col{display:flex;flex-direction:column;gap:9px}
.dh{font-weight:700;font-size:12px;color:#9aa0a8;text-transform:uppercase;letter-spacing:.4px;margin:2px}
.dc{background:#181b21;border:1px solid var(--line);border-radius:13px;padding:12px 13px}
.tile{display:flex;align-items:center;gap:11px}
.tic{width:36px;height:36px;border-radius:10px;background:#23262d;display:flex;align-items:center;justify-content:center;font-size:17px;flex-shrink:0}
.tn{font-weight:600;font-size:13.5px}.ts{font-size:12px;color:var(--muted)}.ts.on{color:var(--amber)}
.mode{font-size:20px;font-weight:700}
.chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
.chips span{background:#23262d;border-radius:7px;padding:3px 8px;font-size:11.5px;color:#b9bfc7}
.chips b{color:#fff}
.scenes{display:grid;grid-template-columns:1fr 1fr;gap:9px}
.scene{background:#181b21;border:1px solid var(--line);border-radius:13px;padding:13px 8px;text-align:center;font-size:12.5px;font-weight:600}
.scene .si{font-size:21px;margin-bottom:5px}
.gz{display:flex;justify-content:center;padding:6px}
.gv{fill:#fff;font-size:18px;font-weight:700}.gu{fill:#8b9199;font-size:7px}
.clim{display:flex;flex-direction:column;gap:7px}
.ctop{display:flex;justify-content:space-between;align-items:center}
.cmode{font-size:11px;color:var(--orange);background:#ff8a651a;padding:2px 8px;border-radius:7px;font-weight:600}
.cmode.cool{color:var(--cyan);background:#5fd0de1a}
.ctemp{font-size:19px;font-weight:700}.carr{color:#5c636c;margin:0 6px;font-weight:400}.ctemp b{color:var(--amber)}
.bar{height:6px;background:#23262d;border-radius:4px;overflow:hidden}
.bar i{display:block;height:100%;background:linear-gradient(90deg,#ff8a65,#ffcb7d);border-radius:4px}
.bar.b i{background:linear-gradient(90deg,#83b9ff,#5fd0de)}
.cfoot{font-size:11.5px;color:var(--muted)}
.row2{display:grid;grid-template-columns:1fr 1fr;gap:9px}
.spark{width:100%;height:46px}
.note{font-size:12px;color:var(--faint);margin-top:10px;text-align:center}
/* real screenshot gallery */
.shots{display:grid;grid-template-columns:repeat(2,1fr);gap:14px;margin-top:22px}
@media(max-width:760px){.shots{grid-template-columns:1fr}}
.shots a{display:block;background:var(--card);border:1px solid var(--line);border-radius:12px;overflow:hidden;text-decoration:none}
.shots a:hover{border-color:#3a444f}
.shots img{display:block;width:100%;height:auto;border-bottom:1px solid var(--line)}
.shots span{display:block;padding:10px 13px;color:var(--muted);font-size:13px}
.shots a.hero{grid-column:1 / -1}
footer{padding:40px 0 60px;color:var(--muted);font-size:13px}
.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0)}
</style>
</head>
<body>
<h1 class="sr-only">nickol-knx-mcp — a design-time KNX/ETS6 assistant exposed as an MCP server, with a full demo house and a smart Home Assistant control layer.</h1>
<nav><div class="wrap">
<div class="brand"><span class="dot"></span> nickol-knx-mcp</div>
<div class="links">
<a class="nav-hide" href="#what">What</a>
<a class="nav-hide" href="#scenarios">Scenarios</a>
<a class="nav-hide" href="#demo">Demo house</a>
<a href="#dashboard">Dashboard</a>
<a class="nav-hide" href="#brain">The brain</a>
<a class="nav-hide" href="https://github.com/NickoScope/nickol-knx-mcp/discussions/1">💬 Discuss</a>
<a href="https://github.com/NickoScope/nickol-knx-mcp" class="gh">★ GitHub</a>
</div>
</div></nav>
<header class="hero"><div class="wrap">
<div class="pills">
<span class="pill mit">MIT</span>
<span class="pill beta">public beta · v0.8.0</span>
<span class="pill">Python 3.10+</span>
<span class="pill">Model Context Protocol</span>
</div>
<h1>The missing <span class="grad">ETS6 ↔ Claude</span><br>design-time layer for KNX</h1>
<p class="tagline">A design-time KNX/ETS6 assistant exposed as an <b>MCP server</b>.
<b>Design</b> a project from a spec into a full implementation kit (ETS XML/CSV · report ·
HA YAML · test protocol · handover pack), <b>audit &amp; repair</b> an existing
<code>.knxproj</code> with concrete fix proposals, and <b>generate</b> the Home Assistant
layer — <b>without ever touching the live bus.</b></p>
<div class="cta">
<a class="btn primary" href="https://github.com/NickoScope/nickol-knx-mcp">★ Star on GitHub</a>
<a class="btn" href="#dashboard">▶ See the demo dashboard</a>
<a class="btn" href="https://github.com/NickoScope/nickol-knx-mcp/releases">Releases</a>
</div>
</div></header>
<!-- WHAT -->
<section id="what"><div class="wrap">
<div class="eyebrow">Why it exists</div>
<h2>Two layers, one source of truth</h2>
<p class="lead">As of 2026 there is no off-the-shelf ETS6 ↔ AI tool. This fills exactly the
<b>design-time</b> gap — and the architecture keeps the dangerous part impossible by design.</p>
<div class="stack" style="margin-top:18px">
<div class="layer"><div class="ll">🧠 Home Assistant</div><div class="ld">The brain — time/season/presence-aware automations, scenes, statistics. Reads <b>real</b> device state.</div><div class="tag">smart layer</div></div>
<div class="layer knx"><div class="ll">🦾 KNX / ETS</div><div class="ld">The body — group addresses are the muscles (commands) &amp; nerves (status/sensors). Runs autonomously.</div><div class="tag">.knxproj = truth</div></div>
</div>
<div class="grid g3" style="margin-top:18px">
<div class="card"><h3>🔒 No bus access</h3><p>Zero networking/bus libraries in the dependency tree. <code>bus_access: false</code> — structural, not a promise.</p></div>
<div class="card"><h3>🧩 30 MCP tools</h3><p><b>Validate</b> (naming · DPT &amp; sub-DPT · status · KNX Secure · Matter) · <b>repair</b> (propose fixes, not just flag) · <b>decompose devices</b> into GA recipes or <b>exact vendor models</b> · <b>diff</b> two versions · <b>grade</b> completeness · <b>compose</b> a new project from room templates · <b>generate</b> HA YAML, ETS XML/CSV, handover pack, test protocol &amp; KNX IoT. <b>Read-only where it counts</b> — never mutates your source <code>.knxproj</code> or touches the bus. See the <a href="#scenarios">four scenarios ↓</a>.</p></div>
<div class="card"><h3>🌍 EN / DE / RU</h3><p>Classifies by DPT + multilingual name keywords. Validated on ETS 4.2 / 5.0 / 5.5 / 6 fixtures, a real <b>signed ETS6 round-trip</b>, and a <b>685-GA Zennio</b> project (false errors there: 29 → 6).</p></div>
</div>
</div></section>
<!-- SCENARIOS -->
<section id="scenarios"><div class="wrap">
<div class="eyebrow">What you can do</div>
<h2>Four scenarios, one read-only toolchain</h2>
<p class="lead">From a blank specification to a commissioned, smart-home-connected project — or straight
into an existing <code>.knxproj</code> that needs checking and finishing.</p>
<div class="grid g3" style="margin-top:18px">
<div class="card"><h3>📐 1 · Design from a spec</h3><p>Equipment list → real object models per device
(a dimmer channel is <b>5 objects</b>, not one GA; a floor-heating zone is 8) → the professional
<b>logic layer</b> a bare spec never mentions: central &amp; zone macros, scenes, presence logic,
sun/wind shutters, leak→shut-off chains, astro/meteo &amp; date-time, reserves. Out comes the
<b>full implementation kit</b>: ETS-importable XML/CSV · report · HA YAML · acceptance test
protocol · as-built handover pack. Field-checked: reconstructing a real <b>3,600+ GA</b> as-built
project from its spec alone → <b>~92% structural match</b>, zero validation errors.</p></div>
<div class="card"><h3>🔍 2 · Audit, repair &amp; finish</h3><p>Load any ETS5/ETS6
<code>.knxproj</code> (password-protected too) → <code>analyze_all</code>: naming · missing status
objects · DPT &amp; <b>sub-DPT sanity</b> · KNX Secure posture · Matter readiness. Then
<b>concrete fixes, not just flags</b>: inferred DPTs, synthesised status GAs, absolute-brightness
additions — <b>145 proposals on a real 3,646-GA project</b>. Grade skeleton→as-built, diff two
revisions, regenerate the report, handover pack and test protocol.</p></div>
<div class="card"><h3>🏠 3 · The smart-home layer</h3><p>Assembled Home Assistant entities —
colour/dimmable <b>lights</b> (RGBW · RGB · CCT + statuses), <b>climate</b> (setpoint · mode ·
valve), covers, sensors — every entity reading <b>real device state</b>, never assuming it.
Anything ambiguous goes to a <code>review</code> list instead of being guessed. Plus a date/time
<code>expose</code> block, Matter lint and a KNX IoT (Turtle/RDF) semantic export. Ships with an
<b>ops-companion skill</b> (<a href="https://github.com/NickoScope/nickol-knx-mcp/tree/main/skills/ha-git-backup">ha-git-backup</a>):
real git history of your deployed <code>/config</code> + encrypted offsite backups.</p></div>
<div class="card"><h3>🧱 4 · Compose a new project</h3><p>Start from a blank slate: pick
<b>parametrised room templates</b> (bedroom · children · living · kitchen · bathroom · corridor),
choose a <b>basic/comfort</b> preset per slot, and <code>compose_rooms</code> builds a <b>new</b>
project → an <b>allocation manifest</b> + ETS-importable <b>GA XML/CSV</b> + a <b>device BOM</b>
proposal. The generated <code>.knxproj</code> is re-read by the standard loader and passes all four
linters (<b>0 errors / 0 warnings</b>). Still <b>read-only where it counts</b> — dry-run by
default, and it only ever writes a <b>new</b> project, never your existing one or the bus.
<span style="color:var(--faint)">R2: docking into an existing project + exact device selection —
planned.</span></p></div>
</div>
<div class="card" style="margin-top:14px"><h3>🧩 The foundation — a growing device library</h3>
<p><code>parse_devices_from_project</code> extracts <b>exact vendor object models</b> (object numbers ·
DPTs · C/R/W/T/U flags · per-channel strides) from the manufacturer application programs inside any
<code>.knxproj</code>/<code>.knxprod</code> — deterministic and PII-safe (vendor catalog data only).
Point <code>NICKOL_KNX_CATALOG</code> at your catalog and <code>decompose_device</code> answers with
the <b>exact model</b> instead of a generic recipe; undeclared DPTs stay honestly
<code>unverified</code>. The design methodology is distilled from the KNX Association standard,
public manufacturer documentation and the study of real professional as-built ETS projects
(anonymised).</p></div>
</div></section>
<!-- DEMO HOUSE -->
<section id="demo"><div class="wrap">
<div class="eyebrow">The demo house</div>
<h2>A whole synthetic home — input to output</h2>
<p class="lead">A complete worked example ships in the repo (<code>examples/demo-home</code>): a synthetic 2-storey
home — living room with fireplace, kitchen, master + 2 kids bedrooms each with ensuite, guest WC, laundry,
2 corridors, staircase. Lighting (switch/dim/CCT/RGBW), underfloor heating + AC, sensors, scenes.</p>
<div class="grid g4" style="margin-top:18px">
<div class="card"><div class="stat green">239</div><div class="stat-l">group addresses</div></div>
<div class="card"><div class="stat blue">47</div><div class="stat-l">ETS Functions</div></div>
<div class="card"><div class="stat amber">13</div><div class="stat-l">zones</div></div>
<div class="card"><div class="stat violet">5</div><div class="stat-l">deliberate mistakes</div></div>
</div>
<div class="card" style="margin-top:16px">
<h3>The 5 planted mistakes — and what the checks caught</h3>
<table>
<tr><th>#</th><th>Planted mistake</th><th>Caught?</th></tr>
<tr><td>1</td><td>Group address with <b>no DPT</b> (Living room CO₂)</td><td class="ok">✅ check_dpt</td></tr>
<tr><td>2</td><td>Two GAs, <b>same name, different DPT</b> (Kitchen temperature)</td><td class="ok">✅ check_dpt</td></tr>
<tr><td>3</td><td>Dimmer with on/off status but <b>no brightness status</b></td><td class="warn">⚠️ not yet — honest limit</td></tr>
<tr><td>4</td><td>Switch with <b>no status</b> (Guest WC)</td><td class="ok">✅ check_missing_status</td></tr>
<tr><td>5</td><td>Group address with an <b>empty name</b></td><td class="ok">✅ check_naming</td></tr>
</table>
<p style="margin-top:10px">The tool surfaced its own limitation (#3): the missing-status check asks “does this control have
<i>a</i> status?”, not “each <i>expected</i> status type”. Tracked for a future release — found by this very demo.</p>
</div>
</div></section>
<!-- DASHBOARD -->
<section id="dashboard"><div class="wrap">
<div class="eyebrow">Live in Home Assistant</div>
<h2>The dashboard</h2>
<p class="lead">The KNX I/O drives a 5-view Home Assistant dashboard. These are <b>real screenshots from a
live Home Assistant</b> running the demo (simulated sensors that drift over time) — now showing the
<b>assembled colour lights</b> (RGBW / RGB / CCT) and <b>six floor-heating climate zones</b> the tool
generates. Below them, an interactive preview lets you click through the views.</p>
<div class="shots">
<a class="hero" href="assets/screenshots/overview.png"><img src="assets/screenshots/overview.png" alt="Overview view: home mode, comfort gauge, scenes, climate snapshot" loading="lazy"><span><b>Overview</b> — at a glance · scenes · climate snapshot incl. floor-heating zones</span></a>
<a href="assets/screenshots/climate.png"><img src="assets/screenshots/climate.png" alt="Climate view" loading="lazy"><span><b>Climate</b> — 6 floor-heating zones (target · mode · valve %) &amp; the computed “brain”</span></a>
<a href="assets/screenshots/lighting.png"><img src="assets/screenshots/lighting.png" alt="Lighting view" loading="lazy"><span><b>Lighting</b> — RGBW / RGB / CCT colour lights · circadian dimmers · blinds</span></a>
<a href="assets/screenshots/energy.png"><img src="assets/screenshots/energy.png" alt="Energy view" loading="lazy"><span><b>Energy &amp; stats</b> — gauges · runtime · air quality</span></a>
<a href="assets/screenshots/presence.png"><img src="assets/screenshots/presence.png" alt="Presence view" loading="lazy"><span><b>Presence</b> — mode machine · occupancy · schedules</span></a>
</div>
<h3 style="margin:34px 0 0;font-size:15px;color:var(--muted)">↓ Or explore an interactive preview (mock data)</h3>
<div class="dash">
<div class="dash-bar">
<div class="dash-title"><span style="width:9px;height:9px;border-radius:50%;background:var(--green);display:inline-block;box-shadow:0 0 8px #7bd88f88"></span> Demo House</div>
<div class="tabs">
<span class="tab active" data-v="ov">Overview</span>
<span class="tab" data-v="cl">Climate</span>
<span class="tab" data-v="li">Lighting</span>
<span class="tab" data-v="en">Energy</span>
<span class="tab" data-v="pr">Presence</span>
</div>
</div>
<div class="dash-body">
<!-- OVERVIEW -->
<div class="view active" data-v="ov">
<div class="badges">
<div class="badge"><span class="bic" style="background:#3949ab1a;color:#9fa8ff">⌂</span>Home</div>
<div class="badge"><span class="bic" style="background:#4fc3f71a;color:#7fd3f5">❄</span>Winter</div>
<div class="badge"><span class="bic" style="background:#ffb74d1a;color:#ffcb7d">☀</span>Morning</div>
<div class="badge"><span class="bic" style="background:#26c6da1a;color:#5fd0de">🌡</span>8.0°C</div>
<div class="badge"><span class="bic" style="background:#ffca281a;color:#ffd968">☺</span>78%</div>
</div>
<div class="dgrid">
<div class="col">
<div class="dh">At a glance</div>
<div class="dc"><div class="mode">Home</div><div class="ts">Morning · Winter · Weekday</div>
<div class="chips"><span>Outside <b>8°C</b></span><span>HVAC <b>Heating</b></span><span>Comfort <b>78%</b></span></div></div>
<div class="dc gz"><svg viewBox="0 0 120 70" width="100%" height="86">
<path d="M10 64 A50 50 0 0 1 110 64" fill="none" stroke="#2a2f37" stroke-width="9" stroke-linecap="round"/>
<path d="M10 64 A50 50 0 0 1 96 30" fill="none" stroke="#7bd88f" stroke-width="9" stroke-linecap="round"/>
<text x="60" y="50" text-anchor="middle" class="gv">78</text><text x="60" y="63" text-anchor="middle" class="gu">Comfort %</text></svg></div>
</div>
<div class="col">
<div class="dh">Scenes</div>
<div class="scenes">
<div class="scene"><div class="si">🎬</div>Movie</div>
<div class="scene"><div class="si">🌙</div>Goodnight</div>
<div class="scene"><div class="si">🌅</div>Morning</div>
<div class="scene"><div class="si">🏃</div>Away</div>
</div>
</div>
<div class="col">
<div class="dh">Climate snapshot</div>
<div class="dc clim"><div class="ctop"><span class="tn">Living room</span><span class="cmode">Heating</span></div>
<div class="ctemp">21.4°<span class="carr">→</span><b>21.5°</b></div><div class="bar"><i style="width:70%"></i></div><div class="cfoot">valve 70% · pre-heat 18 min</div></div>
<div class="dc clim"><div class="ctop"><span class="tn">Master bedroom</span><span class="cmode">Heating</span></div>
<div class="ctemp">19.8°<span class="carr">→</span><b>20.0°</b></div><div class="bar"><i style="width:45%"></i></div><div class="cfoot">night setpoint · occupied</div></div>
</div>
</div>
</div>
<!-- CLIMATE -->
<div class="view" data-v="cl">
<div class="dgrid">
<div class="col">
<div class="dh">Zones</div>
<div class="dc clim"><div class="ctop"><span class="tn">Living room</span><span class="cmode">Heating</span></div>
<div class="ctemp">21.4°<span class="carr">→</span><b>21.5°</b></div><div class="bar"><i style="width:70%"></i></div></div>
<div class="dc clim"><div class="ctop"><span class="tn">Master bedroom</span><span class="cmode">Heating</span></div>
<div class="ctemp">19.8°<span class="carr">→</span><b>20.0°</b></div><div class="bar"><i style="width:45%"></i></div></div>
</div>
<div class="col">
<div class="dh">The brain (computed)</div>
<div class="dc"><div class="ts">Living target</div><div class="ctemp" style="font-size:22px"><b>21.5°C</b></div>
<div class="chips"><span>daypart morning</span><span>mode Home</span><span>winter</span><span>occupied</span><span>window ✓</span></div>
<div class="cfoot" style="margin-top:8px">8-factor setpoint · pre-heat lead 18 min</div></div>
<div class="dc tile"><div class="tic" style="color:#ff8a65">🔥</div><div><div class="tn">Living valve</div><div class="ts on">70%</div></div></div>
</div>
<div class="col">
<div class="dh">Trends · 48h</div>
<div class="dc"><svg class="spark" viewBox="0 0 120 40" preserveAspectRatio="none">
<polyline fill="none" stroke="#ff8a65" stroke-width="1.6" points="0,28 15,26 30,22 45,24 60,18 75,16 90,20 105,15 120,17"/>
<polyline fill="none" stroke="#83b9ff" stroke-width="1.6" points="0,32 15,31 30,30 45,31 60,28 75,29 90,27 105,28 120,26"/>
<polyline fill="none" stroke="#5fd0de" stroke-width="1.4" points="0,36 15,35 30,34 45,36 60,33 75,34 90,35 105,33 120,34"/></svg>
<div class="cfoot">Living · Bedroom · Outdoor</div></div>
<div class="dc tile"><div class="tic" style="color:#ef5350">📈</div><div><div class="tn">Degree-hours today</div><div class="ts">142 °C·h</div></div></div>
</div>
</div>
</div>
<!-- LIGHTING -->
<div class="view" data-v="li">
<div class="dgrid">
<div class="col">
<div class="dh">Adaptive (circadian)</div>
<div class="dc gz"><svg viewBox="0 0 120 70" width="100%" height="86">
<path d="M10 64 A50 50 0 0 1 110 64" fill="none" stroke="#2a2f37" stroke-width="9" stroke-linecap="round"/>
<path d="M10 64 A50 50 0 0 1 78 22" fill="none" stroke="#ffcb7d" stroke-width="9" stroke-linecap="round"/>
<text x="60" y="50" text-anchor="middle" class="gv">62</text><text x="60" y="63" text-anchor="middle" class="gu">Brightness %</text></svg></div>
<div class="dc tile"><div class="tic" style="color:#ffcb7d">🌡</div><div><div class="tn">Colour temp</div><div class="ts on">3400 K</div></div></div>
</div>
<div class="col">
<div class="dh">Dimmable &amp; colour</div>
<div class="dc tile"><div class="tic" style="color:#ffcb7d">💡</div><div style="flex:1"><div class="tn">Living cove</div><div class="bar" style="margin-top:5px"><i style="width:62%"></i></div></div></div>
<div class="dc tile"><div class="tic" style="color:#c4a7f0">🟪</div><div style="flex:1"><div class="tn">RGBW accent</div><div class="bar b" style="margin-top:5px"><i style="width:40%"></i></div></div></div>
<div class="dc tile"><div class="tic" style="color:#ffcb7d">🔆</div><div style="flex:1"><div class="tn">Kitchen worktop CCT</div><div class="bar" style="margin-top:5px"><i style="width:80%"></i></div></div></div>
</div>
<div class="col">
<div class="dh">Switched &amp; blinds</div>
<div class="row2">
<div class="dc tile" style="padding:9px"><div class="tic" style="width:28px;height:28px;font-size:14px">💡</div><div class="tn" style="font-size:12px">Living spots</div></div>
<div class="dc tile" style="padding:9px"><div class="tic" style="width:28px;height:28px;font-size:14px">💡</div><div class="tn" style="font-size:12px">Kitchen</div></div>
</div>
<div class="dc tile"><div class="tic" style="color:#83b9ff">🪟</div><div style="flex:1"><div class="tn">Living blind</div><div class="bar b" style="margin-top:5px"><i style="width:35%"></i></div><div class="cfoot" style="margin-top:3px">position 35% · slat tilt</div></div></div>
</div>
</div>
</div>
<!-- ENERGY -->
<div class="view" data-v="en">
<div class="dgrid">
<div class="col">
<div class="dh">Energy today</div>
<div class="dc gz"><svg viewBox="0 0 120 70" width="100%" height="86">
<path d="M10 64 A50 50 0 0 1 110 64" fill="none" stroke="#2a2f37" stroke-width="9" stroke-linecap="round"/>
<path d="M10 64 A50 50 0 0 1 88 26" fill="none" stroke="#ff8a65" stroke-width="9" stroke-linecap="round"/>
<text x="60" y="50" text-anchor="middle" class="gv">14</text><text x="60" y="63" text-anchor="middle" class="gu">HVAC kWh</text></svg></div>
<div class="dc tile"><div class="tic" style="color:#7bd88f">⚡</div><div><div class="tn">kWh / occupied-h</div><div class="ts on">0.92</div></div></div>
</div>
<div class="col">
<div class="dh">Runtime today</div>
<div class="row2">
<div class="dc"><div class="tn">Living lights</div><div class="ts">3.4 h</div></div>
<div class="dc"><div class="tn">Living heating</div><div class="ts">6.1 h</div></div>
<div class="dc"><div class="tn">Occupied</div><div class="ts">11.2 h</div></div>
<div class="dc"><div class="tn">Bath uses</div><div class="ts">7</div></div>
</div>
</div>
<div class="col">
<div class="dh">Air &amp; comfort</div>
<div class="dc tile"><div class="tic" style="color:#7bd88f">🫧</div><div><div class="tn">Bedroom CO₂</div><div class="ts on">680 ppm</div></div></div>
<div class="dc"><svg class="spark" viewBox="0 0 120 40" preserveAspectRatio="none">
<polyline fill="none" stroke="#7bd88f" stroke-width="1.6" points="0,30 15,28 30,24 45,20 60,26 75,30 90,22 105,18 120,24"/></svg>
<div class="cfoot">CO₂ · 24h</div></div>
</div>
</div>
</div>
<!-- PRESENCE -->
<div class="view" data-v="pr">
<div class="dgrid">
<div class="col">
<div class="dh">Mode machine</div>
<div class="dc tile"><div class="tic" style="color:#9fa8ff">⌂</div><div><div class="tn">Home mode</div><div class="ts on">Home ▾</div></div></div>
<div class="dc tile"><div class="tic" style="color:#7bd88f">👣</div><div><div class="tn">Anyone present</div><div class="ts on">Detected</div></div></div>
</div>
<div class="col">
<div class="dh">Occupancy (computed)</div>
<div class="row2">
<div class="dc"><div class="tn">Living</div><div class="ts on">occupied</div></div>
<div class="dc"><div class="tn">Bedroom</div><div class="ts">clear</div></div>
<div class="dc"><div class="tn">Master bath</div><div class="ts">clear</div></div>
<div class="dc"><div class="tn">Corridors</div><div class="ts on">active</div></div>
</div>
</div>
<div class="col">
<div class="dh">Schedules</div>
<div class="dc tile"><div class="tic" style="color:#83b9ff">🗓</div><div><div class="tn">Work week</div><div class="ts on">07:00 – 22:30</div></div></div>
<div class="dc tile"><div class="tic" style="color:#9fa8ff">🌙</div><div><div class="tn">Quiet hours</div><div class="ts">23:00 – 06:30</div></div></div>
</div>
</div>
</div>
</div>
</div>
<p class="note">↑ Interactive preview with mock data · the screenshots above are the real thing, live in Home Assistant.</p>
</div></section>
<!-- BRAIN -->
<section id="brain"><div class="wrap">
<div class="eyebrow">The brain</div>
<h2>Logic that depends on <i>everything</i></h2>
<p class="lead">On top of the KNX I/O sits a 5-layer Home Assistant control system — built on native helpers
(utility_meter, history_stats, statistics, derivative, trend, threshold, schedule), 22 automations and 3 scripts.</p>
<div class="stack" style="margin-top:18px">
<div class="layer"><div class="ll">L4 Statistics</div><div class="ld">energy meters &amp; tariffs · runtime counters · heating degree-hours · KPIs (kWh/occupied-h, comfort)</div></div>
<div class="layer"><div class="ll">L3 Lighting</div><div class="ld">circadian brightness &amp; colour temp · motion/presence (only when dark) · scenes · vacation simulation</div></div>
<div class="layer"><div class="ll">L2 Climate</div><div class="ld"><b>8-factor target setpoint</b> · heat/cool changeover · adaptive pre-heat · window/CO₂ interlocks</div></div>
<div class="layer"><div class="ll">L1 Home mode</div><div class="ld">Home / Away / Night / Eco / Guest / Vacation state machine</div></div>
<div class="layer"><div class="ll">L0 Senses</div><div class="ld">season · daypart (sun) · weekday/weekend · per-zone occupancy · comfort index</div></div>
</div>
<div class="card" style="margin-top:16px">
<h3>⭐ The 8-factor setpoint</h3>
<p>A single template folds everything into one number:
<code>daypart profile → weekend hold → occupancy setback → home-mode override (Away −3°, Vacation→frost 12°)
→ seasonal trim / cooling target → window interlock → clamp 12–28 °C</code>.</p>
</div>
</div></section>
<!-- CTA / testers -->
<section id="try"><div class="wrap">
<div class="eyebrow">It's a beta — testers welcome</div>
<h2>Try it on your own <code>.knxproj</code></h2>
<p class="lead">It's read-only and never touches a bus, so testing is safe. Run it from Claude, point it at your
project, and tell us what the report got right — and wrong.</p>
<div class="cta">
<a class="btn primary" href="https://github.com/NickoScope/nickol-knx-mcp">Get it on GitHub</a>
<a class="btn" href="https://github.com/NickoScope/nickol-knx-mcp/tree/main/examples/demo-home">Browse the demo house</a>
<a class="btn" href="https://github.com/NickoScope/nickol-knx-mcp/blob/main/CONTRIBUTING.md">How to test</a>
<a class="btn" href="https://github.com/NickoScope/nickol-knx-mcp/discussions/1">💬 Join the discussion</a>
</div>
</div></section>
<footer><div class="wrap">
<p><b>nickol-knx-mcp</b> · <a href="https://github.com/NickoScope/nickol-knx-mcp">github.com/NickoScope/nickol-knx-mcp</a> · MIT © 2026 Nikolay Miroshnichenko</p>
<p style="color:var(--faint)">Not affiliated with or endorsed by the KNX Association. “KNX” and “ETS” are trademarks of the KNX Association cc. An independent community tool.</p>
</div></footer>
<script>
document.querySelectorAll('.tab').forEach(function(t){
t.addEventListener('click',function(){
var v=t.getAttribute('data-v');
document.querySelectorAll('.tab').forEach(function(x){x.classList.toggle('active',x.getAttribute('data-v')===v)});
document.querySelectorAll('.view').forEach(function(x){x.classList.toggle('active',x.getAttribute('data-v')===v)});
});
});
</script>
</body>
</html>