Add OCPP control for the Anker Solix EV charger (Own/Proxy CSMS)
The Anker Solix connector was read-only (cloud monitoring only). Add an
OCPP 1.6J control path with a per-user, cascading control mode:
- off monitoring only (default, unchanged behavior)
- own DriverVault is the charger's Central System (full control)
- proxy DriverVault relays to Anker's cloud and injects commands
New internal/ocpp subsystem (stdlib-only, hand-rolled RFC 6455): a CSMS
with session management, inbound dispatch, and typed control commands
(RemoteStart/Stop, SetChargingProfile current limit, ChangeAvailability,
Reset, UnlockConnector, TriggerMessage, Get/ChangeConfiguration). Own- and
proxy-mode paths are verified end-to-end against a simulated charge point.
The charger connects to /ocpp/{serial}, authenticated with OCPP Basic auth
(serial + a per-charger control token) resolved to the owning user via an
in-memory token index. Control REST endpoints mirror the monitoring ones and
reuse the same cascade gate plus a live-session check. controlMode is a new
cascade field (global -> org -> user) advertised as a select on the plugin.
Frontend: control-mode select + provisioning card in Settings, and a real
Start/Stop/limit/reset control panel in Charging, gated on the active mode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
367113f538
commit
a1519f6e89
@@ -0,0 +1,28 @@
|
||||
package ocpp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Proxy mode reuses the Session read loops (session.go) to pump frames between
|
||||
// the charger and Anker's cloud while tapping status and injecting our own
|
||||
// control calls. This file only provides the upstream dialer and auth helpers;
|
||||
// the CSMS is handed the already-dialed upstream connection via Accept.
|
||||
|
||||
// DialUpstream opens a client OCPP 1.6J connection to an upstream CSMS (Anker's
|
||||
// cloud) for proxy mode. authHeader, when non-empty, is sent as the Authorization
|
||||
// request header (OCPP security profile 1 Basic auth).
|
||||
func DialUpstream(ctx context.Context, url, authHeader string) (*Conn, error) {
|
||||
h := http.Header{}
|
||||
if authHeader != "" {
|
||||
h.Set("Authorization", authHeader)
|
||||
}
|
||||
return Dial(ctx, url, []string{"ocpp1.6"}, h)
|
||||
}
|
||||
|
||||
// BasicAuthHeader builds an HTTP Basic Authorization header value.
|
||||
func BasicAuthHeader(user, pass string) string {
|
||||
return "Basic " + base64.StdEncoding.EncodeToString([]byte(user+":"+pass))
|
||||
}
|
||||
Reference in New Issue
Block a user