Add OpenEVSE chargers (#4462)

This commit is contained in:
sparkyson 2022-09-18 18:53:16 +03:00 • committed by GitHub
parent 1239ec1255
commit 8b88693d40
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 5026 additions and 1 deletions

259
charger/openevse.go Normal file
View file

@ -0,0 +1,259 @@
package charger
import (
"context"
"errors"
"fmt"
"net/url"
"strings"
"time"
"github.com/deepmap/oapi-codegen/pkg/securityprovider"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/charger/openevse"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
)
// OpenEVSE charger implementation
type OpenEVSE struct {
uri string
api *openevse.ClientWithResponses
helper *request.Helper
timeout time.Duration
}
func init() {
registry.Add("openevse", NewOpenEVSEFromConfig)
}
// go:generate go run ../cmd/tools/decorate.go -f decorateOpenEVSE -b ""*OpenEVSE" -r api.Charger -t "api.PhaseSwitcher,Phases1p3p,func(int) error"
// NewOpenEVSEFromConfig creates a go-e charger from generic config
func NewOpenEVSEFromConfig(other map[string]interface{}) (api.Charger, error) {
cc := struct {
URI string
User string
Password string
Timeout time.Duration
}{
Timeout: request.Timeout,
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
if cc.URI == "" {
return nil, errors.New("missing uri")
}
return NewOpenEVSE(cc.URI, cc.User, cc.Password, cc.Timeout)
}
// NewOpenEVSE creates OpenEVSE charger
func NewOpenEVSE(uri, user, password string, timeout time.Duration) (api.Charger, error) {
log := util.NewLogger("openevse").Redact(user, password)
c := &OpenEVSE{
helper: request.NewHelper(log),
uri: uri,
timeout: timeout,
}
options := []openevse.ClientOption{openevse.WithHTTPClient(c.helper.Client)}
if user != "" && password != "" {
basicAuthProvider, err := securityprovider.NewSecurityProviderBasicAuth(user, password)
if err != nil {
return c, err
}
options = append(options, openevse.WithRequestEditorFn(basicAuthProvider.Intercept))
}
var err error
c.api, err = openevse.NewClientWithResponses(uri, options...)
if err != nil {
return c, err
}
var phases1p3p func(int) error
if err := c.hasPhaseSwitchCapabilities(); err == nil {
phases1p3p = c.phases1p3p
// disable EVSE's own 1/3-phase auto-switching
if err := c.rapiCommand("$S8 0"); err != nil {
return c, err
}
}
return decorateOpenEVSE(c, phases1p3p), err
}
func (c *OpenEVSE) requestContextWithTimeout() (context.Context, context.CancelFunc) {
ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
return ctx, cancel
}
func (c *OpenEVSE) hasPhaseSwitchCapabilities() error {
return c.rapiCommand("$G7")
}
func (c *OpenEVSE) rapiCommand(command string) error {
var res struct {
Cmd, Ret string
}
uri := fmt.Sprintf("%s/r?json=1&rapi=%s", c.uri, url.QueryEscape(command))
err := c.helper.GetJSON(uri, &res)
if err == nil && !strings.HasPrefix(res.Ret, "$OK") {
err = fmt.Errorf("rapi command failed: %s", res.Ret)
}
return err
}
func (c *OpenEVSE) SetManualOverride(enable bool) error {
state := "disabled"
if enable {
state = "active"
}
data := openevse.SetManualOverrideJSONRequestBody{
State: &state,
}
ctx, cancel := c.requestContextWithTimeout()
defer cancel()
_, err := c.api.SetManualOverrideWithResponse(ctx, data)
return err
}
func (c *OpenEVSE) Status() (api.ChargeStatus, error) {
ctx, cancel := c.requestContextWithTimeout()
defer cancel()
res, err := c.api.GetStatusWithResponse(ctx)
if err != nil {
return api.StatusNone, err
}
/*
0: "unknown",
1: "not connected",
2: "connected",
3: "charging",
4: "vent required",
5: "diode check failed",
6: "gfci fault",
7: "no ground",
8: "stuck relay",
9: "gfci self-test failure",
10: "over temperature",
11: "over current",
254: "sleeping",
255: "disabled"
*/
switch state := *res.JSON200.State; state {
case 1:
return api.StatusA, nil
case 2, 254, 255:
if connected := *res.JSON200.Vehicle != 0; connected {
return api.StatusB, nil
}
return api.StatusA, nil
case 3:
return api.StatusC, nil
case 4:
return api.StatusD, nil
case 5, 6, 7, 8, 9, 10, 11:
return api.StatusF, nil
default:
return api.StatusNone, fmt.Errorf("unknown EVSE state: %d", state)
}
}
// Enabled implements the api.Charger interface
func (c *OpenEVSE) Enabled() (bool, error) {
ctx, cancel := c.requestContextWithTimeout()
defer cancel()
overrideResp, err := c.api.GetManualOverrideWithResponse(ctx)
if err != nil {
return false, err
}
if overrideResp.JSON200 != nil && overrideResp.JSON200.State != nil {
switch *overrideResp.JSON200.State {
case "disabled":
return false, nil
case "enabled", "active":
return true, nil
}
}
return false, fmt.Errorf("unknown EVSE state: %s", *overrideResp.JSON200.State)
}
// Enable implements the api.Charger interface
func (c *OpenEVSE) Enable(enable bool) error {
return c.SetManualOverride(enable)
}
// MaxCurrent implements the api.Charger interface
func (c *OpenEVSE) MaxCurrent(current int64) error {
cur := int(current)
data := openevse.SetManualOverrideJSONRequestBody{
ChargeCurrent: &cur,
}
ctx, cancel := c.requestContextWithTimeout()
defer cancel()
_, err := c.api.SetManualOverrideWithResponse(ctx, data)
return err
}
var _ api.ChargeRater = (*OpenEVSE)(nil)
// ChargedEnergy implements the api.ChargeRater interface
func (c *OpenEVSE) ChargedEnergy() (float64, error) {
ctx, cancel := c.requestContextWithTimeout()
defer cancel()
resp, err := c.api.GetStatusWithResponse(ctx)
if err != nil {
return 0, err
}
return float64(*resp.JSON200.Wattsec) / 3600 / 1e3, nil
}
var _ api.MeterEnergy = (*OpenEVSE)(nil)
// TotalEnergy implements the api.MeterEnergy interface
func (c *OpenEVSE) TotalEnergy() (float64, error) {
ctx, cancel := c.requestContextWithTimeout()
defer cancel()
resp, err := c.api.GetStatusWithResponse(ctx)
if err != nil {
return 0, err
}
return float64(*resp.JSON200.Watthour) / 1e3, nil
}
// phases1p3p implements the api.ChargePhases interface
func (c *OpenEVSE) phases1p3p(phases int) error {
var set3p int
if phases == 3 {
set3p = 1
}
return c.rapiCommand(fmt.Sprintf("$S7 %d", set3p))
}

3490
charger/openevse/api.go Normal file

File diff suppressed because it is too large Load diff

644
charger/openevse/api.yaml Normal file
View file

@ -0,0 +1,644 @@
openapi: 3.0.2
info:
title: OpenEVSE WiFi API
description: |
The API for the OpenEVSE WiFi module
version: "1.0"
contact:
email: jeremy@bigjungle.net
name: Jeremy Poulter
license:
name: Apache 2.0
url: "http://www.apache.org/licenses/LICENSE-2.0.html"
servers:
- url: "{server}"
variables:
server:
default: "http://openevse.local"
- url: "http://localhost:8080"
paths:
/status:
get:
operationId: getStatus
summary: Get the EVSE status
description: |
The status endpoint can be called to get the status of the EVSE.
While it is posible to poll this endpoint, the recomendatoin is to retrieve the initial
state then use the [/ws](#statusUpdates)
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: ./models/Status.yaml
examples:
EVSE Status:
value:
mode: STA
wifi_client_connected: 1
eth_connected: 0
net_connected: 1
srssi: -73
ipaddress: 192.168.1.43
emoncms_connected: 1
packets_sent: 22307
packets_success: 22290
mqtt_connected: 1
ohm_hour: NotConnected
free_heap: 203268
comm_sent: 335139
comm_success: 335139
rapi_connected: 1
amp: 0
pilot: 32
temp1: 282
temp2: -2560
temp3: -2560
state: 254
elapsed: 3473
wattsec: 22493407
watthour: 51536
gfcicount: 0
nogndcount: 0
stuckcount: 0
divertmode: 1
solar: 390
grid_ie: 0
charge_rate: 7
divert_update: 0
ota_update: 0
time: "2020-05-12T17:53:48Z"
offset: "+0000"
tags:
- Status
/ws:
get:
operationId: statusUpdates
summary: EVSE status updates
description: |
As the state changes on the EVSE a websocket frame will be sent over the socket.
The frame will contain a JSON document conntaining only the updated state.
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: ./models/Status.yaml
tags:
- Status
/config:
get:
operationId: getConfig
summary: Get the EVSE configuration
description: ""
tags:
- Config
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: ./models/Config.yaml
examples: {}
post:
operationId: updateConfig
responses:
"200":
$ref: "#/components/responses/UpdateSuccessful"
"400":
$ref: "#/components/responses/BadRequest"
requestBody:
content:
application/json:
schema:
$ref: ./models/Config.yaml
examples:
Update EmonCMS setting:
value:
emoncms_enabled: true
emoncms_server: "http://emoncms.org"
emoncms_node: openevse-a7d4
emoncms_apikey: emoncmskey
emoncms_fingerprint: ""
Configure Solar divert:
value:
divert_enabled: true
divert_attack_smoothing_factor: 0.4
divert_decay_smoothing_factor: 0.05
divert_min_charge_time: 60
Set normal charging mode:
value:
charge_mode: fast
Set Eco mode:
value:
charge_mode: eco
Configure Tesla credentials:
value:
tesla_enabled: true
tesla_access_token: qts-1234.......
tesla_refresh_token: abcd.......
tesla_created_at: 1623358948
tesla_expires_in: 3888000
Select Tesla vehicle:
value:
tesla_vehicle_id: "2238974987235987"
summary: Update the EVSE configuration
tags:
- Config
/override:
description: Manual override can be used to override a charging timer or to immediately start a charge if the EVSE is in sleeping state.
get:
responses:
"200":
description: Manual overide is active
content:
application/json:
schema:
$ref: ./models/Properties.yaml
"404":
$ref: "#/components/responses/NotFound"
operationId: getManualOverride
tags:
- Manual Override
summary: Get the Manual Override status
description: Returns the current state of the manual override.
post:
requestBody:
content:
application/json:
schema:
$ref: ./models/Properties.yaml
examples:
Force stop of charging:
value:
state: disabled
example-1:
value:
state: active
charge_current: 0
max_current: 0
energy_limit: 0
time_limit: 0
auto_release: true
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/Message"
operationId: setManualOverride
tags:
- Manual Override
summary: Set the manual override
patch:
description: Toggle the manual override
responses:
"200":
description: OK
operationId: toggleManualOverride
tags:
- Manual Override
summary: Toggle the manual Override
delete:
description: Clears the manual override
responses:
"200":
description: OK
operationId: clearManualOverride
tags:
- Manual Override
summary: Clear the manual override
/claims:
get:
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: ./models/Claim.yaml
operationId: listClaims
tags:
- Claims
summary: List EVSE claims
"/claims/{client}":
get:
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: ./models/Properties.yaml
"404":
$ref: "#/components/responses/NotFound"
parameters: []
operationId: getClaim
tags:
- Claims
summary: Get EVSE claim information
post:
description: Make/Update a claim
requestBody:
content:
application/json:
schema:
$ref: ./models/Properties.yaml
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/Message"
"400":
$ref: "#/components/responses/BadRequest"
operationId: setClaim
summary: Make/Update an EVSE claim
tags:
- Claims
delete:
description: Release a claim
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/Message"
"404":
$ref: "#/components/responses/NotFound"
operationId: releaseClaim
summary: Release an EVSE claim
tags:
- Claims
parameters:
- schema:
type: integer
name: client
in: path
required: true
description: "The client ID, defined by [OpenEVSE](https://github.com/OpenEVSE/ESP32_WiFi_V4.x/blob/master/src/evse_man.h#L25)"
/schedule:
get:
summary: List schedule events
description: This will get a complete list of events in the schedule
responses:
"200":
description: List of the schedule events
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/ScheduleEvent"
"400":
$ref: "#/components/responses/BadRequest"
operationId: listSchedule
tags:
- Schedule
post:
operationId: updateSchedule
description: Perform multiple event updates
responses:
"201":
description: item created
"400":
description: "invalid input, object invalid"
"409":
description: an existing item already exists
requestBody:
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/ScheduleEvent"
examples:
Single event:
value:
- id: 1
state: active
time: "14:15:22Z"
days:
- monday
Daily Timer:
value:
- id: 1
state: active
days:
- monday
- tuesday
- wednesday
- thursday
- friday
- saturday
- sunday
time: "07:00:00"
- id: 2
state: disabled
days:
- monday
- tuesday
- wednesday
- thursday
- friday
- saturday
- sunday
time: "10:00:00"
description: Inventory item to add
tags:
- Schedule
summary: Batch update schedule
"/schedule/{id}":
parameters:
- in: path
name: id
required: true
schema:
type: integer
description: Numeric ID of the event
get:
summary: Get event details
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: ./models/EvseState.yaml
operationId: getScheduleEvent
tags:
- Schedule
post:
responses:
"200":
$ref: "#/components/responses/UpdateSuccessful"
requestBody:
content:
application/json:
schema:
$ref: ./models/EvseState.yaml
description: ""
operationId: setScheduleEvent
tags:
- Schedule
summary: Update event details
delete:
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/Message"
examples: {}
operationId: removeScheduleEvent
tags:
- Schedule
summary: Remove event
/logs:
get:
summary: Get event block information
tags:
- Event Log
responses:
"200":
description: OK
content:
application/json:
schema:
type: object
properties:
min:
type: integer
max:
type: integer
operationId: getEventBlockInfo
description: "Retrieve the start and end indexes of the log event blocks. Each log event block contains "
parameters: []
parameters: []
"/logs/{index}":
get:
summary: Get log block events
tags:
- Event Log
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/LogEvent"
"404":
$ref: "#/components/responses/NotFound"
operationId: getEventBlock
description: ""
parameters:
- schema:
type: integer
name: index
in: path
required: true
/tesla/vehicles:
get:
summary: Get Tesla vehicle list
tags:
- Tesla
responses:
"200":
description: OK
content:
application/json:
schema:
description: ""
type: object
x-examples:
example-1:
count: 3
vehicles:
- id: "1492932278133304"
name: AwesomeSAUCE
- id: "1492931610165415"
name: Herbie
- id: "1492930973960452"
name: "Life, the Universe, and Everything"
properties:
count:
type: number
vehicles:
type: array
uniqueItems: true
items:
type: object
additionalProperties: false
properties:
id:
type: string
minLength: 1
name:
type: string
minLength: 1
required:
- id
- name
required:
- count
- vehicles
examples:
example-1:
value:
count: 1
vehicles:
- id: "2238974987235987"
name: My Tesla
operationId: get-tesla-vehicles
description: Once the Tesla account credentials have been configured via the `/config` API this endpoint will return a list of vehicles associated with the account. The ID of the vehicle to use for SoC/Range can then be set using `tesla_vehicle_id` on the `/config` endpoint.
components:
schemas:
Message:
title: Informational message
type: object
properties:
msg:
type: string
description: The message
ScheduleEvent:
type: object
properties:
id:
type: integer
example: 1
state:
$ref: ./models/EvseState.yaml
time:
type: string
format: time
days:
type: array
uniqueItems: true
items:
type: string
enum:
- monday
- tuesday
- wednesday
- thursday
- friday
- saturday
- sunday
required:
- state
- time
- days
title: ""
LogEvent:
title: LogEvent
type: object
x-examples:
example-1:
value:
time: "2021-08-24T14:15:22Z"
type: information
managerState: active
evseState: 1
evseFlags: 123
pilot: 32
energy: 1234.5
elapsed: 123456
temperature: 35.4
tempuratureMax: 50.4
divertMode: 1
properties:
time:
type: string
format: date-time
readOnly: true
type:
type: string
enum:
- information
- notification
- warning
managerState:
$ref: ./models/EvseState.yaml
evseState:
type: integer
minimum: 0
maximum: 255
evseFlags:
type: integer
format: int32
pilot:
type: integer
format: int32
minimum: 0
energy:
type: number
format: double
minimum: 0
elapsed:
type: integer
format: int32
minimum: 0
temperature:
type: number
format: double
tempuratureMax:
type: number
format: double
divertMode:
type: integer
format: int32
minimum: 1
maximum: 2
exclusiveMaximum: false
responses:
UpdateSuccessful:
description: Update successful
content:
application/json:
schema:
$ref: "#/components/schemas/Message"
examples:
Success message:
value:
msg: done
NotFound:
description: Example response
content:
application/json:
schema:
$ref: "#/components/schemas/Message"
examples:
Not found:
value:
msg: Not found
BadRequest:
description: Example response
content:
application/json:
schema:
$ref: "#/components/schemas/Message"
examples:
Bad JSON:
value:
msg: Could not parse JSON
parameters: {}
tags:
- name: Claims
- name: Config
- name: Event Log
- name: Manual Override
- name: Schedule
- name: Status
- name: Tesla

View file

@ -0,0 +1,3 @@
package openevse
// go:generate sh -c "oapi-codegen -package openevse -old-config-style -generate types,client api.yaml > api.go"

View file

@ -0,0 +1,13 @@
title: EVSE claim
allOf:
- type: object
properties:
client:
type: integer
description: "The client ID, defined by [OpenEVSE](src/evse_man.h#L25)"
priority:
type: integer
description: "The priority of the claim, the higher the number the higher the priority"
- $ref: ./Properties.yaml
x-tags:
- Claims

View file

@ -0,0 +1,352 @@
description: ""
type: object
x-examples:
example-1:
firmware: D7.1.2
protocol: "-"
espflash: 4194304
espinfo: ESP32r1 2 core WiFi BLE BT
buildenv: openevse_esp-wrover-kit
version: 4.1.0.dev
diode_check: true
gfci_check: true
ground_check: true
relay_check: true
vent_check: true
temp_check: true
service: 0
scale: 220
offset: 0
max_current_soft: 32
min_current_hard: 6
max_current_hard: 32
mqtt_supported_protocols:
- mqtt
- mqtts
http_supported_protocols:
- http
- https
ssid: wibble_ext
pass: _DUMMY_PASSWORD
www_username: ""
www_password: ""
hostname: openevse-a7d4
sntp_hostname: pool.ntp.org
time_zone: "Europe/London|GMT0BST,M3.5.0/1,M10.5.0"
emoncms_server: "https://emoncms.org"
emoncms_node: openevse-a7d4
emoncms_apikey: _DUMMY_PASSWORD
emoncms_fingerprint: ""
mqtt_server: homeassistant.lan
mqtt_port: 1883
mqtt_topic: openevse
mqtt_user: emonpi
mqtt_pass: _DUMMY_PASSWORD
mqtt_solar: ""
mqtt_grid_ie: emon/test/grid_ie
mqtt_vrms: emon/rightbar/voltage
mqtt_vehicle_soc: ""
mqtt_vehicle_range: ""
mqtt_vehicle_eta: ""
mqtt_announce_topic: openevse/announce/a7d4
ocpp_server: ""
ocpp_chargeBoxId: ""
ocpp_idTag: ""
tx_start_point: tx_pending
ohm: ""
divert_PV_ratio: 1.1
divert_attack_smoothing_factor: 0.4
divert_decay_smoothing_factor: 0.05
divert_min_charge_time: 10
tesla_access_token: _DUMMY_PASSWORD
tesla_refresh_token: _DUMMY_PASSWORD
tesla_created_at: 2479357952
tesla_expires_in: 3888000
tesla_vehicle_id: "142960157278"
led_brightness: 51
flags: 115467
emoncms_enabled: true
mqtt_enabled: true
mqtt_reject_unauthorized: true
ohm_enabled: false
sntp_enabled: true
tesla_enabled: true
divert_enabled: true
pause_uses_disabled: false
mqtt_vehicle_range_miles: false
ocpp_enabled: true
ocpp_suspend_evse: true
ocpp_energize_plug: true
mqtt_protocol: mqtt
charge_mode: fast
examples:
- firmware: D7.1.2
protocol: "-"
espflash: 4194304
espinfo: ESP32r1 2 core WiFi BLE BT
buildenv: openevse_esp-wrover-kit
version: 4.1.0.dev
diode_check: true
gfci_check: true
ground_check: true
relay_check: true
vent_check: true
temp_check: true
service: 0
scale: 220
offset: 0
max_current_soft: 32
min_current_hard: 6
max_current_hard: 32
mqtt_supported_protocols:
- mqtt
- mqtts
http_supported_protocols:
- http
- https
ssid: wibble_ext
pass: _DUMMY_PASSWORD
www_username: ""
www_password: ""
hostname: openevse-a7d4
sntp_hostname: pool.ntp.org
time_zone: "Europe/London|GMT0BST,M3.5.0/1,M10.5.0"
emoncms_server: "https://emoncms.org"
emoncms_node: openevse-a7d4
emoncms_apikey: _DUMMY_PASSWORD
emoncms_fingerprint: ""
mqtt_server: homeassistant.lan
mqtt_port: 1883
mqtt_topic: openevse
mqtt_user: emonpi
mqtt_pass: _DUMMY_PASSWORD
mqtt_solar: ""
mqtt_grid_ie: emon/test/grid_ie
mqtt_vrms: emon/rightbar/voltage
mqtt_vehicle_soc: ""
mqtt_vehicle_range: ""
mqtt_vehicle_eta: ""
mqtt_announce_topic: openevse/announce/a7d4
ocpp_server: ""
ocpp_chargeBoxId: ""
ocpp_idTag: ""
tx_start_point: tx_pending
ohm: ""
divert_PV_ratio: 1.1
divert_attack_smoothing_factor: 0.4
divert_decay_smoothing_factor: 0.05
divert_min_charge_time: 10
tesla_access_token: _DUMMY_PASSWORD
tesla_refresh_token: _DUMMY_PASSWORD
tesla_created_at: 2479357952
tesla_expires_in: 3888000
tesla_vehicle_id: "142960157278"
led_brightness: 51
flags: 115467
emoncms_enabled: true
mqtt_enabled: true
mqtt_reject_unauthorized: true
ohm_enabled: false
sntp_enabled: true
tesla_enabled: true
divert_enabled: true
pause_uses_disabled: false
mqtt_vehicle_range_miles: false
ocpp_enabled: true
ocpp_suspend_evse: true
ocpp_energize_plug: true
mqtt_protocol: mqtt
charge_mode: fast
x-tags:
- Config
properties:
firmware:
type: string
minLength: 1
readOnly: true
protocol:
type: string
minLength: 1
readOnly: true
espflash:
type: number
readOnly: true
espinfo:
type: string
minLength: 1
readOnly: true
buildenv:
type: string
minLength: 1
readOnly: true
version:
type: string
minLength: 1
readOnly: true
diode_check:
type: boolean
gfci_check:
type: boolean
ground_check:
type: boolean
relay_check:
type: boolean
vent_check:
type: boolean
temp_check:
type: boolean
service:
type: integer
minimum: 0
maximum: 2
scale:
type: integer
offset:
type: integer
max_current_soft:
type: integer
min_current_hard:
type: integer
readOnly: true
max_current_hard:
type: integer
readOnly: true
mqtt_supported_protocols:
type: array
uniqueItems: true
items:
type: string
readOnly: true
http_supported_protocols:
type: array
uniqueItems: true
items:
type: string
readOnly: true
ssid:
type: string
minLength: 1
pass:
type: string
minLength: 1
www_username:
type: string
www_password:
type: string
hostname:
type: string
minLength: 1
sntp_hostname:
type: string
minLength: 1
time_zone:
type: string
minLength: 1
emoncms_server:
type: string
minLength: 1
emoncms_node:
type: string
minLength: 1
emoncms_apikey:
type: string
minLength: 1
emoncms_fingerprint:
type: string
mqtt_server:
type: string
minLength: 1
mqtt_port:
type: integer
mqtt_topic:
type: string
minLength: 1
mqtt_user:
type: string
minLength: 1
mqtt_pass:
type: string
minLength: 1
mqtt_solar:
type: string
mqtt_grid_ie:
type: string
minLength: 1
mqtt_vrms:
type: string
minLength: 1
mqtt_vehicle_soc:
type: string
mqtt_vehicle_range:
type: string
mqtt_vehicle_eta:
type: string
mqtt_announce_topic:
type: string
minLength: 1
ocpp_server:
type: string
ocpp_chargeBoxId:
type: string
ocpp_idTag:
type: string
tx_start_point:
type: string
minLength: 1
ohm:
type: string
divert_PV_ratio:
type: number
divert_attack_smoothing_factor:
type: number
divert_decay_smoothing_factor:
type: number
divert_min_charge_time:
type: number
tesla_access_token:
type: string
minLength: 1
tesla_refresh_token:
type: string
minLength: 1
tesla_created_at:
type: number
tesla_expires_in:
type: number
tesla_vehicle_id:
type: string
minLength: 1
led_brightness:
type: number
flags:
type: number
emoncms_enabled:
type: boolean
mqtt_enabled:
type: boolean
mqtt_reject_unauthorized:
type: boolean
ohm_enabled:
type: boolean
sntp_enabled:
type: boolean
tesla_enabled:
type: boolean
divert_enabled:
type: boolean
pause_uses_disabled:
type: boolean
mqtt_vehicle_range_miles:
type: boolean
ocpp_enabled:
type: boolean
ocpp_suspend_evse:
type: boolean
ocpp_energize_plug:
type: boolean
mqtt_protocol:
type: string
minLength: 1
charge_mode:
type: string
minLength: 1

View file

@ -0,0 +1,6 @@
title: EvseState
type: string
x-tags:
- Claims
- Status
description: Either enable charging (`active`) or block charging (`disabled`)

View file

@ -0,0 +1,39 @@
title: Properties
type: object
x-tags:
- Claims
description: ""
examples: []
properties:
state:
$ref: ./EvseState.yaml
charge_current:
type: integer
minimum: 0
description: Specify the active charge current in Amps
max_current:
type: integer
minimum: 0
description: Dynamically alter the max current while still allowing other claims to very the current via `charge_current` that can not exceede this value. This is primerialy designed for load sharing situations while still allowing things like PV divert to work so the charge current is the minimum of `charge_current` <= `max_current` <= configured soft limit <= hard coded limit
energy_limit:
type: integer
minimum: 0
format: int32
description: |-
Stop the charge after `energy_limit` Wh of energy have been transfered.
> Note: Not currently implemented
time_limit:
type: integer
minimum: 0
format: int32
description: |-
Stop the charge after the duration of the charging session has exceeded `time_limit` seconds
> Note: Not currently implemented
auto_release:
type: boolean
description: |
`true` if the manual override is auto-released when the vehicle is
disconnected, `false` if manual override will persist after vehicle
disconnection.

View file

@ -0,0 +1,136 @@
title: EVSE status
type: object
x-tags:
- Status
properties:
mode:
type: string
description: The current mode of the EVSE
wifi_client_connected:
type: integer
description: "`1`, WiFi is connected, `0` no WiFi connection"
eth_connected:
type: integer
description: "`1`, Wired ethernet is connected, `0` no wired ethernet connection"
net_connected:
type: integer
description: "`1`, network is connected, either wired or WiFi, `0` no network connection"
srssi:
type: integer
description: WiFi RSSI signal strength
ipaddress:
type: string
description: The current IP address of the WiFi module
packets_sent:
type: integer
description: The number of posts sent to the EmonCMS server
packets_success:
type: integer
description: The number of successful posts to the EmonCMS server
emoncms_connected:
type: integer
description: "`1`, if connected to an EmonCMS server, `0` not connected"
emoncms_message:
type: string
description: Last response message from the EmonCMS server
mqtt_connected:
type: integer
description: "`1`, if connected to an EmonCMS server, `0` not connected"
ohm_hour:
type: string
description: Last status message from the OhmHour connection
free_heap:
type: integer
description: Number of bytes free in the ESP memory heap
comm_sent:
type: integer
description: Number of RAPI messages sent
comm_success:
type: integer
description: Number of successful RAPI messages
rapi_connected:
type: integer
description: "`1`, if RAPI messages have successfully been sent to the OpenEVSE module, `0` not connected"
evse_connected:
type: integer
description: "`1`, if the WiFi module has negociated a connection to the OpenEVSE module, `0` not connected"
amp:
type: number
description: the value of the charge current in mA
voltage:
type: number
description: "the charge voltage, read form the EVSE if supported, or supplied via MQTT or other external source"
pilot:
type: integer
description: "the pilot value, in amps"
temp:
type: number
description: "the derived temperature, in 1/10th °C"
state:
type: integer
description: The EVSE status
vehicle:
type: integer
description: "`1`, if a vehicle id connected to the EVSE, `0` not connected"
colour:
type: integer
description: The LCD colour
manual_override:
type: integer
description: "`1`, if the manual override is active, `0` default behaviour"
elapsed:
type: number
description: "The duration of the current charge session, in seconds"
wattsec:
type: number
description: "The amount of energy transfered in this charge session, in W/sec"
watthour:
type: integer
description: "The total amount of energy transfered, in W/hour"
gfcicount:
type: integer
description: Number of GFCI errors
nogndcount:
type: integer
description: Number of no ground errors
stuckcount:
type: integer
description: Number of stuck relay errors
divertmode:
type: integer
description: "The PV divert mode, `1` normal, `2` Eco"
service_level:
type: integer
description: The current service level
minimum: 1
maximum: 2
solar:
type: integer
description: The latest solar feed value received
grid_ie:
type: integer
description: The latest grid +I/-E feed value received
charge_rate:
type: integer
description: The divert charge rate
available_current:
type: integer
description: The computed avalible current for divert
smoothed_available_current:
type: integer
description: The computed smoothed available current for divert
divert_update:
type: number
description: The time since the last update of the divert feed
divert_active:
type: boolean
description: "`true` if divert is active, `false` if not"
ota_update:
type: integer
description: "`1`, if there is an OTA update active, `0` if normal operation"
time:
type: string
description: The current time on the WiFi module
offset:
type: string
description: The current timezone

View file

@ -0,0 +1,6 @@
package openevse
type RAPIResponse struct {
Cmd string `json:"cmd"`
Ret string `json:"ret"`
}

View file

@ -0,0 +1,35 @@
package charger
// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT.
import (
"github.com/evcc-io/evcc/api"
)
func decorateOpenEVSE(base *OpenEVSE, phaseSwitcher func(phases int) error) api.Charger {
switch {
case phaseSwitcher == nil:
return base
case phaseSwitcher != nil:
return &struct {
*OpenEVSE
api.PhaseSwitcher
}{
OpenEVSE: base,
PhaseSwitcher: &decorateOpenEVSEPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
}
return nil
}
type decorateOpenEVSEPhaseSwitcherImpl struct {
phaseSwitcher func(int) error
}
func (impl *decorateOpenEVSEPhaseSwitcherImpl) Phases1p3p(phases int) error {
return impl.phaseSwitcher(phases)
}

2
go.mod
View file

@ -18,6 +18,7 @@ require (
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/containrrr/shoutrrr v0.6.1
github.com/coreos/go-oidc/v3 v3.3.0
github.com/deepmap/oapi-codegen v1.11.0
github.com/denisbrodbeck/machineid v1.0.1
github.com/dmarkham/enumer v1.5.6
github.com/dustin/go-humanize v1.0.0
@ -100,7 +101,6 @@ require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deepmap/oapi-codegen v1.11.0 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect

10
go.sum
View file

@ -244,7 +244,9 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/getkin/kin-openapi v0.94.0 h1:bAxg2vxgnHHHoeefVdmGbR+oxtJlcv5HsJJa3qmAHuo=
github.com/getkin/kin-openapi v0.94.0/go.mod h1:LWZfzOd7PRy8GJ1dJ6mCU6tNdSfOwRac1BUPam4aw6Q=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
@ -267,8 +269,10 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU=
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
@ -500,6 +504,7 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC
github.com/joeshaw/carwings v0.0.0-20220711204617-d90801cf2848 h1:mUoi+B77PKkv/pYCR7uOh5ouZdgzFAwOZ3HU8Jr1P+4=
github.com/joeshaw/carwings v0.0.0-20220711204617-d90801cf2848/go.mod h1:tB0OlpicmRVTL1Vksc5XRiYo+wkK2kl/GI7eGMIl6Rs=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpfielding/go-http-digest v0.0.0-20211006141426-fbc93758452e h1:iLmt7czLE9ophoU4xZcckVNCXp+auTvVSQ9MGTWbgBY=
github.com/jpfielding/go-http-digest v0.0.0-20211006141426-fbc93758452e/go.mod h1:oLt6zF2euTyCg2Cxz01B3VC5SJcPpKpYnOAqvO+480s=
@ -545,7 +550,9 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI=
github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
@ -575,6 +582,7 @@ github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPK
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
@ -857,7 +865,9 @@ github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=

View file

@ -0,0 +1,20 @@
template: openevse
products:
- brand: OpenEVSE
requirements:
description:
en: Requires firmware 7.0 or later.
de: Benötigt mindestens Firmware 7.0 oder neuer.
params:
- name: host
required: true
- name: user
required: false
- name: password
required: false
mask: true
render: |
type: openevse
uri: http://{{ .host }}
user: {{ .user }}
password: {{ .password }}

View file

@ -0,0 +1,11 @@
product:
brand: OpenEVSE
description: |
Benötigt mindestens Firmware 7.0 oder neuer.
render:
- default: |
type: template
template: openevse
host: 192.0.2.2 # IP-Adresse oder Hostname
user: # Benutzerkonto (bspw. E-Mail Adresse, User Id, etc.) # Optional
password: # Passwort des Benutzerkontos (bei führenden Nullen bitte in einfache Hochkommata setzen) # Optional

View file

@ -25,6 +25,7 @@
"KSE",
"Mennekes",
"NRGKick",
"OpenEVSE",
"openWB",
"Optec",
"PC Electric",