Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4202a53
feat(ocpp): restore the OCPP 1.6J Central System package
claude Jul 31, 2026
3a90d30
chore: add changeset for the OCPP central system restore
claude Jul 31, 2026
80d27da
feat(ocpp): enable the central system and document driverless OCPP ch…
claude Jul 31, 2026
7952511
feat(ocpp): control chargers with charging profiles instead of remote…
claude Jul 31, 2026
219267b
fix(ocpp): stop serving the OCPP password over /api/config
claude Jul 31, 2026
855487f
feat(ocpp): serve OCPP 2.0.1 alongside 1.6J
claude Jul 31, 2026
86b45dd
docs(ocpp): document where the protocol code comes from
claude Jul 31, 2026
8062714
Merge remote-tracking branch 'upstream/master' into worktree-ocpp-res…
HuggeK Jul 31, 2026
26854b3
docs: note 2.0.1 in the driver-writing OCPP callout
claude Jul 31, 2026
7a88465
docs(ocpp): reword the licence line for the brand-copy check
claude Jul 31, 2026
3ec399d
Merge remote-tracking branch 'upstream/master' into worktree-ocpp-res…
claude Aug 5, 2026
4008ce8
feat(ui): Chargers tab with an OCPP panel and charge-point bindings
claude Aug 5, 2026
c96009c
Merge remote-tracking branch 'upstream/master' into worktree-ocpp-res…
HuggeK Aug 5, 2026
995e031
Merge remote-tracking branch 'upstream/master' into worktree-ocpp-res…
HuggeK Aug 5, 2026
4dc8b70
feat(ocpp): quarantine unadopted charge points as pending
claude Aug 5, 2026
64ddbfc
feat(ocpp): hot-apply charger adoption with the loadpoint reload
claude Aug 5, 2026
dbe861a
docs(ocpp): vehicle capacity describes one car; per-car profiles need…
claude Aug 5, 2026
787104a
feat(ocpp): vehicle profiles — identify the car, switch capacity and …
claude Aug 5, 2026
73a03e2
feat(ocpp): probe, record and present whether a charger can be steered
claude Aug 9, 2026
2540b56
fix(ocpp): ask each charger for its capabilities once, not twice
claude Aug 9, 2026
400c26e
fix(ocpp): clear the capability marker when a charger drops mid-probe
claude Aug 9, 2026
eac3b75
Merge upstream/master into worktree-ocpp-restore
claude Aug 9, 2026
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
11 changes: 11 additions & 0 deletions .changeset/chargers-tab-ocpp-panel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"ftw": minor
---

Rename the Loadpoints tab and dashboard section to Chargers, and give it an
OCPP panel: the exact backend URL to enter on a charger, live state for every
connected charge point (vendor, dialect, vehicle, power, session energy), and
the connected charge points offered in the charger-driver dropdown so an OCPP
charger can be bound to the planner without editing YAML. Backed by a new
GET /api/ocpp/chargers endpoint. Docs now recommend a DHCP reservation for the
FTW host before commissioning chargers.
13 changes: 13 additions & 0 deletions .changeset/ocpp-capability-probe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"ftw": minor
---

FTW now asks each OCPP charger whether it can be steered, and says so. Shortly
after a charger connects, core reads its `SupportedFeatureProfiles` (1.6) or
`SmartChargingCtrlr.Available` (2.0.1), records the raw answer, and shows a
Control column on Settings → Chargers: "smart charging", "telemetry only" (with
a warning explaining the charger will meter but never plan), or "not reported"
for a charger that stayed silent. Also exposed as `steerable` and
`feature_profiles` on `GET /api/ocpp/chargers`. The verdict is advisory —
commands are still attempted, so a charger that under-reports its own
capabilities is never locked out of control.
31 changes: 31 additions & 0 deletions .changeset/ocpp-central-system-restore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
"ftw": minor
---

Add OCPP 1.6J support so EV chargers connect to FTW directly instead of through
a vendor cloud. An OCPP charger needs no driver: the protocol is vendor-neutral,
so one server in core handles every charger that speaks it.

Chargers dial FTW rather than the other way round, so there is nothing to add
under `drivers:`. A charge point becomes a device on its first BootNotification,
keyed by the last segment of the URL it connected to, and dispatch treats it
like any other EV reading.

This reinstates `go/internal/ocpp`, retired as unused in #578, and wires it into
the process behind a new `ocpp` config section. It matters because Charge Amps
has no FTW driver at all and every current model speaks OCPP, while Easee and
Zaptec can be commissioned once through their vendor portal and then run with no
cloud in the runtime path.

FTW throttles, pauses and resumes an OCPP charger like any other EV charger.
Every command is a current limit rather than a remote start or stop, because
`RemoteStopTransaction` is unreliable on Charge Amps hardware — units
acknowledge the stop and resume charging on their own, while a 0 A charging
profile is honoured consistently and keeps the session meter intact across a
pause. Below the IEC 61851 minimum of 6 A the charger is told 0 A rather than
being rounded up to current the site fuse was not asked to carry.

The server is off by default. Enabling it requires a username and password, and
FTW refuses to start without them: the OCPP library builds its listen address
from the port alone, so the socket is reachable on every interface and basic
auth is the only gate. Keep the port closed at your router.
13 changes: 13 additions & 0 deletions .changeset/ocpp-pending-quarantine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"ftw": minor
---

OCPP charge points now start quarantined. A charge point that no charger entry
(loadpoint) names connects as "pending": it shows on Settings → Chargers with
its vendor, dialect and live state so it can be adopted, but its telemetry is
withheld from the site — no DerEV reading, no driver health, no metrics — and
it is never commanded. This stops any device that merely knows the shared OCPP
password from fabricating EV load and steering dispatch (the DerEV sum
suppresses home-battery discharge). Adopting a charger = adding a charger
entry with its id as the charger driver and saving — charger entries
hot-reload, so adoption and un-adoption take effect on the save.
26 changes: 26 additions & 0 deletions .changeset/ocpp-v201-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
"ftw": minor
---

Serve OCPP 2.0.1 alongside 1.6J, so newer chargers connect without a driver too.

Each version listens on its own port. A charger picks its dialect during the
WebSocket handshake, before any message is sent, and the underlying library
keeps one message handler per listener — so a single port cannot serve both.
Set `ocpp.port_v201` to enable 2.0.1; leaving it unset keeps 1.6J only.

Only the message encoding differs. Both dialects share one charger map, one
telemetry path and one control path, so a 2.0.1 charger is metered, throttled
and paused exactly like a 1.6 one, and dispatch cannot tell them apart.

2.0.1 restructures the messages more than the names suggest: StartTransaction
and StopTransaction collapse into a single TransactionEvent, transaction ids
become strings, connector status loses its charging meaning, and meter samples
arrive inside transaction events as well as on their own. The new handler
normalises all of that back to the same charger state.

OCPP 2.1 is not supported. No production-grade Go implementation of it exists:
the library FTW uses covers 1.6 and 2.0.1 and has no 2.1 support, and the Go
projects that do claim 2.1 are early-stage validators and emulators rather than
servers. Adding it later is one more handler and one more listener; the
version-neutral core does not change.
13 changes: 13 additions & 0 deletions .changeset/vehicle-profiles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"ftw": minor
---

Vehicle profiles for chargers shared by several cars. A new `vehicles:` config
list (also editable under Settings → Chargers → Vehicles) holds each car's
battery capacity, identifiers and charging policy — PV-surplus-only and/or a
target SoC the planner fills toward in the cheapest tariff hours. When an OCPP
charging session identifies the car (the RFID idTag on 1.6, a MacAddress or
eMAID idToken on 2.0.1), the charger switches to that car's capacity and
policy for the session; capacity reverts on plug-out. A session matching no
profile changes nothing — the visitor default — and the identity it presented
is shown in the Chargers tab so it can be pasted into a profile.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ rule. See [docs/architecture.md](docs/architecture.md).
- local web UI, SQLite history and Parquet rolloff;
- Home Assistant MQTT discovery;
- CalDAV planning intents and published schedules;
- hot-reloadable, independently released Lua drivers.
- hot-reloadable, independently released Lua drivers;
- a built-in OCPP 1.6J + 2.0.1 server, so OCPP chargers connect with no driver.

The local catalog is generated from `DRIVER` metadata. The public
[`srcfl/device-drivers`](https://github.com/srcfl/device-drivers) repo is the
Expand Down Expand Up @@ -85,6 +86,11 @@ It installs Docker when needed, creates `~/ftw`, downloads the Compose file
and starts core, optimizer, updater and the local MQTT broker. Open
`http://<host>:8080/setup` on the LAN.

Give the FTW machine a DHCP reservation (a fixed IP) in your router. Devices
that dial in to FTW — OCPP chargers store their backend URL at commissioning,
and some hardware whitelists which addresses may talk to it — silently lose
the connection if DHCP later hands the host a different address.

Existing Forty Two Watts or older FTW deployments must use the
[legacy upgrade guide](docs/upgrade-from-legacy.md) so configuration and state
are preserved. Raspberry Pi image installation is covered by
Expand Down Expand Up @@ -177,6 +183,10 @@ driver, which can update or roll back without a new FTW core release. Device
Support may consume the same public source later for other products or a higher
support level.

EV chargers that speak OCPP are the exception: they need no driver. FTW runs an
OCPP Central System (1.6J and 2.0.1), so the charger connects and registers itself.
See [docs/ocpp.md](docs/ocpp.md).

## Releases

There are two channels:
Expand All @@ -200,6 +210,7 @@ metadata are the detailed reference.
- [Operations and recovery](docs/operations.md)
- [Full backup and safe restore](docs/backup-and-restore.md)
- [Writing a driver](docs/writing-a-driver.md)
- [OCPP chargers (no driver needed)](docs/ocpp.md)
- [Device driver catalog](https://srcfl.github.io/device-drivers/) — every supported device and the evidence behind it
- [Self-update and release channels](docs/self-update.md)
- [Home Assistant](docs/ha-integration.md)
Expand Down
23 changes: 23 additions & 0 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,29 @@ caldav:
plan_path: /ftw/plan/
# plan_publish_interval_s: 900 # how often the plan calendar is reconciled

# Built-in OCPP Central System, speaking 1.6J and 2.0.1. EV chargers that speak
# OCPP connect to FTW directly — there is no driver to write and nothing to add
# under `drivers:`.
# A charger appears as a device on its first BootNotification, keyed by the last
# segment of the URL it dialled: ws://<ftw-host>:8887/garage-left
#
# Each version needs its own port: a charger picks its dialect in the WebSocket
# handshake, so one listener cannot serve both. 2.1 is not supported — no
# production-grade Go implementation of it exists yet.
#
# Credentials are required when enabled, and FTW refuses to start without them:
# the OCPP library builds its listen address from the port alone, so the socket
# is reachable on every interface and basic auth is the only gate. Keep the port
# closed at your router. See docs/ocpp.md.
ocpp:
enabled: false
port: 8887 # OCPP 1.6J
# port_v201: 8888 # OCPP 2.0.1; omit to disable
path: /
username: ftw
password: "" # required when enabled; use a long random string
heartbeat_interval_s: 60

# Persistent state (SQLite)
state:
path: state.db
Expand Down
Loading