Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SimpleEVSE-Wifi charger #28

Merged
merged 1 commit into from
Apr 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 16 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ EVCC is an extensible EV Charge Controller with PV integration implemented in [G

- simple and clean user interface
- multiple [chargers](#charger): Wallbe (tested with Wallbe Eco S), Phoenix controllers (similar to Wallbe), any other charger using scripting
- more chargers experimentally supported: NRGKick, Go-E, SimpleEVSE
- more chargers experimentally supported: NRGKick, Go-E, SimpleEVSE, EVSEWifi
- different [vehicles](#vehicle) to show battery status: Audi (eTron), BMW (i3), Tesla, Nissan (Leaf), any other vehicle using scripting
- integration with home automation - supports shell scripts and MQTT
- status notifications using [Telegram](https://telegram.org) and [PushOver](https://pushover.net)
Expand Down Expand Up @@ -39,26 +39,6 @@ Charger and meters **must** be installed by a certified professional.

## Installation

### Hardware

#### Wallbe and Phoenix Chargers

Wallbe chargers are supported out of the box. The Wallbe must be connected using Ethernet. If not configured, the default address `192.168.0.8:502` is used.

To allow controlling charge start/stop, the Wallbe physical configuration must be modified. This requires opening the Wallbe. Once opened, DIP 10 must be set to ON:

![dip10](docs/dip10.jpeg)

More information on interacting with Wallbe chargers can be found at [GoingElectric](https://www.goingelectric.de/forum/viewtopic.php?p=1212583). Use with care.

**NOTE:** Opening the wall box **must** only be done by certified professionals. The box **must** be disconnected from mains before opening.

#### NRGKick

NRGKick is supported with additional NRGConnect for interfacing.

### Software

EVCC is provided as binary executable file and docker image. Download the file for your platform and then execute like this:

evcc -h
Expand Down Expand Up @@ -129,13 +109,26 @@ Optionally, charger can also provide:

Available charger implementations are:

- `wallbe`: Wallbe Eco chargers
- `wallbe`: Wallbe Eco chargers (see [Hardware Preparation](#Wallbe-Hardware-Preparation) for preparing the Wallbe)
- `phoenix`: chargers with Phoenix controllers
- `simpleevse`: chargers with SimpleEVSE controllers (e.g. OpenWB)
- `simpleevse`: chargers with SimpleEVSE controllers connected via ModBus (e.g. OpenWB)
- `evsewifi`: chargers with SimpleEVSE controllers using [SimpleEVSE-Wifi](https://github.com/CurtRod/SimpleEVSE-WiFi)
- `nrgkick`: NRGKick chargers with Connect module
- `go-e`: go-eCharger chargers
- `default`: default charger implementation using configurable [plugins](#plugins) for integrating any type of charger

#### Wallbe Hardware Preparation

Wallbe chargers are supported out of the box. The Wallbe must be connected using Ethernet. If not configured, the default address `192.168.0.8:502` is used.

To allow controlling charge start/stop, the Wallbe physical configuration must be modified. This requires opening the Wallbe. Once opened, DIP 10 must be set to ON:

![dip10](docs/dip10.jpeg)

More information on interacting with Wallbe chargers can be found at [GoingElectric](https://www.goingelectric.de/forum/viewtopic.php?p=1212583). Use with care.

**NOTE:** Opening the wall box **must** only be done by certified professionals. The box **must** be disconnected from mains before opening.

### Meter

Meters provide data about power and energy consumption:
Expand Down
2 changes: 2 additions & 0 deletions charger/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ func NewFromConfig(log *api.Logger, typ string, other map[string]interface{}) ap
c = NewNRGKickFromConfig(log, other)
case "go-e", "goe":
c = NewGoEFromConfig(log, other)
case "evsewifi":
c = NewEVSEWifiFromConfig(log, other)
case "simpleevse", "evse":
c = NewSimpleEVSEFromConfig(log, other)
default:
Expand Down
137 changes: 137 additions & 0 deletions charger/evsewifi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package charger

import (
"errors"
"fmt"
"strings"

"github.com/andig/evcc/api"
)

const (
evseGetParameters apiFunction = "getParameters"
evseSetStatus apiFunction = "setStatus"
evseSetCurrent apiFunction = "setCurrent"

evseSuccess = "S0_EVSE"
)

// EVSEParameterResponse is the getParameters response
type EVSEParameterResponse struct {
Type string `json:"type"`
List []EVSEListEntry `json:"list"`
}

// EVSEListEntry is EVSEParameterResponse.List
type EVSEListEntry struct {
VehicleState int64 `json:"vehicleState"`
EvseState bool `json:"evseState"`
MaxCurrent int64 `json:"maxCurrent"`
ActualCurrent int64 `json:"actualCurrent"`
ActualPower float64 `json:"actualPower"`
Duration int64 `json:"duration"`
AlwaysActive bool `json:"alwaysActive"`
LastActionUser string `json:"lastActionUser"`
LastActionUID string `json:"lastActionUID"`
Energy float64 `json:"energy"`
Mileage float64 `json:"mileage"`
MeterReading float64 `json:"meterReading"`
CurrentP1 float64 `json:"currentP1"`
CurrentP2 float64 `json:"currentP2"`
CurrentP3 float64 `json:"currentP3"`
}

// EVSEWifi charger implementation
type EVSEWifi struct {
*api.HTTPHelper
uri string
}

// NewEVSEWifiFromConfig creates a EVSEWifi charger from generic config
func NewEVSEWifiFromConfig(log *api.Logger, other map[string]interface{}) api.Charger {
cc := struct{ URI string }{}
api.DecodeOther(log, other, &cc)

return NewEVSEWifi(cc.URI)
}

// NewEVSEWifi creates EVSEWifi charger
func NewEVSEWifi(uri string) api.Charger {
evse := &EVSEWifi{
HTTPHelper: api.NewHTTPHelper(api.NewLogger("wifi")),
uri: strings.TrimRight(uri, "/") + "/",
}

evse.HTTPHelper.Log.WARN.Println("-- experimental --")

return evse
}

func (evse *EVSEWifi) apiURL(service apiFunction) string {
return fmt.Sprintf("%s/%s", evse.uri, service)
}

// Status implements the Charger.Status interface
func (evse *EVSEWifi) Status() (api.ChargeStatus, error) {
var pr EVSEParameterResponse
url := evse.apiURL(evseGetParameters)
body, err := evse.GetJSON(url, &pr)
if err != nil {
return api.StatusNone, err
}

if len(pr.List) != 1 {
return api.StatusNone, fmt.Errorf("unexpected response: %s", string(body))
}

switch pr.List[0].VehicleState {
case 1: // ready
return api.StatusA, nil
case 2: // EV is present
return api.StatusB, nil
case 3: // charging
return api.StatusC, nil
case 4: // charging with ventilation
return api.StatusD, nil
case 5: // failure (e.g. diode check, RCD failure)
return api.StatusE, nil
default:
return api.StatusNone, errors.New("invalid response")
}
}

// Enabled implements the Charger.Enabled interface
func (evse *EVSEWifi) Enabled() (bool, error) {
var pr EVSEParameterResponse
url := evse.apiURL(evseGetParameters)
body, err := evse.GetJSON(url, &pr)
if err != nil {
return false, err
}

if len(pr.List) != 1 {
return false, fmt.Errorf("unexpected response: %s", string(body))
}

return pr.List[0].EvseState, nil
}

// checkError checks for EVSE error response with HTTP 200 status
func (evse *EVSEWifi) checkError(b []byte, err error) error {
if err == nil && !strings.HasPrefix(string(b), evseSuccess) {
err = errors.New(string(b))
}
return err
}

// Enable implements the Charger.Enable interface
func (evse *EVSEWifi) Enable(enable bool) error {
url := fmt.Sprintf("%s?active=%v", evse.apiURL(evseSetStatus), enable)
return evse.checkError(evse.Get(url))
}

// MaxCurrent implements the Charger.MaxCurrent interface
func (evse *EVSEWifi) MaxCurrent(current int64) error {
url := fmt.Sprintf("%s?current=%d", evse.apiURL(evseSetCurrent), current)
return evse.checkError(evse.Get(url))
}
2 changes: 1 addition & 1 deletion charger/simpleevse.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func NewSimpleEVSE(conn, device string) api.Charger {
handler.(*modbus.RTUClientHandler).SlaveID = 1
}
if handler == nil {
log.FATAL.Fatal("most define either uri or device")
log.FATAL.Fatal("must define either uri or device")
}

evse := &SimpleEVSE{
Expand Down
24 changes: 22 additions & 2 deletions evcc.dist.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,28 @@ meters:

chargers:
- name: wallbe
type: wallbe # Wallbe charger using Phoenix Contact controller
type: wallbe # Wallbe charger
uri: 192.168.0.8:502 # ModBus address
- name: phoenix
type: phoenix # Charger with Phoenix Contact controller
uri: 192.168.0.8:502 # ModBus address
- name: simpleevse-tcp
type: simpleevse # Charger with Phoenix Contact controller
uri: 192.168.0.8:502 # TCP ModBus address
- name: simpleevse-rtu
type: simpleevse # Charger with Phoenix Contact controller
device: /dev/usb1 # RS485 ModBus device
- name: evsewifi
type: evsewifi # Charger with Phoenix Contact controller
uri: http://192.168.1.4 # SimpleEVSE-Wifi address
- name: nrg
type: nrg # NRGKick charger
ip: 192.168.1.4 # IP
macaddress: 00:99:22 # MAC address
password: # password
- name: go-e
type: go-e # go-e charger
uri: http://192.168.1.4 # go-e address

vehicles:
- name: default
Expand Down Expand Up @@ -110,7 +130,7 @@ vehicles:
# cache: 5m

loadpoints:
- name: lp1 # name for logging
- name: main # name for logging
vehicle: audi
charger: wallbe # charger
gridmeter: grid # grid meter
Expand Down