Add Homematic pluggable switch (#3758)
Some checks failed
Release / call-build-workflow (push) Has been cancelled
Release / Publish Docker :release (push) Has been cancelled
Release / Publish Github & APT release (push) Has been cancelled

This commit is contained in:
Markus Thierolf 2022-07-07 22:56:18 +02:00 • committed by GitHub
parent fe732d806f
commit 5332148fc1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 462 additions and 0 deletions

115
charger/homematic.go Normal file
View file

@ -0,0 +1,115 @@
package charger
import (
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/meter/homematic"
"github.com/evcc-io/evcc/util"
)
// Homematic CCU charger implementation
type CCU struct {
conn *homematic.Connection
standbypower float64
}
func init() {
registry.Add("homematic", NewCCUFromConfig)
}
// NewCCUFromConfig creates a Homematic charger from generic config
func NewCCUFromConfig(other map[string]interface{}) (api.Charger, error) {
cc := struct {
URI string
Device string
MeterChannel string
SwitchChannel string
User string
Password string
StandbyPower float64
}{}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
return NewCCU(cc.URI, cc.Device, cc.MeterChannel, cc.SwitchChannel, cc.User, cc.Password, cc.StandbyPower)
}
// NewCCU creates a new connection with standbypower for charger
func NewCCU(uri, deviceid, meterid, switchid, user, password string, standbypower float64) (*CCU, error) {
conn, err := homematic.NewConnection(uri, deviceid, meterid, switchid, user, password)
wb := &CCU{
conn: conn,
standbypower: standbypower,
}
return wb, err
}
// Enabled implements the api.Charger interface
func (c *CCU) Enabled() (bool, error) {
return c.conn.Enabled()
}
// Enable implements the api.Charger interface
func (c *CCU) Enable(enable bool) error {
return c.conn.Enable(enable)
}
// MaxCurrent implements the api.Charger interface
func (c *CCU) MaxCurrent(current int64) error {
return nil
}
// Status implements the api.Charger interface
func (c *CCU) Status() (api.ChargeStatus, error) {
res := api.StatusB
on, err := c.Enabled()
if err != nil {
return res, err
}
power, err := c.conn.CurrentPower()
if err != nil {
return res, err
}
// static mode || standby power mode condition
if on && (c.standbypower < 0 || power > c.standbypower) {
res = api.StatusC
}
return res, nil
}
var _ api.Meter = (*CCU)(nil)
// CurrentPower implements the api.Meter interface
func (c *CCU) CurrentPower() (float64, error) {
var power float64
// set fix static power in static mode
if c.standbypower < 0 {
on, err := c.Enabled()
if on {
power = -c.standbypower
}
return power, err
}
// ignore power in standby mode
power, err := c.conn.CurrentPower()
if power <= c.standbypower {
power = 0
}
return power, err
}
var _ api.MeterEnergy = (*CCU)(nil)
// TotalEnergy implements the api.MeterEnergy interface
func (c *CCU) TotalEnergy() (float64, error) {
return c.conn.TotalEnergy()
}

66
meter/homematic.go Normal file
View file

@ -0,0 +1,66 @@
package meter
import (
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/meter/homematic"
"github.com/evcc-io/evcc/util"
)
// Homematic CCU meter implementation
type CCU struct {
conn *homematic.Connection
usage string
}
func init() {
registry.Add("homematic", NewCCUFromConfig)
}
// NewCCUFromConfig creates a Homematic meter from generic config
func NewCCUFromConfig(other map[string]interface{}) (api.Meter, error) {
cc := struct {
URI string
Device string
MeterChannel string
SwitchChannel string
User string
Password string
Usage string
}{}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
return NewCCU(cc.URI, cc.Device, cc.MeterChannel, cc.SwitchChannel, cc.User, cc.Password, cc.Usage)
}
// NewCCU creates a new connection with usage for meter
func NewCCU(uri, deviceid, meterid, switchid, user, password, usage string) (*CCU, error) {
conn, err := homematic.NewConnection(uri, deviceid, meterid, switchid, user, password)
m := &CCU{
conn: conn,
usage: usage,
}
return m, err
}
// CurrentPower implements the api.Meter interface
func (c *CCU) CurrentPower() (float64, error) {
if c.usage == "grid" {
return c.conn.GridCurrentPower()
}
return c.conn.CurrentPower()
}
var _ api.MeterEnergy = (*CCU)(nil)
// TotalEnergy implements the api.MeterEnergy interface
func (c *CCU) TotalEnergy() (float64, error) {
if c.usage == "grid" {
return c.conn.GridTotalEnergy()
}
return c.conn.TotalEnergy()
}

View file

@ -0,0 +1,137 @@
package homematic
import (
"encoding/xml"
"fmt"
"net/http"
"strings"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
"github.com/evcc-io/evcc/util/transport"
)
// Homematic plugable switchchannel and meterchannel charger based on CCU XML-RPC interface
// https://homematic-ip.com/sites/default/files/downloads/HM_XmlRpc_API.pdf
// https://homematic-ip.com/sites/default/files/downloads/HMIP_XmlRpc_API_Addendum.pdf
// Homematic CCU settings
type Settings struct {
URI, Device, MeterChannel, SwitchChannel, User, Password string
}
// Connection is the Homematic CCU connection
type Connection struct {
log *util.Logger
*request.Helper
*Settings
}
// NewConnection creates a new Homematic device connection.
func NewConnection(uri, device, meterchannel, switchchannel, user, password string) (*Connection, error) {
log := util.NewLogger("homematic")
settings := &Settings{
URI: util.DefaultScheme(uri, "http"),
Device: device,
MeterChannel: meterchannel,
SwitchChannel: switchchannel,
}
conn := &Connection{
log: log,
Helper: request.NewHelper(log),
Settings: settings,
}
conn.Client.Transport = request.NewTripper(log, transport.Insecure())
if user != "" {
log.Redact(transport.BasicAuthHeader(user, password))
conn.Client.Transport = transport.BasicAuth(user, password, conn.Client.Transport)
}
return conn, nil
}
func (c *Connection) XmlCmd(method, channel string, values ...Param) (MethodResponse, error) {
target := fmt.Sprintf("%s:%s", c.Device, channel)
hmc := MethodCall{
XMLName: xml.Name{},
MethodName: method,
Params: append([]Param{{CCUString: target}}, values...),
}
var hmr MethodResponse
body, err := xml.Marshal(hmc)
if err != nil {
return hmr, err
}
headers := map[string]string{
"Content-Type": "text/xml",
}
if req, err := request.New(http.MethodPost, c.URI, strings.NewReader(xml.Header+string(body)), headers); err == nil {
if res, err := c.DoBody(req); err == nil {
if strings.Contains(string(res), "faultCode") {
return hmr, fmt.Errorf("ccu: %s", string(res))
}
// correct Homematic IP Legacy API (CCU port 2010) method response encoding value
res = []byte(strings.Replace(string(res), "ISO-8859-1", "UTF-8", 1))
// correct XML-RPC-Schnittstelle (CCU port 2001) method response encoding value
res = []byte(strings.Replace(string(res), "iso-8859-1", "UTF-8", 1))
if err := xml.Unmarshal(res, &hmr); err != nil {
return hmr, err
}
}
}
return hmr, err
}
// Enabled reads the homematic HMIP-PSM switchchannel state true=on/false=off
func (c *Connection) Enabled() (bool, error) {
res, err := c.XmlCmd("getValue", c.SwitchChannel, Param{CCUString: "STATE"})
return res.Value.CCUBool == "1", err
}
// Enable sets the homematic HMIP-PSM switchchannel state to true=on/false=off
func (c *Connection) Enable(enable bool) error {
onoff := map[bool]string{true: "1", false: "0"}
_, err := c.XmlCmd("setValue", c.SwitchChannel, Param{CCUString: "STATE"}, Param{CCUBool: onoff[enable]})
return err
}
// CurrentPower reads the homematic HMIP-PSM meterchannel power in W
func (c *Connection) CurrentPower() (float64, error) {
res, err := c.XmlCmd("getValue", c.MeterChannel, Param{CCUString: "POWER"})
return res.Value.CCUFloat, err
}
// TotalEnergy reads the homematic HMIP-PSM meterchannel energy in Wh
func (c *Connection) TotalEnergy() (float64, error) {
res, err := c.XmlCmd("getValue", c.MeterChannel, Param{CCUString: "ENERGY_COUNTER"})
return res.Value.CCUFloat / 1000, err
}
// Currents reads the homematic HMIP-PSM meterchannel L1 current in A
func (c *Connection) Currents() (float64, float64, float64, error) {
res, err := c.XmlCmd("getValue", c.MeterChannel, Param{CCUString: "CURRENT"})
return res.Value.CCUFloat / 1000, 0, 0, err
}
// GridCurrentPower reads the homematic HM-ES-TX-WM grid meterchannel power in W
func (c *Connection) GridCurrentPower() (float64, error) {
res, err := c.XmlCmd("getValue", c.MeterChannel, Param{CCUString: "IEC_POWER"})
return res.Value.CCUFloat, err
}
// GridTotalEnergy reads the homematic HM-ES-TX-WM grid meterchannel energy in Wh
func (c *Connection) GridTotalEnergy() (float64, error) {
res, err := c.XmlCmd("getValue", c.MeterChannel, Param{CCUString: "IEC_ENERGY_COUNTER"})
return res.Value.CCUFloat, err
}

27
meter/homematic/types.go Normal file
View file

@ -0,0 +1,27 @@
package homematic
import (
"encoding/xml"
)
// Homematic CCU XML-RPC types
// https://homematic-ip.com/sites/default/files/downloads/HM_XmlRpc_API.pdf
// https://homematic-ip.com/sites/default/files/downloads/HMIP_XmlRpc_API_Addendum.pdf
type Param struct {
CCUBool string `xml:"value>boolean,omitempty"`
CCUFloat float64 `xml:"value>double,omitempty"`
CCUInt int64 `xml:"value>i4,omitempty"`
CCUString string `xml:"value>string,omitempty"`
}
type MethodCall struct {
XMLName xml.Name `xml:"methodCall"`
MethodName string `xml:"methodName"`
Params []Param `xml:"params>param,omitempty"`
}
type MethodResponse struct {
XMLName xml.Name `xml:"methodResponse"`
Value Param `xml:"params>param,omitempty"`
}

View file

@ -0,0 +1,36 @@
template: homematic
products:
- brand: Homematic IP
group: switchsockets
params:
- name: host
- name: device
description:
de: Geräteadresse/Seriennummer
en: Device address/Serial number
required: true
mask: false
example: "0001EE89AAD848"
help:
en: Homematic device id like shown in the CCU web user interface.
de: Homematic Geräte Id, wie im CCU Webfrontend angezeigt.
- name: standbypower
default: 15
- name: user
required: false
- name: password
required: false
mask: true
render: |
type: homematic
uri: {{ .host }}:2010
device: {{ .device }}
meterchannel: 6
switchchannel: 3
standbypower: {{ .standbypower }}
{{ if ne .user "" }}
user: {{ .user }}
{{ end }}
{{ if ne .password "" }}
password: {{ .password }}
{{ end }}

View file

@ -0,0 +1,35 @@
template: homematic
products:
- brand: Homematic IP
group: switchsockets
params:
- name: usage
choice: ["grid", "pv", "charge"]
- name: host
- name: device
description:
de: Geräteadresse/Seriennummer
en: Device address/Serial number
required: true
mask: false
example: "0001EE89AAD848"
help:
en: Homematic device id like shown in the CCU web user interface.
de: Homematic Geräte Id, wie im CCU Webfrontend angezeigt.
- name: user
required: false
- name: password
required: false
mask: true
render: |
type: homematic
usage: {{ .usage }}
uri: {{ .host }}:{{- if (eq .usage "grid")}}2001{{ else }}2010{{end}}
device: {{ .device }}
meterchannel: {{ if (eq .usage "grid")}}1{{ else }}6{{end}}
{{ if ne .user "" }}
user: {{ .user }}
{{ end }}
{{ if ne .password "" }}
password: {{ .password }}
{{ end }}

View file

@ -0,0 +1,12 @@
product:
brand: Homematic IP
group: Schaltbare Steckdosen
render:
- default: |
type: template
template: homematic
host: 192.0.2.2 # IP-Adresse oder Hostname
device: 0001EE89AAD848 # Homematic Geräte Id, wie im CCU Webfrontend angezeigt.
standbypower: 15 # Leistung oberhalb des angegebenen Wertes wird als Ladeleistung gewertet # Optional
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

@ -0,0 +1,31 @@
product:
brand: Homematic IP
group: Schaltbare Steckdosen
render:
- usage: grid
default: |
type: template
template: homematic
usage: grid
host: 192.0.2.2 # IP-Adresse oder Hostname
device: 0001EE89AAD848 # Homematic Geräte Id, wie im CCU Webfrontend angezeigt.
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: homematic
usage: pv
host: 192.0.2.2 # IP-Adresse oder Hostname
device: 0001EE89AAD848 # Homematic Geräte Id, wie im CCU Webfrontend angezeigt.
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: homematic
usage: charge
host: 192.0.2.2 # IP-Adresse oder Hostname
device: 0001EE89AAD848 # Homematic Geräte Id, wie im CCU Webfrontend angezeigt.
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

@ -43,6 +43,7 @@
],
"SmartPlugs": [
"AVM",
"Homematic IP",
"Shelly",
"Tasmota",
"TP-Link"
@ -56,6 +57,7 @@
"Eastron",
"FENECON",
"Fronius",
"Homematic IP",
"Huawei",
"Janitza",
"Kostal",
@ -85,6 +87,7 @@
"Eastron",
"FENECON",
"Fronius",
"Homematic IP",
"Huawei",
"Janitza",
"Kostal",