evcc-io/cmd/configure/devicetest.go
Andreas Linde 7e41a970d5
Experimentelle geführte Erstellung der Konfigurationsdatei (#1888)
Dies ist eine experimentelle Umsetzung im folgenden aufgeführten Ziele.

Wie es verwendet:
- Start über `evcc configure`
- Die Konfiguration wird in evcc.yaml geschrieben. Falls diese existiert kann ein alternativer Dateiname angegeben werden

Was es kann:
- Eine geführte Erstellung der Konfigurationsdatei
- Direktes Testen ob die Konfiguration jedes Gerätes auch funktioniert
- Konfigurationsabhängigkeiten durch direkte Konfiguration der Abhängigkeit lösen (z.B. Sponsorshipt required)
- Konfiguration in für Anwender bekannte Informationen und möglichst wenig Informationseingabe zu ermöglichen, z.B. anhand von Produkten anstatt aus der Implementierungssicht
- Bisherige Konfiguration funktioniert weiter
2021-11-30 14:29:09 +01:00

142 lines
3.6 KiB
Go

package configure
import (
"errors"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/charger"
"github.com/evcc-io/evcc/meter"
"github.com/evcc-io/evcc/util/templates"
"github.com/evcc-io/evcc/vehicle"
"gopkg.in/yaml.v3"
)
type DeviceTestResult string
const (
DeviceTestResultValid DeviceTestResult = "Valid"
DeviceTestResultValidMissingMeter DeviceTestResult = "Valid_MissingMeter"
DeviceTestResultInvalid DeviceTestResult = "Invalid"
)
type DeviceTest struct {
DeviceCategory DeviceCategory
Template templates.Template
ConfigValues map[string]interface{}
}
// Test returns:
// - DeviceTestResult: Valid, Valid_MissingMeter, Invalid
// - error: != nil if the device is invalid and can not be configured with the provided settings
func (d *DeviceTest) Test() (DeviceTestResult, error) {
v, err := d.configure()
if err != nil {
return DeviceTestResultInvalid, err
}
switch DeviceCategories[d.DeviceCategory].class {
case DeviceClassCharger:
return d.testCharger(v)
case DeviceClassMeter:
return d.testMeter(d.DeviceCategory, v)
case DeviceClassVehicle:
return d.testVehicle(v)
}
return DeviceTestResultInvalid, errors.New("testDevice not implemented for this device class")
}
// configure creates a configured device from a template so we can test it
func (d *DeviceTest) configure() (interface{}, error) {
b, _, err := d.Template.RenderResult(false, d.ConfigValues)
if err != nil {
return nil, err
}
var instance struct {
Type string
Other map[string]interface{} `yaml:",inline"`
}
if err := yaml.Unmarshal(b, &instance); err != nil {
return nil, err
}
var v interface{}
switch DeviceCategories[d.DeviceCategory].class {
case DeviceClassMeter:
v, err = meter.NewFromConfig(instance.Type, instance.Other)
case DeviceClassCharger:
v, err = charger.NewFromConfig(instance.Type, instance.Other)
case DeviceClassVehicle:
v, err = vehicle.NewFromConfig(instance.Type, instance.Other)
}
return v, err
}
// testCharger tests a charger device
func (d *DeviceTest) testCharger(v interface{}) (DeviceTestResult, error) {
if v, ok := v.(api.Charger); ok {
if _, err := v.Status(); err != nil {
return DeviceTestResultInvalid, err
}
} else {
return DeviceTestResultInvalid, errors.New("Selected device is not a wallbox!")
}
if v, ok := v.(api.Meter); ok {
if _, err := v.CurrentPower(); err != nil {
return DeviceTestResultValidMissingMeter, nil
}
}
return DeviceTestResultValid, nil
}
// testMeter tests a meter device
func (d *DeviceTest) testMeter(deviceCategory DeviceCategory, v interface{}) (DeviceTestResult, error) {
if v, ok := v.(api.Meter); ok {
if _, err := v.CurrentPower(); err != nil {
return DeviceTestResultInvalid, err
}
if deviceCategory == DeviceCategoryBatteryMeter {
if v, ok := v.(api.Battery); ok {
_, err := v.SoC()
for err != nil && errors.Is(err, api.ErrMustRetry) {
time.Sleep(3 * time.Second)
_, err = v.SoC()
}
if err != nil {
return DeviceTestResultInvalid, err
}
} else {
return DeviceTestResultInvalid, errors.New("Selected device is not a battery meter!")
}
}
} else {
return DeviceTestResultInvalid, errors.New("Selected device is not a meter!")
}
return DeviceTestResultValid, nil
}
// testVehicle tests a vehicle device
func (d *DeviceTest) testVehicle(v interface{}) (DeviceTestResult, error) {
if _, ok := v.(api.Vehicle); ok {
if v, ok := v.(api.Battery); ok {
if _, err := v.SoC(); err != nil {
return DeviceTestResultInvalid, err
}
}
} else {
return DeviceTestResultInvalid, errors.New("Selected device is not a vehicle!")
}
return DeviceTestResultValid, nil
}