26 Commits

Author SHA1 Message Date
f19754aa5b docs: Set-up-Grafana-dashboard how-to
Five-minute walkthrough for the new grafana/ bundle shipped in
the integration repo. Covers booting InfluxDB+Grafana via docker
compose, wiring HA's influxdb integration via ha-snippet.yaml,
opening the dashboard, the four-row layout, and the iteration
loop (UI edits -> JSON Model export -> commit).

Sidebar.order: 6 puts it after the other panel-related how-tos.
2026-05-17 23:43:11 -06:00
de02f10be7 docs: Edit-programs-in-HA how-to + side-panel reference + MDX fixes
New how-to walks through the Omni Programs side panel end-to-end:
list view, filters, the detail panel, compact-form editing, the
clausal chain editor, structured-AND with Arg2-as-object, known
limits, and the websocket commands the panel speaks. Five
embedded screenshots from a real .pca fixture.

reference/ha-entities.md picks up a short callout near the top so
readers browsing entity docs don't miss the side panel.

program-format.mdx had three pre-existing unescaped '<' characters
('firmware <3.0' and '(family << 8)' inside a table) that were
breaking the MDX build. Replaced with 'before 3.0' and escaped
operators.
2026-05-17 13:06:30 -06:00
7bbe1b4372 program-format: structured-OP AND record fully RE'd
Final RE pass on the multi-record AND extension. Authored "AND IF
DATE IS EQUAL TO 12/31" and resolved the structured-OP case via the
captured bytes 08 07 01 00 00 01 00 1f 0c 00 ....

The structured-AND section now has:
- A clear distinction between Traditional (OP=0) and Structured (OP>0)
  modes, with both using the same disk byte slots but different
  semantics.
- A worked byte-by-byte example for "AND IF DATE IS EQUAL TO 12/31"
  showing how Read's LE-to-BE swap places each field into Data[].
- The clsConditionLine.Cond bridge documented inline, explaining why
  the Traditional case packs (family, instance) into the same byte
  positions that structured records use for (OP, Arg1_ArgType, Arg1_IX).
- UI labels for each enuCondOP value (IS EQUAL TO, IS GREATER THAN,
  etc.) so readers can map the enum to what they'd see in PC Access.

Python usage section updated to show the structured-AND properties:
  and_op, and_arg1_argtype, and_arg1_ix, and_arg1_field,
  and_arg2_argtype, and_arg2_ix, and_arg2_field, and_compconst.
2026-05-12 15:37:18 -06:00
8ece4fa9ed program-format: per-record byte tables for all multi-record types
All four originally-open RE questions are now resolved (findings #15-18
in pca-re/clausal-re/FINDINGS.md). Update the docs to reflect:

- Each of the 6 multi-record ProgTypes (WHEN/AT/EVERY/AND/OR/THEN)
  now has its own per-byte table in a dedicated subsection.
- Removed the old stale "AND-record layout" section that conflated the
  Traditional case with structured-OP layouts.
- Moved the structured-OP enum tables (enuCondOP, enuCondArgType) into
  a "Structured-OP AND records (future RE)" section as reference for
  the next pass.
- Added a Python "Decoding multi-record programs" subsection showing
  the new typed accessor properties on the Program class:
    p.is_multi_record(), p.event_id, p.every_interval,
    p.and_family, p.and_instance
- Summary table at the top of "Multi-record form" now states the
  actual layouts instead of "layout TBD".

Removed:
- "Open: byte 1's semantic role" caution (resolved by Unit 1 ON capture).
- Now-redundant AND-record table that didn't match empirical findings.

Kept:
- "Byte order: BE for AND/EVERY records" note (still useful context).
- The 5-record example block (good illustration).
- enuCondOP / enuCondArgType enum tables (still useful reference for
  the structured-OP case).
2026-05-12 05:17:11 -06:00
7d53992841 program-format: AND-record u16s are BE on disk (verified empirically)
Authored AND IF ZONE 5 SECURE in PC Access and diffed against AND IF
NEVER. The zone number 5 lands at byte 4 (high-offset of the u16),
confirming that Arg1_IX / Arg2_IX / CompConst in AND records use
big-endian byte order on disk. This is the opposite of compact-form
cond / cond2 / pr2 which are LE. Different record families use
different byte orders.

Convert the earlier "open question" caution to a confirmation note,
and add a separate caution for the remaining open question about
byte 1's semantic role (OP vs family code).
2026-05-12 03:48:03 -06:00
7a766d69ba program-format: document AND-record field layout (firmware >=3.0)
Add a per-byte breakdown of the multi-record AND record format from
clsProgram.cs:326-436, with the enuCondOP and enuCondArgType companion
enum tables. Note the Arg1_Traditional special case where the Cond
u16 carries the condition instead of the structured Arg1_* fields,
and flag the open question about disk byte order for Arg1_IX /
Arg2_IX / CompConst (Read says LE, accessors say BE; needs a
non-symmetric controlled capture to disambiguate).
2026-05-12 03:19:56 -06:00
2300be0f6c program-format: correct byte order (LE, not BE) + document multi-record form
The cond / cond2 / pr2 fields are little-endian u16s, not big-endian
as the original page documented. Caught by authoring controlled
programs in PC Access (running in a Win XP VM) and byte-diffing the
resulting .pca. The worked example's expected values are corrected:

- pr2 bytes [01, 00] read as 1, not 256
- cond bytes [8d, 09] read as 0x098d (Unit 397 OFF), not 0x8d09
- cond2 bytes [9b, 09] read as 0x099b (Unit 411 OFF), not 0x9b09

Also promote the multi-record ProgType values (WHEN/AT/EVERY/AND/OR/THEN)
from "RE-pending" to a documented "Compact vs multi-record form" section.
Multi-record form requires firmware >=3.0.0 per clsCapOMNI_PRO_II.cs:290
(Features.Add(MultiLinePrograms, 196608u)); the user's 2.16A panel
cannot produce these records, so all entries in its .pca are compact form.

A 5-record example block (WHEN + 3 ANDs + THEN), captured by using PC
Access's Account Info -> Version Override to spoof firmware 3.0, shows
the per-record layout at the discriminator level. The AND record's
structured-condition fields (OP / Arg1_* / Arg2_* / CompConst) remain
on the RE backlog.
2026-05-12 03:02:09 -06:00
db3832c68c docs/program-format: document cond/cond2 bit split per family
New section breaking down the 5 condition families (OTHER / ZONE /
CTRL / TIME / SEC) with their bit layouts, worked examples, and a
Python usage block showing Condition.decode and Program.condition().
Removes the corresponding entry from the "what we don't yet know"
list.

Also rewords the ProgramCond table to point at the new section
instead of saying the bit split is unknown.
2026-05-11 22:35:05 -06:00
7057fa4410 docs/program-format: document TIMED sunrise/sunset offset encoding
New section explaining the hour-byte overload (25 = sunrise-relative,
26 = sunset-relative, with the minute byte read as sbyte for the
offset). Python example showing TimeKind classification +
format_time() output. Removes the corresponding line from the
"what we don't yet know" list.
2026-05-11 21:38:37 -06:00
e4dbcf0429 docs/program-format: document Remarks-table layout + resolution path
The previous "What we don't yet know" entry for RemarkID -> RemarkText
is now closed. Replaced the placeholder note with the actual format
(clsPrograms.ReadRemarks layout) and a Python usage example showing
how to resolve a Program's remark_id against PcaAccount.remarks.

Removed the "RemarkID -> RemarkText lookup" line from the
"what we don't yet know" list.
2026-05-11 21:34:02 -06:00
f45e9fd014 docs: reference/program-format — wire layout + Mon/Day quirk + worked example
New reference page documenting the Omni Pro II's 14-byte program record
format. Mirrors file-format.mdx's structure: byte-offset table, enum
tables (ProgramType, ProgramCond, Days), worked example decoding a
real slot from the live fixture, callouts for the EVENT-only Mon/Day
swap on disk, and an explicit "what we don't yet know" section that
lists the gaps a future editor pass would have to close (cond bit
split, multi-record clausal encoding, RemarkID -> RemarkText lookup,
DPC capability flag, sunrise/sunset offset flag).

Sidebar adds the entry between File format and Hardware specs.
2026-05-11 19:48:07 -06:00
814d945f6d docs: refresh HA screenshots in dark mode (2026-05-11)
Re-captured against the current dev stack via dev/screenshot.py which
now injects selectedTheme={dark:true} into localStorage before HA boots.
Six shots, all matching the docs site's dark theme:

  01-overview.png            lovelace dashboard
  02-integrations-list.png   integrations dashboard (shows BOTH panels
                             now -- mock @ host.docker.internal + real
                             @ 192.168.1.9 -- as "2 devices")
  03-omni-pca-config.png     omni_pca integration detail
  04-panel-device.png        device deep-link
  05-entities-omni.png       entities filtered by config entry
  06-developer-states.png    developer-tools/state
2026-05-11 13:35:02 -06:00
45003e9049 docs: drop external attribution link from hardware-specs 2026-05-11 13:29:50 -06:00
9ea05c3b94 docs: add Hardware specs reference + Zone & unit numbering explanation
Two new pages distilled from HAI's official OmniPro II Installation
Manual 3-2 + Product Specifications datasheet (Quadomated mirror).

reference/hardware-specs.md
  Capacity ceilings (176 zones, 511 units, 8 areas, 64 thermostats,
  128 buttons, 1500 programs, 99 codes, 128 messages, etc.), digital
  communicator + network features, electrical specs with the per-output
  current caps and the 24-hour battery-standby derating numbers,
  physical dimensions, environmental ranges, listings, languages, and
  part-number lookup. The numbers here are the source of every cap in
  clsCapOMNI_PRO_II.cs and the upper bounds for the protocol's range
  fields, so anyone debugging "why does my panel NAK at index N" should
  start here.

explanation/zone-unit-numbering.md
  Appendix C of the install manual, transcribed and explained: how the
  panel maps physical hardware to its 1-176 zone and 1-511 unit
  address spaces. Documents the four overlapping families that share
  the unit number line -- X-10 (1-256, by house code), ALC bus
  (parallel address space within those slots), physical outputs
  (257-392), and panel flags (393-511) -- which is why the working
  panel reports units at index 257+ (sprinkler outputs on the first
  17A00 expansion enclosure) and 393+ (named flags) even though only
  a dozen lights are wired up.

  Closes a real debugging mystery from Phase 2/3 of the v1+UDP work:
  OmniClientV1's long-form RequestUnitStatus path (BE u16 start/end)
  exists specifically to address units > 255, which only happens
  because of this firmware-fixed address layout.

astro.config.mjs
  Slot both new pages into the existing Reference and Explanation
  sidebar groups.
2026-05-11 13:23:58 -06:00
f7be0f7b18 Homepage redesign: lead with the HA integration, then the library
The previous homepage opened with prose ('What it is') and listed three
generic 'where to start' cards. New version is more concrete:

- Hero block with three call-to-action buttons (Add to HA, Decode .pca,
  Source on Gitea) using Starlight's hero frontmatter
- '## In Home Assistant' section opens with what users actually get,
  followed by a 2x2 grid of clickable HA screenshots (integrations
  list, integration page, device controls, dev-tools states) — each
  thumbnail links to the relevant detail page with a short caption
- '## In Python' section: 15-line OmniClient + events() example so
  the library shape is immediately legible, plus four bullets
  highlighting opcode coverage / typed dataclasses / mock controller
  / async-first
- '## Two non-public protocol quirks' kept verbatim — still the
  headline RE finding
- '## Read the in-depth content' replaces the three generic cards
  with six specific ones routing to install how-to, dev-stack
  tutorial, protocol reference, quirks explainer, file format,
  and journey

Build clean, 23 pages. Local container rebuilt + recreated. The
new sections render: In Home Assistant, In Python, Two non-public
protocol quirks, Read the in-depth content.
2026-05-10 18:18:44 -06:00
17cd57f1bf Compose convention: dev/prod target switch, HMR-friendly Caddy labels
Aligns with the warehack-ing host convention (matches birdcage-docs):

Dockerfile — multi-stage with named targets:
  base   shared npm ci + COPY
  dev    npx astro dev --host 0.0.0.0 --port 4321  (HMR enabled)
  build  npx astro build (produces /app/dist)
  prod   caddy:2-alpine serves /srv with /health probe + HEALTHCHECK

docker-compose.yml — picks the target via APP_ENV in .env:
  build.target = ${APP_ENV:-dev}
  caddy.reverse_proxy upstream port = ${APP_PORT:-4321}
  Adds the streaming/HMR caddy labels CLAUDE.md requires for Vite-over-
  Caddy: flush_interval=-1, transport.read/write_timeout=0, keepalive,
  stream_timeout=24h, stream_close_delay=5s
  Volume mounts ./src, ./public, astro.config.mjs (live in dev, harmless
  in prod since the prod stage doesn't reference /app)

astro.config.mjs — vite.server.hmr block now picks up VITE_HMR_HOST
env var and configures host/protocol/clientPort for wss-on-443 HMR
through Caddy. Falls back to undefined when unset so plain
'npm run dev' still works.

Caddyfile — adds /health endpoint for the Dockerfile HEALTHCHECK,
SPA-style try_files fallback chain (path -> path/index.html -> /index.html).

Makefile — adds 'make dev' / 'make prod' that rewrite APP_ENV+APP_PORT
in .env then rebuild. Tail-logs after up/restart so you see startup
diagnostics without a separate 'make logs'.

.env.example — five vars (COMPOSE_PROJECT_NAME, APP_ENV, APP_PORT,
PUBLIC_DOMAIN, VITE_HMR_HOST), defaulting to prod mode against the
public hai-omni-pro-ii.warehack.ing domain. .env stays gitignored so
each environment (local, remote) can override.

Local container rebuilt + recreated, healthy, /health returns 200,
PUBLIC_DOMAIN locally still set to .l.warehack.ing in the host .env.
2026-05-10 18:05:54 -06:00
0525895dbe Install pages: Gitea-release URLs while PyPI publish is pending
quickstart:
  - 'pip install omni-pca' -> pip install from git+https at the v2026.5.10 tag
  - uvx --from form documented for no-install runs
  - note callout that PyPI publish is pending and what to switch to once it lands

install-in-home-assistant:
  - Common errors table: replace generic 'check internet' fix with a
    concrete pre-install command using the release wheel URL
  - Known gaps: same wheel install command shown inline with HA-OS hint

Build clean, 23 pages.
2026-05-10 17:55:00 -06:00
9855211cd0 URLs: github -> git.supported.systems/warehack.ing/{omni-pca,omni-pca-docs}
astro.config.mjs                                editLink baseUrl + GitHub social link
  README.md                                       all references
  src/content/docs/changelog.md                   release tag URL
  src/content/docs/reference/library-api.md      'see github' note
  src/content/docs/reference/protocol.mdx         opcodes.py source link
  src/content/docs/tutorials/dev-stack.md         git clone command
  src/content/docs/how-to/install-in-home-assistant.md  HACS custom-repo URL + manual git clone

Build: 23 pages clean.
2026-05-10 17:47:04 -06:00
0c245b0af5 Per-packet diagrams: every PacketType now has a wire-format visual
src/assets/diagrams/packet-control-empty.svg
  Covers the five empty-payload control types: NoMessage (0x00),
  ClientRequestNewSession (0x01), ClientSessionTerminated (0x05),
  ControllerSessionTerminated (0x06), ControllerCannotStartNewSession
  (0x07). Single 4-byte header diagram with the type byte highlighted,
  '(no payload)' shown as a dashed empty box, and all five type values
  enumerated below.

src/assets/diagrams/packet-controller-ack-new-session.svg
  Type 0x02. 11 bytes total — 4-byte header + 2-byte protocol version
  (0x00 0x01) + 5-byte SessionID nonce. SessionID cells in accent
  colour because they're what feed quirk #1's session key XOR mix.
  Bottom annotation explains the proto-version is hard-coded.

src/assets/diagrams/packet-secure-session.svg
  Types 0x03 and 0x04 share the same shape both directions. Two-row
  layout: top shows the plaintext (5-byte SessionID echo + 11 zero-pad
  cells with 'zero pad to 16 bytes'); bottom shows the wire form
  (header + 16-byte AES ciphertext block). Highlights the symmetric
  client-up/controller-down design.

src/assets/diagrams/packet-omnilink-message.svg
  Covers all four conversation packet types in one diagram: 0x10
  (v1 encrypted), 0x11 (v1 plaintext), 0x20 (v2 encrypted), 0x21 (v2
  plaintext). Top row shows the encrypted variant with N x 16-byte
  ciphertext blocks; brace down to bottom row showing the inner
  Message format (start byte + length + opcode + data + CRC u16 LE).
  Same diagram serves all 4 since the layout is identical except
  for the type byte and whether AES is applied.

src/content/docs/reference/protocol.mdx
  Added five new sections so every PacketType is now documented:
  - NoMessage (0x00) folded into the ClientRequestNewSession heading
    (same empty-payload layout)
  - ClientSessionTerminated (0x05) / ControllerSessionTerminated (0x06)
    / ControllerCannotStartNewSession (0x07) get a combined section
    with a one-paragraph explanation of when each fires
  - OmniLinkMessage (0x10), OmniLinkUnencryptedMessage (0x11),
    OmniLink2Message (0x20), OmniLink2UnencryptedMessage (0x21)
    consolidated into one section with the diagram + a note about
    which TCP/PC-Access actually uses

  ClientRequestSecureSession + ControllerAckSecureSession merged into
  one section since they share the diagram; original prose preserved
  as #### subsections beneath.

Build: 23 pages clean. Protocol page now 118 KB (was 92 KB) — six
inline SVG titles confirmed via grep on the rendered HTML. Every
packet type defined in omni_pca.opcodes.PacketType (12 values) is
now visualized in the docs.
2026-05-10 17:44:27 -06:00
d5d2ea3d32 Diagrams: five hand-crafted SVGs explaining the protocol + architecture
The auto-extracted manual SVGs were unusable PDF text-glyph soup. These
are fresh, theme-aware (currentColor everywhere, accent via the
--sl-color-accent CSS var), and built to teach.

src/assets/diagrams/handshake-sequence.svg
  Sequence diagram with CLIENT and CONTROLLER swim lanes, five steps:
  ClientRequestNewSession -> ControllerAckNewSession (carries SessionID)
  -> derive SessionKey (inline note) -> ClientRequestSecureSession
  (encrypted, accent-coloured) -> ControllerAckSecureSession (encrypted)
  -> first OmniLink2Message. Plaintext arrows in currentColor, encrypted
  arrows in accent.

src/assets/diagrams/packet-structure.svg
  Bytes-on-the-wire box diagram: outer Packet header (seq u16 + type +
  reserved + encrypted payload) decomposed below into the inner Message
  (start byte 0x21, length, opcode, data, CRC u16 LE). Plain vs encrypted
  fields colour-coded with a legend.

src/assets/diagrams/session-key-derivation.svg
  Quirk #1 visual. Three rows of byte cells: ControllerKey (16 bytes,
  with bytes 0..10 in plain colour and 11..15 highlighted), SessionID
  (5 bytes), and the resulting SessionKey with the XOR boundary
  visible. XOR operator in the accent colour to draw the eye.

src/assets/diagrams/per-block-whitening.svg
  Quirk #2 visual. seq pill at the top, three blocks below (block 1,
  block 2, block N) each showing 16 byte cells with the first two
  highlighted in accent and labelled with the seq XOR mask. Drives home
  that it's the SAME mask on EVERY block.

src/assets/diagrams/architecture.svg
  Three groups (LIBRARY, HA INTEGRATION, TEST SURFACE) with boxes
  inside. Library shows the four protocol-layer modules + connection +
  client + models + events. HA shows coordinator + 8 platforms. Test
  surface shows MockPanel (accent-coloured), HA test harness, e2e tests,
  unit tests. One accent-coloured arrow runs from OmniConnection across
  to MockPanel labelled 'TCP/4369 (encrypted)'.

src/assets/diagrams/pca-file-format.svg
  Key chain: hardcoded keyPC01 -> decrypts PCA01.CFG (boxes for the
  CFG fields including the highlighted pca_key) -> arrow showing the
  extracted pca_key -> decrypts the .pca file (boxes for PCA03 magic,
  account info, model byte, body, and the highlighted ControllerKey)
  -> caption 'feeds session-key derivation (quirk #1)'.

Wired in via inline-SVG-via-?raw-import + set:html (so currentColor
adapts to the theme). Required converting four pages to .mdx:
  reference/protocol.mdx        + handshake + packet diagrams
  reference/file-format.mdx     + pca-file-format diagram
  explanation/quirks.mdx        + session-key + whitening diagrams
  explanation/architecture.mdx  + architecture diagram

Two MDX paper cuts during conversion: bare '<100ms' and '<50ms' in
architecture.mdx confused the JSX parser; backticked them as .

Build: 23 pages clean. Verified inline SVG ships in the rendered HTML
(grep for SVG title IDs returns 2/2 hits per relevant page). Container
rebuilt + redeployed. Protocol page is now 92750 bytes (was ~63000),
quirks page 84156 (was ~63000).
2026-05-10 17:32:49 -06:00
d7ee0a3e98 How-to: install-in-home-assistant — full setup + reauth + troubleshooting
src/content/docs/how-to/install-in-home-assistant.md (~900 w)
  Sidebar order 0 so it appears first in the How-to section. Covers:
  - Prereqs: HA 2026.1+, panel reachable, ControllerKey
  - Backup-first nudge before adding any custom component
  - HACS install path (custom repo until upstream publish)
  - Manual git+cp path that works on every HA install type
  - The 'untested custom integration' WARNING is expected
  - Add-via-UI walkthrough with screenshots of integration page +
    device page
  - Customise: entity rename, areas, dashboards, logbook
  - Common errors table (4 toast messages and their fixes)
  - 'Loads but no entities' deep-dive (3 ordered causes)
  - Reauth flow when ControllerKey rotates
  - Cross-links to the other how-tos and entity/service references
  - Known gaps: HACS not published yet, omni-pca not on PyPI yet,
    live panel validation pending

23 pages now (was 22). Sidebar autogenerates so it appears at the top
of How-to without further config.
2026-05-10 17:25:46 -06:00
4812b56622 Tutorials + how-tos: nine new pages populating the empty Diataxis lanes
src/content/docs/tutorials/  (3 pages, learning-oriented)
  decrypt-your-pca.md (~600 w)
    Walks the user through installing the CLI and running decode-pca
    against a real .pca file, ending with --include-pii to confirm the
    decryption landed on real plaintext (their own customer name).
    Cites the file format reference for what's actually happening.

  dev-stack.md (~700 w)
    Boots the docker dev stack, onboards HA in 60 seconds, adds the
    integration with the documented host/port/key, then five concrete
    things to try (toggle a light, arm an area with right and wrong
    code, trigger a button, watch developer states). Includes the
    panel-device screenshot.

  first-script.md (~750 w)
    Twenty-line Python script: connect, get system info, walk zones,
    then evolves through three more steps to add an event-stream
    consumer and a command dispatch. Shows ASCII output so the user
    knows what to expect on success. Cross-links the two protocol
    quirks pages.

src/content/docs/how-to/  (6 pages, task-oriented recipes)
  find-controller-key.md (~400 w)
    Four ways: from .pca file, PC Access UI, panel keypad, generate
    a new one. Plus a smoke-test command to verify the key works.

  automate-on-alarm.md (~600 w)
    HA event automation pattern keyed off the omni_pca event entity's
    event_type / event_data attributes. Includes an alarm-type-specific
    filter table and a fire-alarm worked example.

  bypass-zone.md (~400 w)
    Three flavours: HA service call, HA per-zone switch entity, raw
    Python. Includes verification snippets and caveats around
    installer-disabled bypass and code requirements.

  send-panel-message.md (~350 w)
    show_message / clear_message services with a 'laundry done'
    automation example. Notes that Omni messages are pre-programmed in
    PC Access, not free-form.

  decode-a-packet.md (~750 w)
    Step-by-step: take a hex dump, decode the outer Packet, derive the
    session key from the ack, decrypt with per-block whitening,
    decode the inner Message, dispatch on opcode. Includes tcpdump
    capture commands at the end.

  migrate-from-jomnilinkii.md (~700 w)
    What changes when swapping from jomnilinkII / pyomnilink to
    omni-pca. Method-name translation table, async-vs-sync surface,
    pattern-matching event handler example, what's gained (quirks,
    types, mock) and what's lost (years of production hardening).

Build: 22 pages clean (was 13), sitemap regenerated, Pagefind index
covers everything. Container rebuilt + recreated; verified
/how-to/automate-on-alarm/ returns HTTP 200 with the right title.
Sidebar autogenerates from the directories so all nine pages appear
without further config.
2026-05-10 17:23:02 -06:00
4ec43b269f Images: wordmark hero + four HA screenshots scattered through pages
src/assets/manual/omnipro-ii-wordmark.png
  Lifted from the Owner's Manual cover (Owner_s_Manual_page_1_img_1).
  1280x592, 13KB, 8-bit grayscale. Used as the eager-loaded hero on
  index.mdx via Astro's Image component, with CSS filter:invert(1) so
  the black-on-white wordmark renders crisp on the dark theme.

src/assets/screenshots/  (six PNGs, copied from omni-pca/dev/artifacts):
  01-overview.png            HA Lovelace
  02-integrations-list.png   HAI/Leviton tile in the integrations list
  03-omni-pca-config.png     '1 device, 38 entities' integration page
  04-panel-device.png        Omni Pro II device page with all controls
  05-entities-omni.png       config-entry filtered entity table
  06-developer-states.png    alarm_control_panel.omni_pro_ii_main raw
                             attributes from Developer Tools

Wired into pages:

  index.mdx                       wordmark hero (eager load)
  start/quickstart.md             04-panel-device.png at the bottom of
                                  step 3 so the reader sees the payoff
  reference/ha-entities.md        new 'What it looks like in HA' section
                                  with four screenshots (integrations
                                  list, integration detail, device page,
                                  developer states)

Astro Image processed all screenshots into webp at request size:
  06-developer-states 188 KB -> 91 KB after VP8 encoding.

Build: 13 pages clean in 1.66s, sitemap and Pagefind index regenerated.
Container rebuilt + recreated; verified HTTP 200 with 33315-byte index
page and the /reference/ha-entities/ page references four /_astro/*.webp
URLs that all return 200 from the running container.
2026-05-10 17:15:07 -06:00
0e6f75d2f7 Domain: hai-omni-pro-ii.l.warehack.ing (local subdomain pattern)
The .l.warehack.ing subdomain is the convention for the user's local
services. DNS for hai-omni-pro-ii.l.warehack.ing already resolves to
the host; caddy-docker-proxy picks up the new caddy label and routes
appropriately.

Patched .env, .env.example, docker-compose.yml, README.md,
astro.config.mjs.

Container rebuilt and restarted; verified caddy-docker-proxy is
serving 200 OK with 33051 bytes of rendered HTML on the internal
network, and the host-level Caddy issues a 308 redirect to https://
on the new hostname.
2026-05-10 17:06:35 -06:00
9dbe563aed Content + docker pin: 13-page Starlight site live behind Caddy
src/content/docs/ — twelve pages totalling ~18,800 words, ported from
the omni-pca repo's docs and reference material:

  index.mdx (377 w)               landing page with three CardGrid links
  start/quickstart.md (572 w)     three flows: decode .pca / talk to
                                  panel / install in HA

  reference/protocol.md (2525 w)  byte-level Omni-Link II spec, full
                                  packet+message layouts, the two
                                  non-public quirks, opcode tables
  reference/file-format.md (1593 w)  XOR-LCG cipher, key derivation,
                                  PCA01.CFG schema, .pca PCA03 header
  reference/library-api.md (1170 w)  module-by-module Python API summary
  reference/ha-entities.md (1070 w)  per-platform entity catalogue
  reference/ha-services.md (567 w)   seven services + automation YAML

  explanation/quirks.md (1448 w)  the headline RE essay — session-key
                                  XOR mix + per-block whitening, why
                                  no public client documents them
  explanation/architecture.md (1123 w)  library + HA + mock + tests
  explanation/pc-access-bug.md (1131 w)  LargeVocabulary off-by-N

  journey.md (6194 w)             chronological retrospective ported
                                  from omni-pca/docs/JOURNEY.md
  changelog.md (1213 w)           full 2026.5.10 release notes

Dockerfile — pinned node:lts-alpine and caddy:latest (registry-1
.docker.io was returning 'tls: internal error' on node:25-alpine and
caddy:2-alpine pulls; the pinned tags are cached locally and work).
TODO comment notes to bump back to node:25 once registry stabilises.

.gitignore — added .env / .env.local just in case.

Build: 13 pages built clean in 1.83s, sitemap + Pagefind search index
emitted. Container runs at hai-omni-docs-docs (caddy network), accepts
requests with Host: hai-omni-pro-ii.warehack.ing, returns rendered
Starlight HTML with title/description meta intact. Once DNS for
hai-omni-pro-ii.warehack.ing points at the host, the site is live.
2026-05-10 17:05:25 -06:00
c5e72c679b Initial scaffold
Astro 6 + Starlight 0.39 documentation site for omni-pca, organised
around the Diatáxis framework (Tutorials / How-to / Reference /
Explanation), plus a chronological Journey page and Changelog.

Theme: muted slate-blue with amber accents. astro-icon + lucide
preinstalled. Astro telemetry and Starlight devToolbar both off.

Deployment: multi-stage Dockerfile (node:25-alpine builder ->
caddy:2-alpine runtime), inner Caddy serves static dist on :80,
outer caddy-docker-proxy on the host terminates TLS for
hai-omni-pro-ii.warehack.ing.
2026-05-10 16:42:12 -06:00