Add Shelly Pro 3 EM (#6457)

This commit is contained in:
Wilko 2023-02-27 17:48:21 +01:00 • committed by GitHub
parent 892057bb7f
commit 69a37555cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 358 additions and 174 deletions

View file

@ -10,7 +10,7 @@ import (
// Shelly charger implementation
type Shelly struct {
conn *shelly.Connection
conn *shelly.Switch
*switchSocket
}
@ -44,7 +44,7 @@ func NewShelly(embed embed, uri, user, password string, channel int, standbypowe
}
c := &Shelly{
conn: conn,
conn: shelly.NewSwitch(conn),
}
c.switchSocket = NewSwitchSocket(&embed, c.Enabled, c.conn.CurrentPower, standbypower)

View file

@ -24,5 +24,10 @@ func NewShellyFromConfig(other map[string]interface{}) (api.Meter, error) {
return nil, err
}
return shelly.NewConnection(cc.URI, cc.User, cc.Password, cc.Channel)
conn, err := shelly.NewConnection(cc.URI, cc.User, cc.Password, cc.Channel)
if err != nil {
return nil, err
}
return shelly.NewSwitch(conn), nil
}

View file

@ -78,80 +78,6 @@ func NewConnection(uri, user, password string, channel int) (*Connection, error)
return conn, nil
}
// CurrentPower implements the api.Meter interface
func (d *Connection) CurrentPower() (float64, error) {
var power float64
switch d.gen {
case 0, 1:
var res Gen1StatusResponse
uri := fmt.Sprintf("%s/status", d.uri)
if err := d.GetJSON(uri, &res); err != nil {
return 0, err
}
switch {
case d.channel < len(res.Meters):
power = res.Meters[d.channel].Power
case d.channel < len(res.EMeters):
power = res.EMeters[d.channel].Power
default:
return 0, errors.New("invalid channel, missing power meter")
}
default:
var res Gen2StatusResponse
if err := d.execGen2Cmd("Shelly.GetStatus", false, &res); err != nil {
return 0, err
}
switch d.channel {
case 1:
power = res.Switch1.Apower
case 2:
power = res.Switch2.Apower
default:
power = res.Switch0.Apower
}
}
return power, nil
}
// Enabled implements the api.Charger interface
func (d *Connection) Enabled() (bool, error) {
switch d.gen {
case 0, 1:
var res Gen1SwitchResponse
uri := fmt.Sprintf("%s/relay/%d", d.uri, d.channel)
err := d.GetJSON(uri, &res)
return res.Ison, err
default:
var res Gen2SwitchResponse
err := d.execGen2Cmd("Switch.GetStatus", false, &res)
return res.Output, err
}
}
// Enable implements the api.Charger interface
func (d *Connection) Enable(enable bool) error {
var err error
onoff := map[bool]string{true: "on", false: "off"}
switch d.gen {
case 0, 1:
var res Gen1SwitchResponse
uri := fmt.Sprintf("%s/relay/%d?turn=%s", d.uri, d.channel, onoff[enable])
err = d.GetJSON(uri, &res)
default:
var res Gen2SwitchResponse
err = d.execGen2Cmd("Switch.Set", enable, &res)
}
return err
}
// execGen2Cmd executes a shelly api gen1/gen2 command and provides the response
func (d *Connection) execGen2Cmd(method string, enable bool, res interface{}) error {
// Shelly gen 2 rfc7616 authentication
@ -172,53 +98,3 @@ func (d *Connection) execGen2Cmd(method string, enable bool, res interface{}) er
return d.DoJSON(req, &res)
}
// TotalEnergy implements the api.Meter interface
func (d *Connection) TotalEnergy() (float64, error) {
var energy float64
switch d.gen {
case 0, 1:
var res Gen1StatusResponse
uri := fmt.Sprintf("%s/status", d.uri)
if err := d.GetJSON(uri, &res); err != nil {
return 0, err
}
switch {
case d.channel < len(res.Meters):
energy = res.Meters[d.channel].Total
case d.channel < len(res.EMeters):
energy = res.EMeters[d.channel].Total
default:
return 0, errors.New("invalid channel, missing power meter")
}
energy = GetGen1Energy(d.devicetype, energy)
default:
var res Gen2StatusResponse
if err := d.execGen2Cmd("Shelly.GetStatus", false, &res); err != nil {
return 0, err
}
switch d.channel {
case 1:
energy = res.Switch1.Aenergy.Total
case 2:
energy = res.Switch2.Aenergy.Total
default:
energy = res.Switch0.Aenergy.Total
}
}
return energy / 1000, nil
}
// GetGen1Energy in kWh
func GetGen1Energy(devicetype string, energy float64) float64 {
// Gen 1 Shelly EM devices are providing Watt hours, Shelly EM devices are providing Watt minutes
if !strings.Contains(devicetype, "EM") {
energy = energy / 60
}
return energy
}

View file

@ -0,0 +1,71 @@
package shelly
import "github.com/evcc-io/evcc/api"
type EnergyMeter struct {
*Connection
}
func NewEnergyMeter(conn *Connection) *EnergyMeter {
res := &EnergyMeter{
Connection: conn,
}
return res
}
// CurrentPower implements the api.Meter interface
func (sh *EnergyMeter) CurrentPower() (float64, error) {
var res Gen2EmStatusResponse
if err := sh.Connection.execGen2Cmd("EM.GetStatus", false, &res); err != nil {
return 0, err
}
return res.TotalPower, nil
}
// TotalEnergy implements the api.Meter interface
func (sh *EnergyMeter) TotalEnergy() (float64, error) {
var res Gen2EmDataStatusResponse
if err := sh.Connection.execGen2Cmd("EMData.GetStatus", false, &res); err != nil {
return 0, err
}
return res.TotalEnergy, nil
}
var _ api.PhaseCurrents = (*EnergyMeter)(nil)
// Currents implements the api.PhaseCurrents interface
func (sh *EnergyMeter) Currents() (float64, float64, float64, error) {
var res Gen2EmStatusResponse
if err := sh.Connection.execGen2Cmd("EM.GetStatus", false, &res); err != nil {
return 0, 0, 0, err
}
return res.CurrentA, res.CurrentB, res.CurrentC, nil
}
var _ api.PhaseVoltages = (*EnergyMeter)(nil)
// Voltages implements the api.PhaseVoltages interface
func (sh *EnergyMeter) Voltages() (float64, float64, float64, error) {
var res Gen2EmStatusResponse
if err := sh.Connection.execGen2Cmd("EM.GetStatus", false, &res); err != nil {
return 0, 0, 0, err
}
return res.VoltageA, res.VoltageB, res.VoltageC, nil
}
var _ api.PhasePowers = (*EnergyMeter)(nil)
// Powers implements the api.PhasePowers interface
func (sh *EnergyMeter) Powers() (float64, float64, float64, error) {
var res Gen2EmStatusResponse
if err := sh.Connection.execGen2Cmd("EM.GetStatus", false, &res); err != nil {
return 0, 0, 0, err
}
return res.PowerA, res.PowerB, res.PowerC, nil
}

149
meter/shelly/switch.go Normal file
View file

@ -0,0 +1,149 @@
package shelly
import (
"errors"
"fmt"
"strings"
)
type Switch struct {
*Connection
}
func NewSwitch(conn *Connection) *Switch {
res := &Switch{
Connection: conn,
}
return res
}
// CurrentPower implements the api.Meter interface
func (sh *Switch) CurrentPower() (float64, error) {
var power float64
d := sh.Connection
switch d.gen {
case 0, 1:
var res Gen1StatusResponse
uri := fmt.Sprintf("%s/status", d.uri)
if err := d.GetJSON(uri, &res); err != nil {
return 0, err
}
switch {
case d.channel < len(res.Meters):
power = res.Meters[d.channel].Power
case d.channel < len(res.EMeters):
power = res.EMeters[d.channel].Power
default:
return 0, errors.New("invalid channel, missing power meter")
}
default:
var res Gen2StatusResponse
if err := d.execGen2Cmd("Shelly.GetStatus", false, &res); err != nil {
return 0, err
}
switch d.channel {
case 1:
power = res.Switch1.Apower
case 2:
power = res.Switch2.Apower
default:
power = res.Switch0.Apower
}
}
return power, nil
}
// Enabled implements the api.Charger interface
func (sh *Switch) Enabled() (bool, error) {
d := sh.Connection
switch d.gen {
case 0, 1:
var res Gen1SwitchResponse
uri := fmt.Sprintf("%s/relay/%d", d.uri, d.channel)
err := d.GetJSON(uri, &res)
return res.Ison, err
default:
var res Gen2SwitchResponse
err := d.execGen2Cmd("Switch.GetStatus", false, &res)
return res.Output, err
}
}
// Enable implements the api.Charger interface
func (sh *Switch) Enable(enable bool) error {
var err error
onoff := map[bool]string{true: "on", false: "off"}
d := sh.Connection
switch d.gen {
case 0, 1:
var res Gen1SwitchResponse
uri := fmt.Sprintf("%s/relay/%d?turn=%s", d.uri, d.channel, onoff[enable])
err = d.GetJSON(uri, &res)
default:
var res Gen2SwitchResponse
err = d.execGen2Cmd("Switch.Set", enable, &res)
}
return err
}
// TotalEnergy implements the api.Meter interface
func (sh *Switch) TotalEnergy() (float64, error) {
var energy float64
d := sh.Connection
switch d.gen {
case 0, 1:
var res Gen1StatusResponse
uri := fmt.Sprintf("%s/status", d.uri)
if err := d.GetJSON(uri, &res); err != nil {
return 0, err
}
switch {
case d.channel < len(res.Meters):
energy = res.Meters[d.channel].Total
case d.channel < len(res.EMeters):
energy = res.EMeters[d.channel].Total
default:
return 0, errors.New("invalid channel, missing power meter")
}
energy = gen1Energy(d.devicetype, energy)
default:
var res Gen2StatusResponse
if err := d.execGen2Cmd("Shelly.GetStatus", false, &res); err != nil {
return 0, err
}
switch d.channel {
case 1:
energy = res.Switch1.Aenergy.Total
case 2:
energy = res.Switch2.Aenergy.Total
default:
energy = res.Switch0.Aenergy.Total
}
}
return energy / 1000, nil
}
// gen1Energy in kWh
func gen1Energy(devicetype string, energy float64) float64 {
// Gen 1 Shelly EM devices are providing Watt hours, Shelly EM devices are providing Watt minutes
if !strings.Contains(devicetype, "EM") {
energy = energy / 60
}
return energy
}

View file

@ -37,6 +37,23 @@ type Gen2StatusResponse struct {
Switch2 Gen2Switch `json:"switch:2"`
}
type Gen2EmStatusResponse struct {
TotalPower float64 `json:"total_act_power"`
CurrentA float64 `json:"a_current"`
CurrentB float64 `json:"b_current"`
CurrentC float64 `json:"c_current"`
VoltageA float64 `json:"a_voltage"`
VoltageB float64 `json:"b_voltage"`
VoltageC float64 `json:"c_voltage"`
PowerA float64 `json:"a_act_power"`
PowerB float64 `json:"b_act_power"`
PowerC float64 `json:"c_act_power"`
}
type Gen2EmDataStatusResponse struct {
TotalEnergy float64 `json:"total_act"`
}
type Gen1SwitchResponse struct {
Ison bool
}

View file

@ -3,65 +3,54 @@ package shelly
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
)
// Test Gen1StatusResponse response
func TestUnmarshalGen1StatusResponse(t *testing.T) {
var res Gen1StatusResponse
{
// Shelly 1 PM channel 0 (1)
var res Gen1StatusResponse
// Shelly 1 PM channel 0 (1)
jsonstr := `{"wifi_sta":{"connected":true,"ssid":"XXXX-WLAN","ip":"192.168.178.XXX","rssi":-54},"cloud":{"enabled":false,"connected":false},"mqtt":{"connected":false},"time":"17:59","unixtime":1676134770,"serial":2437,"has_update":true,"mac":"84CCA8XXXXXXX","cfg_changed_cnt":1,"actions_stats":{"skipped":0},"relays":[{"ison":false,"has_timer":false,"timer_started":0,"timer_duration":0,"timer_remaining":0,"overpower":false,"source":"http"}],"meters":[{"power":4711.12,"overpower":0.00,"is_valid":true,"timestamp":1676138370,"counters":[0.000, 0.000, 0.000],"total":6472513}],"inputs":[{"input":0,"event":"","event_cnt":0}],"temperature":16.79,"overtemperature":false,"tmp":{"tC":16.79,"tF":62.22, "is_valid":true},"temperature_status":"Normal","ext_sensors":{},"ext_temperature":{},"ext_humidity":{},"update":{"status":"pending","has_update":true,"new_version":"20221108-153925/v1.12.1-1PM-fix-g2821131","old_version":"20220209-094317/v1.11.8-g8c7bb8d"},"ram_total":50456,"ram_free":37056,"fs_size":233681,"fs_free":149094,"uptime":17284290}`
if err := json.Unmarshal([]byte(jsonstr), &res); err != nil {
t.Error(err)
}
if GetGen1Energy("SHSW-PM", res.Meters[0].Total) != 107875.21666666666 {
t.Error("SHSW-PM res.Meters[0].Total) != 107875.21666666666")
}
if res.Meters[0].Power != 4711.12 {
t.Error("res.Meters[0].Power != 4711.12")
jsonstr := `{"wifi_sta":{"connected":true,"ssid":"XXXX-WLAN","ip":"192.168.178.XXX","rssi":-54},"cloud":{"enabled":false,"connected":false},"mqtt":{"connected":false},"time":"17:59","unixtime":1676134770,"serial":2437,"has_update":true,"mac":"84CCA8XXXXXXX","cfg_changed_cnt":1,"actions_stats":{"skipped":0},"relays":[{"ison":false,"has_timer":false,"timer_started":0,"timer_duration":0,"timer_remaining":0,"overpower":false,"source":"http"}],"meters":[{"power":4711.12,"overpower":0.00,"is_valid":true,"timestamp":1676138370,"counters":[0.000, 0.000, 0.000],"total":6472513}],"inputs":[{"input":0,"event":"","event_cnt":0}],"temperature":16.79,"overtemperature":false,"tmp":{"tC":16.79,"tF":62.22, "is_valid":true},"temperature_status":"Normal","ext_sensors":{},"ext_temperature":{},"ext_humidity":{},"update":{"status":"pending","has_update":true,"new_version":"20221108-153925/v1.12.1-1PM-fix-g2821131","old_version":"20220209-094317/v1.11.8-g8c7bb8d"},"ram_total":50456,"ram_free":37056,"fs_size":233681,"fs_free":149094,"uptime":17284290}`
assert.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
assert.Equal(t, 107875.21666666666, gen1Energy("SHSW-PM", res.Meters[0].Total))
assert.Equal(t, 4711.12, res.Meters[0].Power)
}
// Shelly 1 channel 0 (1)
res = Gen1StatusResponse{}
jsonstr = `{"wifi_sta":{"connected":true,"ssid":"XXXX-WLAN","ip":"192.168.178.XXX","rssi":-57},"cloud":{"enabled":false,"connected":false},"mqtt":{"connected":false},"time":"19:25","unixtime":1676139913,"serial":959,"has_update":true,"mac":"E8DB8XXXXXX","cfg_changed_cnt":1,"actions_stats":{"skipped":0},"relays":[{"ison":false,"has_timer":false,"timer_started":0,"timer_duration":0,"timer_remaining":0,"source":"timer"}],"meters":[{"power":81.5,"is_valid":true}],"inputs":[{"input":0,"event":"","event_cnt":0}],"ext_sensors":{},"ext_temperature":{},"ext_humidity":{},"update":{"status":"pending","has_update":true,"new_version":"20221027-091427/v1.12.1-ga9117d3","old_version":"20211109-124958/v1.11.7-g682a0db"},"ram_total":50880,"ram_free":38796,"fs_size":233681,"fs_free":151102,"uptime":20319391}`
if err := json.Unmarshal([]byte(jsonstr), &res); err != nil {
t.Error(err)
}
if GetGen1Energy("SHSW-1", res.Meters[0].Total) != 0 {
t.Error("SHSW-1 res.Meters[0].Total) != 0")
}
if res.Meters[0].Power != 81.5 {
t.Error("res.Meters[0].Power != -81.5")
{
// Shelly 1 channel 0 (1)
var res Gen1StatusResponse
jsonstr := `{"wifi_sta":{"connected":true,"ssid":"XXXX-WLAN","ip":"192.168.178.XXX","rssi":-57},"cloud":{"enabled":false,"connected":false},"mqtt":{"connected":false},"time":"19:25","unixtime":1676139913,"serial":959,"has_update":true,"mac":"E8DB8XXXXXX","cfg_changed_cnt":1,"actions_stats":{"skipped":0},"relays":[{"ison":false,"has_timer":false,"timer_started":0,"timer_duration":0,"timer_remaining":0,"source":"timer"}],"meters":[{"power":81.5,"is_valid":true}],"inputs":[{"input":0,"event":"","event_cnt":0}],"ext_sensors":{},"ext_temperature":{},"ext_humidity":{},"update":{"status":"pending","has_update":true,"new_version":"20221027-091427/v1.12.1-ga9117d3","old_version":"20211109-124958/v1.11.7-g682a0db"},"ram_total":50880,"ram_free":38796,"fs_size":233681,"fs_free":151102,"uptime":20319391}`
assert.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
assert.Equal(t, 0.0, gen1Energy("SHSW-1", res.Meters[0].Total))
assert.Equal(t, 81.5, res.Meters[0].Power)
}
// Shelly EM channel 0 (1)
res = Gen1StatusResponse{}
jsonstr = `{"wifi_sta":{"connected":true,"ssid":"XXXX","ip":"192.168.178.XXX","rssi":-55},"cloud":{"enabled":false,"connected":false},"mqtt":{"connected":false},"time":"11:16","unixtime":1676110566,"serial":21580,"has_update":false,"mac":"C45BXXXXX","cfg_changed_cnt":0,"actions_stats":{"skipped":0},"relays":[{"ison":false,"has_timer":false,"timer_started":0,"timer_duration":0,"timer_remaining":0,"overpower":false,"is_valid":true,"source":"input"}],"emeters":[{"power":-620.34,"reactive":714.48,"pf":-0.66,"voltage":235.68,"is_valid":true,"total":401472.9,"total_returned":653673.7},{"power":0.00,"reactive":0.00,"pf":0.00,"voltage":235.68,"is_valid":true,"total":173411.3,"total_returned":294.2}],"update":{"status":"idle","has_update":false,"new_version":"20221027-105518/v1.12.1-ga9117d3","old_version":"20221027-105518/v1.12.1-ga9117d3"},"ram_total":51072,"ram_free":35660,"fs_size":233681,"fs_free":156373,"uptime":2226140}`
if err := json.Unmarshal([]byte(jsonstr), &res); err != nil {
t.Error(err)
}
if GetGen1Energy("SHEM", res.EMeters[0].Total) != 401472.9 {
t.Error("SHEM res.EMeters[0].Total) != 401472.9")
}
if res.EMeters[0].Power != -620.34 {
t.Error("res.EMeters[0].Power != -620.34")
}
{
// Shelly EM channel 0 (1)
var res Gen1StatusResponse
jsonstr := `{"wifi_sta":{"connected":true,"ssid":"XXXX","ip":"192.168.178.XXX","rssi":-55},"cloud":{"enabled":false,"connected":false},"mqtt":{"connected":false},"time":"11:16","unixtime":1676110566,"serial":21580,"has_update":false,"mac":"C45BXXXXX","cfg_changed_cnt":0,"actions_stats":{"skipped":0},"relays":[{"ison":false,"has_timer":false,"timer_started":0,"timer_duration":0,"timer_remaining":0,"overpower":false,"is_valid":true,"source":"input"}],"emeters":[{"power":-620.34,"reactive":714.48,"pf":-0.66,"voltage":235.68,"is_valid":true,"total":401472.9,"total_returned":653673.7},{"power":0.00,"reactive":0.00,"pf":0.00,"voltage":235.68,"is_valid":true,"total":173411.3,"total_returned":294.2}],"update":{"status":"idle","has_update":false,"new_version":"20221027-105518/v1.12.1-ga9117d3","old_version":"20221027-105518/v1.12.1-ga9117d3"},"ram_total":51072,"ram_free":35660,"fs_size":233681,"fs_free":156373,"uptime":2226140}`
assert.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
assert.Equal(t, 401472.9, gen1Energy("SHEM", res.EMeters[0].Total))
assert.Equal(t, -620.34, res.EMeters[0].Power)
}
}
// Test Gen2StatusResponse response
func TestUnmarshalGen2StatusResponse(t *testing.T) {
// Shelly Pro 1PM channel 0 (1)
var res Gen2StatusResponse
// Shelly Pro 1PM channel 0 (1)
jsonstr := `{"ble":{},"cloud":{"connected":true},"eth":{"ip":null},"input:0":{"id":0,"state":false},"input:1":{"id":1,"state":false},"mqtt":{"connected":false},"switch:0":{"id":0, "source":"HTTP", "output":false, "apower":47.11, "voltage":232.0, "current":0.000, "pf":0.00, "aenergy":{"total":5.125,"by_minute":[0.000,0.000,0.000],"minute_ts":1675718520},"temperature":{"tC":25.3, "tF":77.5}},"sys":{"mac":"30C6F78BB4D8","restart_required":false,"time":"22:22","unixtime":1675718522,"uptime":45070,"ram_size":234204,"ram_free":137716,"fs_size":524288,"fs_free":172032,"cfg_rev":13,"kvs_rev":1,"schedule_rev":0,"webhook_rev":0,"available_updates":{"beta":{"version":"0.13.0-beta3"}}},"wifi":{"sta_ip":"192.168.178.64","status":"got ip","ssid":"***","rssi":-62},"ws":{"connected":false}}`
if err := json.Unmarshal([]byte(jsonstr), &res); err != nil {
t.Error(err)
}
if res.Switch0.Aenergy.Total != 5.125 {
t.Error("res.Switch0.Aenergy.Total != 5.125")
}
if res.Switch0.Apower != 47.11 {
t.Error("res.Switch0.Apower != 47.11")
}
assert.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
assert.Equal(t, 5.125, res.Switch0.Aenergy.Total)
assert.Equal(t, 47.11, res.Switch0.Apower)
}

View file

@ -0,0 +1,33 @@
package meter
import (
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/meter/shelly"
"github.com/evcc-io/evcc/util"
)
// Shelly meter implementation
func init() {
registry.Add("shelly-energymeter", NewShellyEnergyMeterFromConfig)
}
// NewShellyFromConfig creates a Shelly charger from generic config
func NewShellyEnergyMeterFromConfig(other map[string]interface{}) (api.Meter, error) {
var cc struct {
URI string
User string
Password string
Channel int
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
conn, err := shelly.NewConnection(cc.URI, cc.User, cc.Password, cc.Channel)
if err != nil {
return nil, err
}
return shelly.NewEnergyMeter(conn), nil
}

View file

@ -11,9 +11,7 @@ params:
default: 192.0.2.2
- name: user
- name: password
mask: true
- name: channel
default: 0
render: |
type: shelly
uri: http://{{ .host }} # shelly device ip address (local)

View file

@ -0,0 +1,18 @@
template: shelly-pro-3em
products:
- brand: Shelly
description:
generic: Pro 3 EM
params:
- name: usage
choice: ["grid", "pv", "charge"]
- name: host
default: 192.0.2.2
- name: user
- name: password
render: |
type: shelly-energymeter
uri: http://{{ .host }} # shelly device ip address (local)
{{ if ne .user "" }}user: {{ .user }}{{ end }}
{{ if ne .password "" }}password: {{ .password }}{{ end }}
channel: 0 # shelly device relay channel

View file

@ -11,4 +11,4 @@ render:
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
channel: 0 # Optional
channel: # Optional

View file

@ -0,0 +1,28 @@
product:
brand: Shelly
description: Pro 3 EM
render:
- usage: grid
default: |
type: template
template: shelly-pro-3em
usage: grid
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
- usage: pv
default: |
type: template
template: shelly-pro-3em
usage: pv
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
- usage: charge
default: |
type: template
template: shelly-pro-3em
usage: charge
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