Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
**/node_modules
**/dist
.git
43 changes: 43 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run check
- run: npm test
- run: npm run build
- name: Validate the demo house
run: node server/dist/cli.js validate examples/demo-house

docker:
runs-on: ubuntu-latest
needs: build
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
if: github.event_name == 'push'
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.event_name == 'push' }}
tags: ghcr.io/matdata-eu/hadoc:latest
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
dist/
*.log
.DS_Store
.env
29 changes: 29 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# syntax=docker/dockerfile:1
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
COPY server/package.json server/
COPY web/package.json web/
RUN npm ci
COPY tsconfig.base.json ./
COPY schema schema
COPY server server
COPY web web
RUN npm run build && npm prune --omit=dev

FROM node:22-alpine
ENV NODE_ENV=production \
HADOC_DATA=/data \
PORT=8080
WORKDIR /app
COPY --from=build /app/package.json ./
COPY --from=build /app/node_modules node_modules
COPY --from=build /app/schema schema
COPY --from=build /app/server/package.json server/
COPY --from=build /app/server/dist server/dist
COPY --from=build /app/web/dist web/dist
COPY examples/demo-house /data
USER node
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://127.0.0.1:8080/api/health || exit 1
CMD ["node", "server/dist/index.js"]
110 changes: 109 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,111 @@
# HAdoc

Home automation documentation: floor plans, cabling, power and automation links for your house, in a phone-friendly web app.
**Home automation documentation.** One phone-friendly web app that answers, for anything in the house: *what is this, where does it get power, how is it wired, and what controls it?*

HAdoc keeps floor plans, cabling, power circuits, controller I/O (PLC channels, ESP pins) and the link to your home automation software in one place, as plain YAML files you keep in git.

> Status: early scaffold. The viewer, data format, validation and trace work; editing in the app, importers and the AREI diagrams are on the [roadmap](#roadmap).

## What you get

- **Floor plan** per level, with rooms, points and cable routes. Pinch and drag on a phone, scroll and drag on a desktop.
- **Layers** for power, automation, data, hidden things and cables.
- **Tap anything** to see its trace: the circuit and breaker that feed it, the cables and terminals it lands on, the device or PLC channel behind it, and the MQTT topic or openHAB item that reads it.
- **"If it stops working"** notes in plain language, for everyone in the house.
- **Checks** that flag broken references, IDs that don't follow your scheme, and what is still undocumented.
- **Search** by room, ID, device, PLC name or openHAB item.
- Installs on a phone home screen and keeps showing the last copy when the server is down.

## Quick start

```bash
docker run -d --name hadoc -p 8090:8080 -v "$PWD/house:/data" ghcr.io/matdata-eu/hadoc:latest
```

Open http://localhost:8090. Without a volume the image shows the demo house from [`examples/demo-house`](examples/demo-house). See [`docker-compose.yml`](docker-compose.yml) for a compose example.

To run from source (Node 22):

```bash
npm install
npm run build
node server/dist/index.js examples/demo-house
```

For development, run `npm run dev -w server` and `npm run dev -w web` in two terminals and open the Vite URL.

## Your data

Copy [`examples/demo-house`](examples/demo-house) into its own (private) git repo and edit the YAML. Every file has a JSON Schema in [`schema/`](schema); the `# yaml-language-server` line at the top of each file gives completion and validation in VS Code with the YAML extension.

| File | Holds |
| --- | --- |
| `settings.yaml` | House name, the **point ID pattern** and the **level and type abbreviations** |
| `site.yaml` | Floors and rooms, in centimetres, with an optional plan image as tracing underlay |
| `points/*.yaml` | Switches, sensors, lights, sockets, hidden boards: anything you can touch |
| `enclosures.yaml` | Distribution boards, PLC cabinets, racks |
| `devices.yaml` | PLC and I/O modules, ESP boards, relays, breakers, with their ports |
| `cables.yaml` | Cables by the label on the cable, their ends and route |
| `circuits.yaml` | Breaker circuits and what they feed |
| `signals.yaml` | Where software sees a port: PLC name and address, MQTT topic, openHAB item |

Cable and circuit ends are written as a point or device ID, or as `device:port` (for example `plc-di01:DI 01.1`). Plan images go in `plans/`, photos in `photos/`.

### Point IDs

The default pattern is `{level}-{room}-{type}-{nr}`, for example `1-DRS-PIR-01`. The pattern and every abbreviation are settings:

```yaml
ids:
pattern: "{level}-{room}-{type}-{nr}"
nr_digits: 2
levels:
- { code: MIN1, name: Basement }
- { code: "0", name: Ground floor }
types:
- { code: PIR, name: Motion sensor, layer: automation }
```

Room codes are defined per floor in `site.yaml`.

### Validate

```bash
node server/dist/cli.js validate path/to/house
```

Errors (broken references, schema violations, IDs that contradict their fields) exit with code 1, so this works as a pre-commit hook or CI step in your data repo. Warnings list what is still undocumented.

## API

| Endpoint | Returns |
| --- | --- |
| `GET /api/house` | All data plus error and warning counts |
| `GET /api/trace/:id` | The trace for a point, device or enclosure |
| `GET /api/issues` | Every error and warning |
| `GET /api/health` | Liveness and when the data was last loaded |

The server reloads the data when files in the data folder change.

## Roadmap

1. **MVP**: plan underlay, rooms, points, point pages, trace, checks *(this scaffold)*. Next: placing and editing points in the app, with each change committed to git.
2. **WAGO import** from CODESYS 2.3 `PLC CONFIGURATION.EXP`, seeding points from channel names and comments.
3. **Cables and circuits**: drawing routes, terminals and cores, breaker colouring.
4. **Importers**: openHAB, UniFi (status and IP), ESPHome, Tasmota, MQTT, plus the "undocumented" report and the rules behind each item.
5. **PLC logic viewer** from POU exports, wall and enclosure views, QR labels.
6. **AREI** single-line diagram (eendraadschema) and situation sketch (situatieschets).
7. Importers for other systems (Home Assistant, KNX, Loxone, Shelly).

## Project layout

```
schema/ JSON Schemas for every data file
server/ Fastify API, YAML loader, checks, trace, CLI (TypeScript)
web/ Svelte app: floor plan, details, issues
examples/ Demo house
```

## License

[MIT](LICENSE)
11 changes: 11 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Example: run HAdoc next to openHAB. Point the volume at a git checkout of your house data.
services:
hadoc:
image: ghcr.io/matdata-eu/hadoc:latest
build: .
container_name: hadoc
restart: unless-stopped
ports:
- "8090:8080"
volumes:
- ./house:/data
22 changes: 22 additions & 0 deletions examples/demo-house/cables.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# yaml-language-server: $schema=../../schema/cables.schema.json
# Cable IDs are the labels on the cables.
- id: K-0012
type: SVV 4x0.8
from: 0-HAL-SW-01
to: "plc-di01:DI 01.1"
route:
- level: "0"
path: [[40, 760], [20, 760], [20, 20]]

- id: K-0031
type: XVB 3G1.5
from: "plc-do01:DO 01.1"
to: 0-HAL-LGT-01

- id: K-0107
type: null
from: 1-DRS-PIR-01
to: "esp-dressing:GPIO14"
route:
- level: "1"
path: [[450, 30], [520, 30], [520, 80]]
8 changes: 8 additions & 0 deletions examples/demo-house/circuits.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# yaml-language-server: $schema=../../schema/circuits.schema.json
- id: F3
name: Lights first floor
breaker: F3
rating: C16
rcd: RCD1
phase: L1
feeds: [1-DRS-LGT-01, sonoff-mirror, 1-BED-BLD-01]
55 changes: 55 additions & 0 deletions examples/demo-house/devices.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# yaml-language-server: $schema=../../schema/devices.schema.json
- id: plc
kind: plc
model: WAGO 750-881
location: plc-cabinet
hostname: plc.lan
notes: Low-level logic for switches and blinds.

- id: plc-di01
kind: io-module
model: WAGO 750-403 (4 DI 24V)
label: DI 01
location: plc
ports:
- { name: "DI 01.1", signal: DI 24V }
- { name: "DI 01.2", signal: DI 24V }
- { name: "DI 01.3", signal: DI 24V }
- { name: "DI 01.4", signal: DI 24V }

- id: plc-do01
kind: io-module
model: WAGO 750-504 (4 DO 24V)
label: DO 01
location: plc
ports:
- { name: "DO 01.1", signal: DO 24V }
- { name: "DO 01.2", signal: DO 24V }

- id: esp-dressing
kind: esp
model: NodeMCU v3
location: 1-DRS-ESP-01
firmware: ESPHome
hostname: esp-dressing
mac: null
powered_by: sonoff-mirror
ports:
- { name: GPIO14, signal: GPIO }

- id: sonoff-mirror
kind: relay
model: Sonoff Mini
location: 1-DRS-LGT-01
firmware: Tasmota
hostname: sonoff-mirror

- id: F3
kind: breaker
model: C16
location: board-main

- id: RCD1
kind: rcd
model: 30 mA type A
location: board-main
14 changes: 14 additions & 0 deletions examples/demo-house/enclosures.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# yaml-language-server: $schema=../../schema/enclosures.schema.json
- id: board-main
name: Main distribution board
level: MIN1
room: null
pos: null
rails: 4

- id: plc-cabinet
name: PLC cabinet
level: MIN1
room: null
pos: null
rails: 3
25 changes: 25 additions & 0 deletions examples/demo-house/points/0-ground-floor.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# yaml-language-server: $schema=../../../schema/points.schema.json
- id: 0-HAL-SW-01
type: SW
level: "0"
room: HAL
name: Hall light switch by the front door
pos: [40, 760]
height: 110
if_broken: The hall light can also be switched from the app.

- id: 0-HAL-LGT-01
type: LGT
level: "0"
room: HAL
name: Hall ceiling light
pos: [150, 250]
height: 260

- id: 0-KIT-SKT-01
type: SKT
level: "0"
room: KIT
name: Socket above the sink
pos: [700, 20]
height: 110
35 changes: 35 additions & 0 deletions examples/demo-house/points/1-first-floor.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# yaml-language-server: $schema=../../../schema/points.schema.json
- id: 1-DRS-PIR-01
type: PIR
level: "1"
room: DRS
name: Motion sensor above the mirror
pos: [450, 30]
height: 240
notes: Wired to a hidden ESP board above the ceiling hatch.
if_broken: Check that the ESP board is online in the app. It is powered from the mirror light relay.

- id: 1-DRS-ESP-01
type: ESP
level: "1"
room: DRS
name: Hidden ESP board above the ceiling hatch
pos: [520, 80]
height: null
device: esp-dressing

- id: 1-DRS-LGT-01
type: LGT
level: "1"
room: DRS
name: Mirror light
pos: [450, 60]
height: 200

- id: 1-BED-BLD-01
type: BLD
level: "1"
room: BED
name: Bedroom blind
pos: [990, 250]
height: 240
22 changes: 22 additions & 0 deletions examples/demo-house/settings.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# yaml-language-server: $schema=../../schema/settings.schema.json
name: Demo house
language: en

# Point IDs. Change the pattern and the abbreviations to your own convention.
ids:
pattern: "{level}-{room}-{type}-{nr}"
nr_digits: 2

levels:
- { code: MIN1, name: Basement }
- { code: "0", name: Ground floor }
- { code: "1", name: First floor }

types:
- { code: SW, name: Wall switch, layer: automation }
- { code: PIR, name: Motion sensor, layer: automation }
- { code: LGT, name: Light, layer: power }
- { code: SKT, name: Socket, layer: power }
- { code: BLD, name: Blind motor, layer: automation }
- { code: ESP, name: Hidden ESP board, layer: hidden }
- { code: JB, name: Junction box, layer: hidden }
Loading
Loading