Add Shelly pseudo charger (#1028)
This commit is contained in:
parent
f43c204cc9
commit
3bb4990312
3 changed files with 143 additions and 2 deletions
|
|
@ -13,7 +13,7 @@ EVCC is an extensible EV Charge Controller with PV integration implemented in [G
|
|||
- simple and clean user interface
|
||||
- multiple [chargers](#charger):
|
||||
- Wallbe, Phoenix (includes ESL Walli), go-eCharger, NRGkick (direct Bluetooth or via Connect device), SimpleEVSE, EVSEWifi, KEBA/BMW, openWB, Mobile Charger Connect and any other charger using scripting
|
||||
- Smart-Home outlets: FritzDECT, Tasmota, TP-Link
|
||||
- Smart-Home outlets: FritzDECT, Shelly, Tasmota, TP-Link
|
||||
- multiple [meters](#meter): ModBus (Eastron SDM, MPM3PM, SBC ALE3 and many more), Discovergy (using HTTP plugin), SMA Sunny Home Manager and Energy Meter, KOSTAL Smart Energy Meter (KSEM, EMxx), any Sunspec-compatible inverter or home battery devices (Fronius, SMA, SolarEdge, KOSTAL, STECA, E3DC, ...), Tesla PowerWall
|
||||
- wide support of vendor-specific [vehicles](#vehicle) interfaces (remote charge, battery and preconditioning status): Audi, BMW, Ford, Hyundai, Kia, Nissan, Niu, Porsche, Renault, Seat, Skoda, Tesla, Volkswagen, Volvo and any other connected vehicle using scripting
|
||||
- [plugins](#plugins) for integrating with hardware devices and home automation: Modbus (meters and grid inverters), HTTP, MQTT, Javascript, WebSockets and shell scripts
|
||||
|
|
@ -188,6 +188,7 @@ Available charger implementations are:
|
|||
|
||||
Smart-Home outlet charger implementations:
|
||||
- `fritzdect`: Fritz!DECT 200/210 outlets
|
||||
- `shelly`: Shelly outlets
|
||||
- `tasmota`: Tasmota outlets
|
||||
- `tplink`: TP-Link HSXXX series outlets
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ const (
|
|||
taskMeter = "meter"
|
||||
taskFronius = "fronius"
|
||||
taskTasmota = "tasmota"
|
||||
taskShelly = "shelly"
|
||||
// taskTPLink = "tplink"
|
||||
)
|
||||
|
||||
|
|
@ -252,7 +253,7 @@ func init() {
|
|||
Type: tasks.Http,
|
||||
Depends: TaskHttp,
|
||||
Config: map[string]interface{}{
|
||||
"path": "//cm?cmnd=Module",
|
||||
"path": "/cm?cmnd=Module",
|
||||
"jq": ".Module",
|
||||
},
|
||||
})
|
||||
|
|
@ -277,4 +278,14 @@ func init() {
|
|||
"jq": ".version",
|
||||
},
|
||||
})
|
||||
|
||||
taskList.Add(tasks.Task{
|
||||
ID: taskShelly,
|
||||
Type: tasks.Http,
|
||||
Depends: TaskHttp,
|
||||
Config: map[string]interface{}{
|
||||
"path": "/shelly",
|
||||
"jq": ".type",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
129
internal/charger/shelly.go
Normal file
129
internal/charger/shelly.go
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
package charger
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/andig/evcc/util/request"
|
||||
)
|
||||
|
||||
// Shelly api homepage
|
||||
// https://shelly-api-docs.shelly.cloud/#common-http-api
|
||||
|
||||
// shellyRelayResponse provides the evcc shelly charger enabled information
|
||||
type shellyRelayResponse struct {
|
||||
Ison bool `json:"ison,omitempty"`
|
||||
}
|
||||
|
||||
// shellyStatusResponse provides the evcc shelly charger current power information
|
||||
type shellyStatusResponse struct {
|
||||
Meters []struct {
|
||||
Power float64 `json:"power,omitempty"`
|
||||
} `json:"meters,omitempty"`
|
||||
}
|
||||
|
||||
// Shelly charger implementation
|
||||
type Shelly struct {
|
||||
*request.Helper
|
||||
uri string
|
||||
channel int
|
||||
standbypower float64
|
||||
}
|
||||
|
||||
func init() {
|
||||
registry.Add("shelly", NewShellyFromConfig)
|
||||
}
|
||||
|
||||
// NewShellyFromConfig creates a Shelly charger from generic config
|
||||
func NewShellyFromConfig(other map[string]interface{}) (api.Charger, error) {
|
||||
cc := struct {
|
||||
URI string
|
||||
Channel int
|
||||
StandbyPower float64
|
||||
}{}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cc.URI == "" {
|
||||
return nil, errors.New("missing uri")
|
||||
}
|
||||
|
||||
return NewShelly(cc.URI, cc.Channel, cc.StandbyPower)
|
||||
}
|
||||
|
||||
// NewShelly creates Shelly charger
|
||||
func NewShelly(uri string, channel int, standbypower float64) (*Shelly, error) {
|
||||
c := &Shelly{
|
||||
Helper: request.NewHelper(util.NewLogger("shelly")),
|
||||
uri: strings.TrimRight(uri, "/"),
|
||||
channel: channel,
|
||||
standbypower: standbypower,
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Enabled implements the api.Charger interface
|
||||
func (c *Shelly) Enabled() (bool, error) {
|
||||
var resp shellyRelayResponse
|
||||
err := c.GetJSON(fmt.Sprintf("%s/relay/%d", c.uri, c.channel), &resp)
|
||||
|
||||
return resp.Ison, err
|
||||
}
|
||||
|
||||
// Enable implements the api.Charger interface
|
||||
func (c *Shelly) Enable(enable bool) error {
|
||||
var resp shellyRelayResponse
|
||||
onoff := map[bool]string{true: "on", false: "off"}
|
||||
err := c.GetJSON(fmt.Sprintf("%s/relay/%d?turn=%s", c.uri, c.channel, onoff[enable]), &resp)
|
||||
|
||||
switch {
|
||||
case err != nil:
|
||||
return err
|
||||
case enable && !resp.Ison:
|
||||
return errors.New("switchOn failed")
|
||||
case !enable && resp.Ison:
|
||||
return errors.New("switchOff failed")
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MaxCurrent implements the api.Charger interface
|
||||
func (c *Shelly) MaxCurrent(current int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Status implements the api.Charger interface
|
||||
func (c *Shelly) Status() (api.ChargeStatus, error) {
|
||||
power, err := c.CurrentPower()
|
||||
if power > 0 {
|
||||
return api.StatusC, err
|
||||
}
|
||||
return api.StatusB, err
|
||||
}
|
||||
|
||||
var _ api.Meter = (*Shelly)(nil)
|
||||
|
||||
// CurrentPower implements the api.Meter interface
|
||||
func (c *Shelly) CurrentPower() (float64, error) {
|
||||
var resp shellyStatusResponse
|
||||
err := c.GetJSON(fmt.Sprintf("%s/%s", c.uri, "status"), &resp)
|
||||
|
||||
if c.channel >= len(resp.Meters) {
|
||||
return 0, errors.New("invalid channel, power meter missing")
|
||||
}
|
||||
power := resp.Meters[c.channel].Power
|
||||
|
||||
// ignore standby power
|
||||
if power < c.standbypower {
|
||||
power = 0
|
||||
}
|
||||
|
||||
return power, err
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue