HomeAssistant: detect units (BC) (#26439)

This commit is contained in:
andig 2026-01-09 22:03:05 +01:00 • committed by GitHub
parent 708d600db4
commit b045f17b94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 62 additions and 28 deletions

View file

@ -7,6 +7,7 @@ import (
"net/url"
"strconv"
"strings"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util"
@ -59,19 +60,19 @@ func (c *Connection) GetStates() ([]StateResponse, error) {
}
// GetState retrieves the state of an entity
func (c *Connection) GetState(entity string) (string, error) {
func (c *Connection) GetState(entity string) (StateResponse, error) {
var res StateResponse
uri := fmt.Sprintf("%s/api/states/%s", c.instance.URI(), url.PathEscape(entity))
if err := c.GetJSON(uri, &res); err != nil {
return "", err
return res, err
}
if res.State == "unknown" || res.State == "unavailable" {
return "", api.ErrNotAvailable
return res, api.ErrNotAvailable
}
return res.State, nil
return res, nil
}
// GetIntState retrieves the state of an entity as int64
@ -81,9 +82,9 @@ func (c *Connection) GetIntState(entity string) (int64, error) {
return 0, err
}
value, err := strconv.ParseInt(state, 10, 64)
value, err := strconv.ParseInt(state.State, 10, 64)
if err != nil {
return 0, fmt.Errorf("invalid numeric state '%s' for entity %s: %w", state, entity, err)
return 0, fmt.Errorf("invalid numeric state '%s' for entity %s: %w", state.State, entity, err)
}
return value, nil
@ -96,12 +97,17 @@ func (c *Connection) GetFloatState(entity string) (float64, error) {
return 0, err
}
value, err := strconv.ParseFloat(state, 64)
value, err := strconv.ParseFloat(state.State, 64)
if err != nil {
return 0, fmt.Errorf("invalid numeric state '%s' for entity %s: %w", state, entity, err)
return 0, fmt.Errorf("invalid numeric state '%s' for entity %s: %w", state.State, entity, err)
}
return value, nil
scale, err := state.scale()
if err != nil {
return 0, fmt.Errorf("%w for entity %s", err, entity)
}
return scale * value, nil
}
// GetBoolState retrieves the state of an entity as boolean
@ -111,8 +117,8 @@ func (c *Connection) GetBoolState(entity string) (bool, error) {
return false, err
}
state = strings.ToLower(state)
switch state {
res := strings.ToLower(state.State)
switch res {
case "on", "true", "1", "active", "yes":
return true, nil
case "off", "false", "0", "inactive", "no":
@ -122,6 +128,20 @@ func (c *Connection) GetBoolState(entity string) (bool, error) {
}
}
// GetTimeState retrieves the state of an entity as time
func (c *Connection) GetTimeState(entity string) (time.Time, error) {
state, err := c.GetState(entity)
if err != nil {
return time.Time{}, err
}
if ts, err := strconv.ParseInt(state.State, 10, 64); err == nil {
return time.Unix(ts, 0), nil
}
return time.Parse(time.RFC3339, state.State)
}
// chargeStatusMap maps Home Assistant states to EVCC charge status
var chargeStatusMap = map[string]api.ChargeStatus{
// Status C - Charging
@ -166,7 +186,7 @@ func (c *Connection) GetChargeStatus(entity string) (api.ChargeStatus, error) {
return api.StatusNone, err
}
if status, ok := chargeStatusMap[strings.ToLower(strings.TrimSpace(state))]; ok {
if status, ok := chargeStatusMap[strings.ToLower(strings.TrimSpace(state.State))]; ok {
return status, nil
}

View file

@ -1,5 +1,10 @@
package homeassistant
import (
"fmt"
"strings"
)
type StateResponse struct {
EntityId string `json:"entity_id"`
State string `json:"state"`
@ -9,3 +14,26 @@ type StateResponse struct {
FriendlyName string `json:"friendly_name"`
} `json:"attributes"`
}
func (state StateResponse) scale() (float64, error) {
if unit, ok := strings.CutSuffix(state.Attributes.UnitOfMeasurement, "W"); ok {
switch unit {
case "":
case "k":
return 1e3, nil
default:
return 0, fmt.Errorf("invalid unit '%s'", state.Attributes.UnitOfMeasurement)
}
} else if unit, ok := strings.CutSuffix(state.Attributes.UnitOfMeasurement, "Wh"); ok {
switch unit {
case "":
return 1e-3, nil
case "k":
case "M":
return 1e3, nil
default:
return 0, fmt.Errorf("invalid unit '%s'", state.Attributes.UnitOfMeasurement)
}
}
return 1, nil
}

View file

@ -2,7 +2,6 @@ package vehicle
import (
"errors"
"strconv"
"time"
"github.com/evcc-io/evcc/api"
@ -66,7 +65,7 @@ func NewHomeAssistantVehicleFromConfig(other map[string]any) (api.Vehicle, error
soc: cc.Sensors.Soc,
}
// prepare optional feature functions with concise names
// optional features
var (
limitSoc func() (int64, error)
status func() (api.ChargeStatus, error)
@ -101,7 +100,7 @@ func NewHomeAssistantVehicleFromConfig(other map[string]any) (api.Vehicle, error
climater = func() (bool, error) { return conn.GetBoolState(cc.Sensors.Climater) }
}
if cc.Sensors.FinishTime != "" {
finish = func() (time.Time, error) { return res.finishTime(cc.Sensors.FinishTime) }
finish = func() (time.Time, error) { return conn.GetTimeState(cc.Sensors.FinishTime) }
}
if cc.Services.Start != "" && cc.Services.Stop != "" {
enable = func(enable bool) error { return res.enable(cc.Services.Start, cc.Services.Stop, enable) }
@ -133,19 +132,6 @@ func (v *HomeAssistant) Soc() (float64, error) {
return v.conn.GetFloatState(v.soc)
}
func (v *HomeAssistant) finishTime(entity string) (time.Time, error) {
s, err := v.conn.GetState(entity)
if err != nil {
return time.Time{}, err
}
if ts, err := strconv.ParseInt(s, 10, 64); err == nil {
return time.Unix(ts, 0), nil
}
return time.Parse(time.RFC3339, s)
}
func (v *HomeAssistant) enable(on, off string, enable bool) error {
if enable {
return v.conn.CallSwitchService(on, true)