-
Notifications
You must be signed in to change notification settings - Fork 0
/
devicetest.go
157 lines (126 loc) · 3.9 KB
/
devicetest.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package configure
import (
"errors"
"time"
"github.com/thommyho/robotui/api"
"github.com/thommyho/robotui/charger"
"github.com/thommyho/robotui/meter"
"github.com/thommyho/robotui/util/templates"
"github.com/thommyho/robotui/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 templates.Charger:
return d.testCharger(v)
case templates.Meter:
return d.testMeter(d.DeviceCategory, v)
case templates.Vehicle:
return d.testVehicle(v)
default:
panic("invalid class for category: " + d.DeviceCategory)
}
}
// configure creates a configured device from a template so we can test it
func (d *DeviceTest) configure() (interface{}, error) {
b, _, err := d.Template.RenderResult(templates.TemplateRenderModeInstance, 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 templates.Meter:
v, err = meter.NewFromConfig(instance.Type, instance.Other)
case templates.Charger:
v, err = charger.NewFromConfig(instance.Type, instance.Other)
case templates.Vehicle:
v, err = vehicle.NewFromConfig(instance.Type, instance.Other)
}
return v, err
}
// testCharger tests a charger device
func (d *DeviceTest) testCharger(v interface{}) (DeviceTestResult, error) {
c, ok := v.(api.Charger)
if !ok {
return DeviceTestResultInvalid, errors.New("selected device is not a wallbox")
}
if _, err := c.Status(); err != nil {
return DeviceTestResultInvalid, err
}
m, ok := v.(api.Meter)
if !ok {
return DeviceTestResultValidMissingMeter, nil
}
if _, err := m.CurrentPower(); err != nil {
return DeviceTestResultInvalid, err
}
return DeviceTestResultValid, nil
}
// testMeter tests a meter device
func (d *DeviceTest) testMeter(deviceCategory DeviceCategory, v interface{}) (DeviceTestResult, error) {
m, ok := v.(api.Meter)
if !ok {
return DeviceTestResultInvalid, errors.New("selected device is not a meter")
}
power, err := m.CurrentPower()
if err != nil {
return DeviceTestResultInvalid, err
}
// check if the grid meter reports power 0, which should be impossible
// happens with Kostal Piko charger that do not have a grid meter attached
// but we can't determine this
if power == 0 && deviceCategory == DeviceCategoryGridMeter {
return DeviceTestResultInvalid, errors.New("grid meter reports power 0")
}
if deviceCategory == DeviceCategoryBatteryMeter {
b, ok := v.(api.Battery)
if !ok {
return DeviceTestResultInvalid, errors.New("selected device is not a battery meter")
}
_, err := b.Soc()
for err != nil && errors.Is(err, api.ErrMustRetry) {
time.Sleep(3 * time.Second)
_, err = b.Soc()
}
if err != nil {
return DeviceTestResultInvalid, err
}
}
return DeviceTestResultValid, nil
}
// testVehicle tests a vehicle device
func (d *DeviceTest) testVehicle(v interface{}) (DeviceTestResult, error) {
vv, ok := v.(api.Vehicle)
if !ok {
return DeviceTestResultInvalid, errors.New("selected device is not a vehicle")
}
if _, err := vv.Soc(); err != nil {
return DeviceTestResultInvalid, err
}
return DeviceTestResultValid, nil
}