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:
1 parent
367113f538
commit
a1519f6e89
22 files changed
+2850
-11
No files matched your search
@@ -0,0 +1,79 @@
|
||||
package ocpp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestAcceptKey(t *testing.T) {
|
||||
// RFC 6455 §1.3 worked example.
|
||||
if got := acceptKey("dGhlIHNhbXBsZSBub25jZQ=="); got != "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" {
|
||||
t.Fatalf("acceptKey = %q, want s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", got)
|
||||
}
|
||||
}
|
||||
|
||||
// echoServer upgrades and echoes every message back, exercising the framing +
|
||||
// masking round trip in both directions across a range of payload sizes.
|
||||
func TestFramingRoundTrip(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := Upgrade(w, r)
|
||||
if err != nil {
|
||||
t.Errorf("upgrade: %v", err)
|
||||
return
|
||||
}
|
||||
for {
|
||||
msg, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err := conn.WriteMessage(msg); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
conn, err := Dial(ctx, wsURL(srv.URL, "/"), []string{"ocpp1.6"}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("dial: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Small (<126), medium (2-byte length), large (8-byte length) — all masked
|
||||
// client→server, unmasked on the way back.
|
||||
for _, size := range []int{5, 200, 70000} {
|
||||
want := bytes.Repeat([]byte("x"), size)
|
||||
if err := conn.WriteMessage(want); err != nil {
|
||||
t.Fatalf("write size %d: %v", size, err)
|
||||
}
|
||||
got, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
t.Fatalf("read size %d: %v", size, err)
|
||||
}
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Fatalf("size %d: echoed %d bytes, want %d", size, len(got), len(want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpgradeRejectsNonWebSocket(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/ocpp/CP1", nil)
|
||||
if _, err := Upgrade(rec, req); err == nil {
|
||||
t.Fatal("expected error upgrading a plain GET")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialRejectsBadScheme(t *testing.T) {
|
||||
if _, err := Dial(context.Background(), "ftp://example/x", nil, nil); err == nil ||
|
||||
!strings.Contains(err.Error(), "unsupported scheme") {
|
||||
t.Fatalf("want unsupported scheme error, got %v", err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user