Skip to content

Commit 8b99f06

Browse files
committed
feat(pages): sidebar, search, and a compatibility page
Compared the site against the two best-documented projects in this space, Tasmota and Zigbee2MQTT. Both do three things this site did not: a persistent sidebar, client-side search, and a prominent "which devices does this work on" destination. The sidebar is the one that mattered. Under the primer theme an inner page rendered with NO navigation at all, so anyone arriving from a search engine on, say, the OTA page could reach nothing else and had no way back. Search traffic lands on inner pages by definition, which made it the worst possible gap for a site whose entire purpose is being found. Switches to just-the-docs (sidebar + Ctrl+K search, no server), adds section landing pages, and teaches the assembler to inject `parent` and `nav_order` so pages nest correctly. Guide pages are ordered by reading sequence rather than alphabetically: wiring, then flashing, then commissioning, since that is the order a newcomer needs them. Adds a compatibility page, the equivalent of the "supported devices" page both comparables lead with, and the thing someone types a model number into a search engine looking for. It separates what is confirmed on real hardware from what merely shares the bus protocol, and says plainly that "should work" is not "does work". Also builds the site on pull requests, deploying only from main. The site previously built only after merge, which is exactly how the broken path filter reached main: nothing could fail before it was too late. Assisted-by: AI
1 parent 08918aa commit 8b99f06

8 files changed

Lines changed: 198 additions & 24 deletions

File tree

.github/scripts/assemble_docs_site.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,26 @@
1616
SITE = "_site_src"
1717

1818
# (source dir, destination subdir, label used only in log output)
19+
# (source dir, destination subdir, log label, sidebar parent title)
20+
# The parent title MUST match the `title:` of docs-site/<subdir>/index.md, which is how
21+
# just-the-docs nests a page under a section in the sidebar.
1922
SOURCES = [
20-
("firmware/docs", "firmware", "firmware docs"),
21-
("reverse-engineering/docs", "internals", "reverse engineering"),
22-
("docs/guide", "guide", "guide"),
23+
("firmware/docs", "firmware", "firmware docs", "Firmware"),
24+
("reverse-engineering/docs", "internals", "reverse engineering", "Reverse engineering"),
25+
("docs/guide", "guide", "guide", "Guide"),
26+
]
27+
28+
# Guide pages read in a deliberate order rather than alphabetically: a newcomer needs wiring before
29+
# flashing before commissioning. Anything unlisted sorts after these, alphabetically.
30+
GUIDE_ORDER = [
31+
"Hardware-and-Wiring",
32+
"Installing-Custom-Firmware",
33+
"Commissioning-and-HA-Setup",
34+
"Everyday-Control",
35+
"OTA-Updates",
36+
"Recovery-and-Reflash",
37+
"ESP32-Replacement-Build",
38+
"FAQ-Gotchas",
2339
]
2440

2541
# Wiki pages that are navigation fragments, plus its own linter. Not content.
@@ -100,7 +116,7 @@ def sub(m):
100116
return re.sub(r"\[([^\]]*)\]\(([A-Za-z][A-Za-z0-9._-]*)(#[^)]*)?\)", sub, text)
101117

102118

103-
def convert(src, dst, pages=None):
119+
def convert(src, dst, pages=None, parent=None, order=None):
104120
with open(src, encoding="utf-8") as fh:
105121
lines = fh.readlines()
106122

@@ -118,6 +134,10 @@ def convert(src, dst, pages=None):
118134
fm = ["---", f"title: {yaml_quote(title)}"]
119135
if desc:
120136
fm.append(f"description: {yaml_quote(desc)}")
137+
if parent:
138+
fm.append(f"parent: {yaml_quote(parent)}")
139+
if order is not None:
140+
fm.append(f"nav_order: {order}")
121141
fm += ["---", ""]
122142

123143
body = "".join(lines)
@@ -135,7 +155,7 @@ def main():
135155
shutil.copytree("docs-site", SITE)
136156

137157
total = 0
138-
for src_dir, sub, label in SOURCES:
158+
for src_dir, sub, label, parent in SOURCES:
139159
if not os.path.isdir(src_dir):
140160
print(f" skip {label}: {src_dir} absent")
141161
continue
@@ -144,12 +164,21 @@ def main():
144164
# Page-name set for the link rewrite above (wiki only; repo docs link by filename).
145165
pages = {os.path.splitext(f)[0] for f in os.listdir(src_dir) if f.endswith(".md")} \
146166
if src_dir == "docs/guide" else None
167+
# Sidebar order: guide pages follow the reading order above; the numbered docs sort by
168+
# their own filename prefix, which is already meaningful.
169+
def sort_key(fn):
170+
stem = os.path.splitext(fn)[0]
171+
if sub == "guide":
172+
return (GUIDE_ORDER.index(stem) if stem in GUIDE_ORDER else len(GUIDE_ORDER), stem)
173+
return (0, stem)
174+
147175
n = 0
148-
for name in sorted(os.listdir(src_dir)):
176+
for name in sorted(os.listdir(src_dir), key=sort_key):
149177
# Leading underscore = nav fragment or repo-facing note, never site content.
150178
if name in SKIP or name.startswith("_") or not name.endswith(".md"):
151179
continue
152-
convert(os.path.join(src_dir, name), os.path.join(out_dir, name), pages)
180+
convert(os.path.join(src_dir, name), os.path.join(out_dir, name), pages,
181+
parent=parent, order=n + 1)
153182
n += 1
154183
# Copy assets too. Pages reference them RELATIVELY (![](images/foo.png)), so they have to
155184
# land beside the markdown that points at them or every image 404s -- which is exactly

.github/workflows/pages.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ on:
1818
- README.md
1919
- .github/workflows/pages.yaml
2020
- .github/scripts/assemble_docs_site.py # the assembler IS the build; changing it must rebuild
21+
pull_request: # build-only on PRs: catches a broken site config BEFORE it reaches main
22+
paths:
23+
- docs-site/**
24+
- docs/guide/**
25+
- firmware/docs/**
26+
- reverse-engineering/docs/**
27+
- .github/workflows/pages.yaml
28+
- .github/scripts/assemble_docs_site.py
2129
workflow_dispatch:
2230

2331
permissions:
@@ -50,6 +58,7 @@ jobs:
5058

5159
deploy:
5260
needs: build
61+
if: github.event_name != 'pull_request'
5362
runs-on: ubuntu-latest
5463
environment:
5564
name: github-pages

docs-site/_config.yml

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,31 @@ description: >-
1616
url: https://andrewdemsds.github.io
1717
baseurl: /hisense-w41h1
1818

19-
theme: jekyll-theme-primer
19+
# just-the-docs, not primer: primer renders a bare page with NO navigation, so anyone arriving
20+
# from a search engine on an inner page has no way to reach anything else. Every leading project
21+
# in this space (Tasmota, Zigbee2MQTT) uses a persistent sidebar plus search for exactly that
22+
# reason. remote_theme works on GitHub Pages via jekyll-remote-theme.
23+
remote_theme: just-the-docs/just-the-docs
24+
25+
# Client-side search over every page. No server, no external service.
26+
search_enabled: true
27+
search:
28+
heading_level: 3
29+
previews: 3
30+
tokenizer_separator: /[\s/]+/
31+
32+
aux_links:
33+
"Source on GitHub":
34+
- "https://github.com/AndrewDemsDS/hisense-w41h1"
35+
aux_links_new_tab: true
36+
37+
heading_anchors: true
38+
back_to_top: true
39+
back_to_top_text: "Back to top"
40+
41+
nav_external_links:
42+
- title: "Releases (firmware downloads)"
43+
url: "https://github.com/AndrewDemsDS/hisense-w41h1/releases"
2044

2145
plugins:
2246
- jekyll-seo-tag # per-page <title>, meta description, canonical, Open Graph, JSON-LD

docs-site/compatibility.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
title: Will this work on my A/C?
3+
nav_order: 5
4+
description: >-
5+
Which Hisense air conditioners and Wi-Fi modules this de-cloud firmware works on: confirmed
6+
hardware, the AEH-W41H1 and AEH-W4A1 modules, and how to check your own unit before buying
7+
anything.
8+
---
9+
10+
# Will this work on my A/C?
11+
12+
Short version: **if your A/C uses a ConnectLife Wi-Fi module, the odds are good, and you can find
13+
out for certain in about a minute without opening anything.**
14+
15+
Two things have to be true. The bus protocol has to match, and the module has to be one you can
16+
either reflash or replace.
17+
18+
## Confirmed working
19+
20+
| | |
21+
|---|---|
22+
| **Wi-Fi module** | `AEH-W41H1` (Realtek RTL8710C / AmebaZ2), secure boot off |
23+
| **App it came with** | ConnectLife |
24+
| **Bus** | RS-485, 9600 8N1, on the 4-pin module connector (5 V · GND · A · B) |
25+
| **Verified on** | two units, running continuously |
26+
27+
Both the protocol and the flashing route are proven on this hardware. Everything on this site was
28+
written from those units.
29+
30+
## Very likely, not personally verified
31+
32+
**Other Hisense units using the same module family.** The `AEH-W4A1` appears throughout the
33+
reference material this project built on and speaks the same bus. The protocol work should carry
34+
over; the flashing details may differ.
35+
36+
**Other brands on the same bus.** This A/C bus is not Hisense-only. The community
37+
[`esphome_airconintl`](https://github.com/pslawinski/esphome_airconintl) project drives the identical
38+
protocol on AirconIntl hardware, which is why the codec here could be cross-checked against its
39+
sample frames before it ever touched an A/C. Hisense manufactures for several brands, so a
40+
rebadged unit may well be the same machine underneath.
41+
42+
Being honest about the boundary: "should work" is not "does work". If you try it on something not
43+
listed above, [open an issue](https://github.com/AndrewDemsDS/hisense-w41h1/issues) with what you
44+
find, working or not.
45+
46+
## How to check your own unit
47+
48+
**1. Does it use ConnectLife?** If your A/C pairs with the ConnectLife app, it is in the right
49+
family. Some regions ship the same hardware under a different app name.
50+
51+
**2. Look at the module bay.** The Wi-Fi module is a small plastic dongle in a slot on the indoor
52+
unit, usually behind the front panel and reachable without tools. The part number is printed on it.
53+
54+
**3. Count the pins.** Four pins (5 V, GND, and an RS-485 A/B pair) is the signature this project
55+
targets.
56+
57+
## If your module is not supported, or is dead
58+
59+
You do not need the original module. The [ESP32 route](guide/ESP32-Replacement-Build.html) puts an
60+
ESP32 plus an RS-485 transceiver in the module bay instead, speaking the same bus bytes. It costs
61+
about €5, needs no CH341A clip, and is the recommended path if you do not already have a working
62+
`AEH-W41H1` — those modules are fragile and increasingly hard to buy.
63+
64+
The [path comparison](firmware/13-path-comparison.html) covers the tradeoffs with figures measured
65+
on real hardware.
66+
67+
## What you get either way
68+
69+
Local Matter control in Home Assistant with no cloud: mode, setpoint, six fan speeds, swing, and
70+
the Eco / Quiet / Turbo / Sleep special modes, plus temperature and energy telemetry. See
71+
[everyday control](guide/Everyday-Control.html).

docs-site/firmware/index.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
title: Firmware
3+
nav_order: 3
4+
has_children: true
5+
description: Build, OTA procedure, Matter clusters, attestation, QA strategy and energy monitoring for the custom firmware.
6+
---
7+
8+
# Firmware
9+
10+
How the firmware is built, versioned and shipped, what it exposes over Matter, and how it is tested
11+
without hardware.

docs-site/guide/index.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
title: Guide
3+
nav_order: 2
4+
has_children: true
5+
description: Wiring, flashing, commissioning, everyday control, OTA updates and recovery for a de-clouded Hisense A/C.
6+
---
7+
8+
# Guide
9+
10+
Everything needed to take a Hisense A/C off the ConnectLife cloud and run it locally, in the order
11+
you will need it: wiring, flashing, commissioning into Home Assistant, day-to-day control, OTA
12+
updates, and getting back out of trouble.

docs-site/index.md

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
title: Hisense AEH-W41H1 de-cloud with custom Matter firmware
3+
nav_order: 1
34
description: >-
45
Replace the ConnectLife cloud on a Hisense AEH-W41H1 (Realtek RTL8710C / AmebaZ2) air-conditioner
56
Wi-Fi module with custom Matter firmware for local Home Assistant control. Zero cloud.
@@ -20,21 +21,26 @@ Two hardware paths are documented and both are running on real units:
2021
Everything below is written from a working system, not a plan. The RS-485 protocol was
2122
reverse-engineered from the stock firmware and validated against live hardware.
2223

24+
## Not sure if your A/C is supported?
25+
26+
**[Check compatibility](compatibility.html)** — which units are confirmed, and how to tell in a minute
27+
without opening anything.
28+
2329
## Start here
2430

2531
| | |
2632
|---|---|
27-
| [Hardware and wiring](guide/Hardware-and-Wiring) | pinout, the 4-pin module port, RS-485 A/B |
28-
| [Installing the custom firmware](guide/Installing-Custom-Firmware) | CH341A clip, or convert a stock unit over the air |
29-
| [Commissioning and Home Assistant](guide/Commissioning-and-HA-Setup) | pairing into python-matter-server and HA |
30-
| [Everyday control](guide/Everyday-Control) | modes, fan, swing, Eco / Quiet / Turbo / Sleep |
31-
| [OTA updates](guide/OTA-Updates) | Matter OTA, the break-glass HTTP path, and the serial trap |
32-
| [Recovery and reflash](guide/Recovery-and-Reflash) | getting back from a bad flash |
33-
| [FAQ and gotchas](guide/FAQ-Gotchas) | the things that actually bite |
33+
| [Hardware and wiring](guide/Hardware-and-Wiring.html) | pinout, the 4-pin module port, RS-485 A/B |
34+
| [Installing the custom firmware](guide/Installing-Custom-Firmware.html) | CH341A clip, or convert a stock unit over the air |
35+
| [Commissioning and Home Assistant](guide/Commissioning-and-HA-Setup.html) | pairing into python-matter-server and HA |
36+
| [Everyday control](guide/Everyday-Control.html) | modes, fan, swing, Eco / Quiet / Turbo / Sleep |
37+
| [OTA updates](guide/OTA-Updates.html) | Matter OTA, the break-glass HTTP path, and the serial trap |
38+
| [Recovery and reflash](guide/Recovery-and-Reflash.html) | getting back from a bad flash |
39+
| [FAQ and gotchas](guide/FAQ-Gotchas.html) | the things that actually bite |
3440

3541
## Choosing a path
3642

37-
[ESP32 vs AmebaZ2](firmware/13-path-comparison) compares the two on cost, toolchain,
43+
[ESP32 vs AmebaZ2](firmware/13-path-comparison.html) compares the two on cost, toolchain,
3844
reproducibility, OTA mechanics, flash headroom and diagnostics, with figures measured on this
3945
project's own hardware rather than taken from datasheets.
4046

@@ -43,17 +49,17 @@ project's own hardware rather than taken from datasheets.
4349
The protocol and firmware analysis, if you want to port this to another Hisense unit or verify the
4450
claims:
4551

46-
- [RS-485 A/C protocol](internals/03-rs485-ac-protocol) — framing, checksum, every byte offset
47-
- [Stock firmware init and comms](internals/10-stock-fw-init-and-comms) — disassembly of the stock dongle
48-
- [Device-type to capability map](internals/11-model-capability-map) — how the A/C advertises its own features
49-
- [Hardware](internals/01-hardware) · [Cloud and firewall](internals/04-cloud-and-firewall) · [ESP32 replacement](internals/05-esp32-replacement)
52+
- [RS-485 A/C protocol](internals/03-rs485-ac-protocol.html) — framing, checksum, every byte offset
53+
- [Stock firmware init and comms](internals/10-stock-fw-init-and-comms.html) — disassembly of the stock dongle
54+
- [Device-type to capability map](internals/11-model-capability-map.html) — how the A/C advertises its own features
55+
- [Hardware](internals/01-hardware.html) · [Cloud and firewall](internals/04-cloud-and-firewall.html) · [ESP32 replacement](internals/05-esp32-replacement.html)
5056

5157
## Firmware and build
5258

53-
- [Firmware build and OTA procedure](firmware/10-firmware-ota-procedure) — the canonical reference
54-
- [Matter clusters exposed](firmware/01-expose-all-clusters) · [Attestation](firmware/02-fix-attestation)
55-
- [QA strategy](firmware/04-qa-strategy) · [Energy monitoring](firmware/09-energy-monitoring)
56-
- [Stock parity gaps](firmware/07-stock-parity-gaps) — what the stock firmware does that this does not, yet
59+
- [Firmware build and OTA procedure](firmware/10-firmware-ota-procedure.html) — the canonical reference
60+
- [Matter clusters exposed](firmware/01-expose-all-clusters.html) · [Attestation](firmware/02-fix-attestation.html)
61+
- [QA strategy](firmware/04-qa-strategy.html) · [Energy monitoring](firmware/09-energy-monitoring.html)
62+
- [Stock parity gaps](firmware/07-stock-parity-gaps.html) — what the stock firmware does that this does not, yet
5763

5864
## Source
5965

docs-site/internals/index.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
title: Reverse engineering
3+
nav_order: 4
4+
has_children: true
5+
description: The RS-485 protocol, stock firmware disassembly, and the device-type capability map behind this project.
6+
---
7+
8+
# Reverse engineering
9+
10+
The analysis this project is built on: the A/C's RS-485 protocol, the stock dongle's firmware, and
11+
how an A/C advertises which features it has. Read this if you want to port the work to another
12+
Hisense unit, or to check the claims rather than take them on trust.

0 commit comments

Comments
 (0)