Easee: add cmd for (de)activating local ocpp (#29868)
This commit is contained in:
parent
8ce8d6d9f9
commit
603979167c
3 changed files with 254 additions and 0 deletions
|
|
@ -3,6 +3,9 @@ package easee
|
|||
// API is the Easee API endpoint
|
||||
const API = "https://api.easee.com/api"
|
||||
|
||||
// LocalOcppAPI is the Easee local OCPP configuration API endpoint
|
||||
const LocalOcppAPI = "https://api.easee.com/local-ocpp"
|
||||
|
||||
const (
|
||||
ChargeStart = "start_charging"
|
||||
ChargeStop = "stop_charging"
|
||||
|
|
|
|||
201
cmd/easee_ocpp.go
Normal file
201
cmd/easee_ocpp.go
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/evcc-io/evcc/charger/easee"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/request"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var easeeOcppCmd = &cobra.Command{
|
||||
Use: "easee-ocpp",
|
||||
Short: "Manage Easee local OCPP configuration",
|
||||
}
|
||||
|
||||
var easeeOcppEnableCmd = &cobra.Command{
|
||||
Use: "enable",
|
||||
Short: "Enable local OCPP on Easee charger",
|
||||
Run: runEaseeOcppEnable,
|
||||
}
|
||||
|
||||
var easeeOcppDisableCmd = &cobra.Command{
|
||||
Use: "disable",
|
||||
Short: "Disable local OCPP on Easee charger",
|
||||
Run: runEaseeOcppDisable,
|
||||
}
|
||||
|
||||
var easeeOcppStatusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Show Easee local OCPP configuration",
|
||||
Run: runEaseeOcppStatus,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(easeeOcppCmd)
|
||||
easeeOcppCmd.AddCommand(easeeOcppEnableCmd)
|
||||
easeeOcppCmd.AddCommand(easeeOcppDisableCmd)
|
||||
easeeOcppCmd.AddCommand(easeeOcppStatusCmd)
|
||||
|
||||
easeeOcppCmd.PersistentFlags().String("user", "", "Easee account email")
|
||||
easeeOcppCmd.PersistentFlags().String("password", "", "Easee account password")
|
||||
easeeOcppCmd.PersistentFlags().String("charger", "", "Easee charger serial number")
|
||||
|
||||
_ = easeeOcppCmd.MarkPersistentFlagRequired("user")
|
||||
_ = easeeOcppCmd.MarkPersistentFlagRequired("password")
|
||||
_ = easeeOcppCmd.MarkPersistentFlagRequired("charger")
|
||||
|
||||
easeeOcppEnableCmd.Flags().String("url", "", "OCPP Central System URL (e.g. ws://192.168.1.100:8887/)")
|
||||
_ = easeeOcppEnableCmd.MarkFlagRequired("url")
|
||||
}
|
||||
|
||||
type localOcppConnectionDetails struct {
|
||||
ConnectivityMode string `json:"connectivityMode"`
|
||||
WebsocketConnectionArgs *localOcppWebsocketArgs `json:"websocketConnectionArgs,omitempty"`
|
||||
}
|
||||
|
||||
type localOcppWebsocketArgs struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type localOcppVersionResponse struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type localOcppApplyRequest struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type localOcppStatusResponse struct {
|
||||
ConnectivityMode string `json:"connectivityMode"`
|
||||
ChargePointID string `json:"chargePointId"`
|
||||
WebsocketConnectionArgs *localOcppWebsocketArgs `json:"websocketConnectionArgs"`
|
||||
}
|
||||
|
||||
func easeeOcppClient(user, password string) (*request.Helper, error) {
|
||||
log := util.NewLogger("easee-ocpp")
|
||||
log.Redact(user, password)
|
||||
|
||||
ts, err := easee.TokenSource(log, user, password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("authentication failed: %w", err)
|
||||
}
|
||||
|
||||
client := request.NewHelper(log)
|
||||
client.Client.Transport = &oauth2.Transport{
|
||||
Source: ts,
|
||||
Base: client.Client.Transport,
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func easeeOcppApplyConfig(client *request.Helper, serial string, details localOcppConnectionDetails) error {
|
||||
uri := fmt.Sprintf("%s/v1/connection-details/%s", easee.LocalOcppAPI, serial)
|
||||
req, err := request.New(http.MethodPost, uri, request.MarshalJSON(details), request.JSONEncoding)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var version localOcppVersionResponse
|
||||
if err := client.DoJSON(req, &version); err != nil {
|
||||
return fmt.Errorf("storing config: %w", err)
|
||||
}
|
||||
|
||||
apply := localOcppApplyRequest{Version: version.Version}
|
||||
uri = fmt.Sprintf("%s/v1/connections/chargers/%s", easee.LocalOcppAPI, serial)
|
||||
req, err = request.New(http.MethodPost, uri, request.MarshalJSON(apply), request.JSONEncoding)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := client.DoBody(req); err != nil {
|
||||
return fmt.Errorf("applying config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runEaseeOcppEnable(cmd *cobra.Command, args []string) {
|
||||
user, _ := cmd.Flags().GetString("user")
|
||||
password, _ := cmd.Flags().GetString("password")
|
||||
charger, _ := cmd.Flags().GetString("charger")
|
||||
|
||||
client, err := easeeOcppClient(user, password)
|
||||
if err != nil {
|
||||
log.FATAL.Fatal(err)
|
||||
}
|
||||
|
||||
ocppURL, _ := cmd.Flags().GetString("url")
|
||||
|
||||
details := localOcppConnectionDetails{
|
||||
ConnectivityMode: "DualProtocol",
|
||||
WebsocketConnectionArgs: &localOcppWebsocketArgs{URL: ocppURL},
|
||||
}
|
||||
|
||||
if err := easeeOcppApplyConfig(client, charger, details); err != nil {
|
||||
log.FATAL.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Local OCPP enabled for charger %s\n", charger)
|
||||
fmt.Printf("OCPP Central System URL: %s\n", ocppURL)
|
||||
}
|
||||
|
||||
func runEaseeOcppDisable(cmd *cobra.Command, args []string) {
|
||||
user, _ := cmd.Flags().GetString("user")
|
||||
password, _ := cmd.Flags().GetString("password")
|
||||
charger, _ := cmd.Flags().GetString("charger")
|
||||
|
||||
client, err := easeeOcppClient(user, password)
|
||||
if err != nil {
|
||||
log.FATAL.Fatal(err)
|
||||
}
|
||||
|
||||
// GET current config, strip charger-appended serial from URL, set OcppOff
|
||||
uri := fmt.Sprintf("%s/v1/connection-details/%s", easee.LocalOcppAPI, charger)
|
||||
|
||||
var current localOcppConnectionDetails
|
||||
if err := client.GetJSON(uri, ¤t); err != nil {
|
||||
log.FATAL.Fatal(err)
|
||||
}
|
||||
|
||||
current.ConnectivityMode = "OcppOff"
|
||||
if current.WebsocketConnectionArgs != nil {
|
||||
current.WebsocketConnectionArgs.URL = strings.TrimSuffix(current.WebsocketConnectionArgs.URL, charger)
|
||||
}
|
||||
|
||||
if err := easeeOcppApplyConfig(client, charger, current); err != nil {
|
||||
log.FATAL.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Local OCPP disabled for charger %s\n", charger)
|
||||
}
|
||||
|
||||
func runEaseeOcppStatus(cmd *cobra.Command, args []string) {
|
||||
user, _ := cmd.Flags().GetString("user")
|
||||
password, _ := cmd.Flags().GetString("password")
|
||||
charger, _ := cmd.Flags().GetString("charger")
|
||||
|
||||
client, err := easeeOcppClient(user, password)
|
||||
if err != nil {
|
||||
log.FATAL.Fatal(err)
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s/v1/connection-details/%s", easee.LocalOcppAPI, charger)
|
||||
|
||||
var status localOcppStatusResponse
|
||||
if err := client.GetJSON(uri, &status); err != nil {
|
||||
log.FATAL.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Charger: %s\n", charger)
|
||||
fmt.Printf("Connectivity mode: %s\n", status.ConnectivityMode)
|
||||
fmt.Printf("Charge point ID: %s\n", status.ChargePointID)
|
||||
if status.WebsocketConnectionArgs != nil {
|
||||
fmt.Printf("OCPP URL: %s\n", status.WebsocketConnectionArgs.URL)
|
||||
}
|
||||
}
|
||||
50
templates/definition/charger/ocpp-easee.yaml
Normal file
50
templates/definition/charger/ocpp-easee.yaml
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
template: ocpp-easee
|
||||
products:
|
||||
- brand: Easee
|
||||
description:
|
||||
generic: Home (OCPP)
|
||||
- brand: Easee
|
||||
description:
|
||||
generic: Charge (OCPP)
|
||||
- brand: Easee
|
||||
description:
|
||||
generic: Charge Lite (OCPP)
|
||||
- brand: Easee
|
||||
description:
|
||||
generic: Charge Core (OCPP)
|
||||
capabilities: ["rfid"]
|
||||
requirements:
|
||||
description:
|
||||
de: |
|
||||
Voraussetzungen:
|
||||
* Firmware Version >= 328 mit aktiviertem nativem OCPP
|
||||
* Für Stromsteuerung über OCPP ist Beta-Firmware erforderlich (getestet mit v9012)
|
||||
* OCPP muss über die Easee API aktiviert werden:
|
||||
* `evcc easee-ocpp --user <email> --password <passwort> --charger <seriennummer> enable --url ws://<evcc-host>:8887/`
|
||||
* Zum Deaktivieren: `evcc easee-ocpp ... disable`
|
||||
* Status prüfen: `evcc easee-ocpp ... status`
|
||||
* Die Seriennummer des Chargers wird automatisch als Station-ID verwendet
|
||||
* Protokoll: OCPP-J v1.6, Websocket, ws://
|
||||
* Verbindung über das lokale Netzwerk
|
||||
* Minimales Meter-Intervall: 30s
|
||||
en: |
|
||||
Requirements:
|
||||
* Firmware version >= 328 with native OCPP enabled
|
||||
* Beta firmware required for current control via OCPP (tested with v9012)
|
||||
* OCPP must be enabled via the Easee API:
|
||||
* `evcc easee-ocpp --user <email> --password <password> --charger <serial> enable --url ws://<evcc-host>:8887/`
|
||||
* To disable: `evcc easee-ocpp ... disable`
|
||||
* Check status: `evcc easee-ocpp ... status`
|
||||
* The charger serial number is automatically used as station ID
|
||||
* Protocol: OCPP-J v1.6, Websocket, ws://
|
||||
* Local network connection
|
||||
* Minimum meter interval: 30s
|
||||
evcc: ["sponsorship", "skiptest"]
|
||||
params:
|
||||
- preset: ocpp
|
||||
- name: metervalues
|
||||
default: Energy.Active.Import.Register,Power.Active.Import,Current.Import
|
||||
render: |
|
||||
{{ include "ocpp" . }}
|
||||
remotestart: true
|
||||
meterinterval: 30s
|
||||
Loading…
Add table
Add a link
Reference in a new issue