chore: fix linter (#11028)

This commit is contained in:
andig 2023-12-06 18:29:32 +01:00 • committed by GitHub
parent 098a0fc2b5
commit 58e2421a5b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
91 changed files with 374 additions and 280 deletions

View file

@ -1,10 +1,55 @@
run: run:
go: "1.21" go: "1.21"
issues: issues:
exclude: exclude:
- .regActualCurrent. is unused - .regActualCurrent. is unused
- "`routeLogger` is unused" - "`routeLogger` is unused"
linters-settings: linters-settings:
staticcheck: staticcheck:
checks: checks:
- "-SA1019" - "-SA1019"
linters:
disable-all: true
enable:
- dogsled
- durationcheck
- exportloopref
- gci
- gofmt
- gofumpt
- goimports
- goprintffuncname
- govet
- importas
- ineffassign
- makezero
- misspell
- nolintlint
- rowserrcheck
- sqlclosecheck
- staticcheck
- tparallel
- typecheck
- unconvert
- unused
- wastedassign
- whitespace
# fixme
# - bodyclose
# - exhaustive
# - gocritic
# - godot
# - gomoddirectives
# - nakedret
# - nilerr
# - noctx
# - prealloc
# - predeclared
# - revive
# - stylecheck
# - unparam
# - dupl

View file

@ -93,7 +93,7 @@ func NewAlphatec(uri, device, comset string, baudrate int, proto modbus.Protocol
func (wb *Alphatec) setCurrent(current uint16) error { func (wb *Alphatec) setCurrent(current uint16) error {
b := make([]byte, 2) b := make([]byte, 2)
binary.BigEndian.PutUint16(b, uint16(current)) binary.BigEndian.PutUint16(b, current)
_, err := wb.conn.WriteMultipleRegisters(alphatecRegAmpsConfig, 1, b) _, err := wb.conn.WriteMultipleRegisters(alphatecRegAmpsConfig, 1, b)

View file

@ -356,7 +356,7 @@ func (c *Easee) ProductUpdate(i json.RawMessage) {
} }
// OpMode changed FROM >1 ("car connected") TO 1/disconnected - stop ticker if channel exists // OpMode changed FROM >1 ("car connected") TO 1/disconnected - stop ticker if channel exists
// channel may not exist regulary if the car was connected but charging never started // channel may not exist regularly if the car was connected but charging never started
if c.opMode != easee.ModeDisconnected && opMode == easee.ModeDisconnected && c.stopTicker != nil { if c.opMode != easee.ModeDisconnected && opMode == easee.ModeDisconnected && c.stopTicker != nil {
close(c.stopTicker) close(c.stopTicker)
c.stopTicker = nil c.stopTicker = nil

View file

@ -73,7 +73,7 @@ const (
LOCAL_PRE_AUTHORIZE_ENABLED ObservationID = 15 // Preauthorize with whitelist enabled. Readback on setting [event] [Boolean] LOCAL_PRE_AUTHORIZE_ENABLED ObservationID = 15 // Preauthorize with whitelist enabled. Readback on setting [event] [Boolean]
LOCAL_AUTHORIZE_OFFLINE_ENABLED ObservationID = 16 // Allow offline charging for whitelisted RFID token. Readback on setting [event] [Boolean] LOCAL_AUTHORIZE_OFFLINE_ENABLED ObservationID = 16 // Allow offline charging for whitelisted RFID token. Readback on setting [event] [Boolean]
ALLOW_OFFLINE_TX_FOR_UNKNOWN_ID ObservationID = 17 // Allow offline charging for all RFID tokens. Readback on setting [event] [Boolean] ALLOW_OFFLINE_TX_FOR_UNKNOWN_ID ObservationID = 17 // Allow offline charging for all RFID tokens. Readback on setting [event] [Boolean]
ERRATIC_EVMAX_TOGGLES ObservationID = 18 // 0 == erratic checking disabled, otherwise the number of toggles between states Charging and Charging Complate that will trigger an error [Integer] ERRATIC_EVMAX_TOGGLES ObservationID = 18 // 0 == erratic checking disabled, otherwise the number of toggles between states Charging and Charging Complete that will trigger an error [Integer]
BACKPLATE_TYPE ObservationID = 19 // Readback on backplate type [Integer] BACKPLATE_TYPE ObservationID = 19 // Readback on backplate type [Integer]
SITE_STRUCTURE ObservationID = 20 // Site Structure [boot] [String] SITE_STRUCTURE ObservationID = 20 // Site Structure [boot] [String]
DETECTED_POWER_GRID_TYPE ObservationID = 21 // Detected power grid type according to PowerGridType table [boot] [Integer] DETECTED_POWER_GRID_TYPE ObservationID = 21 // Detected power grid type according to PowerGridType table [boot] [Integer]
@ -94,7 +94,7 @@ const (
FORCED_THREE_PHASE_ON_ITWITH_GND_FAULT ObservationID = 39 // Default disabled. Must be set manually if grid type is indeed three phase IT [Boolean] FORCED_THREE_PHASE_ON_ITWITH_GND_FAULT ObservationID = 39 // Default disabled. Must be set manually if grid type is indeed three phase IT [Boolean]
LED_STRIP_BRIGHTNESS ObservationID = 40 // LED strip brightness, 0-100% [Integer] LED_STRIP_BRIGHTNESS ObservationID = 40 // LED strip brightness, 0-100% [Integer]
LOCAL_AUTHORIZATION_REQUIRED ObservationID = 41 // Local RFID authorization is required for charging [user options] [event] [Boolean] LOCAL_AUTHORIZATION_REQUIRED ObservationID = 41 // Local RFID authorization is required for charging [user options] [event] [Boolean]
AUTHORIZATION_REQUIRED ObservationID = 42 // Authorization is requried for charging [Boolean] AUTHORIZATION_REQUIRED ObservationID = 42 // Authorization is required for charging [Boolean]
REMOTE_START_REQUIRED ObservationID = 43 // Remote start required flag [event] [Boolean] REMOTE_START_REQUIRED ObservationID = 43 // Remote start required flag [event] [Boolean]
SMART_BUTTON_ENABLED ObservationID = 44 // Smart button is enabled [Boolean] SMART_BUTTON_ENABLED ObservationID = 44 // Smart button is enabled [Boolean]
OFFLINE_CHARGING_MODE ObservationID = 45 // Charger behavior when offline [Integer] OFFLINE_CHARGING_MODE ObservationID = 45 // Charger behavior when offline [Integer]

View file

@ -36,12 +36,10 @@ func NewWattpilotFromConfig(other map[string]interface{}) (api.Charger, error) {
} }
return NewWattpilot(cc.URI, cc.Password, cc.Cache) return NewWattpilot(cc.URI, cc.Password, cc.Cache)
} }
// NewWattpilot creates Wattpilot charger // NewWattpilot creates Wattpilot charger
func NewWattpilot(uri, password string, cache time.Duration) (api.Charger, error) { func NewWattpilot(uri, password string, cache time.Duration) (api.Charger, error) {
c := &Wattpilot{ c := &Wattpilot{
api: wattpilot.New(uri, password), api: wattpilot.New(uri, password),
} }

View file

@ -66,7 +66,7 @@ func (c *LocalAPI) upgradeV2() {
err := c.response("api/status?filter=alw", &res) err := c.response("api/status?filter=alw", &res)
if err == nil { if err == nil {
c.uri = c.uri + "/api" c.uri += "/api"
} else { } else {
c.v2 = false c.v2 = false
} }

View file

@ -108,7 +108,7 @@ func (wb *HuaweiACCharger) Status() (api.ChargeStatus, error) {
return api.StatusNone, err return api.StatusNone, err
} }
//ToDo: Real status // ToDo: Real status
switch u := binary.BigEndian.Uint16(b); u { switch u := binary.BigEndian.Uint16(b); u {
case 0: case 0:
return api.StatusA, nil return api.StatusA, nil

View file

@ -119,7 +119,7 @@ func (mcc *MobileConnect) fetchToken(request *http.Request) error {
} }
// login as the home user with the given password // login as the home user with the given password
func (mcc *MobileConnect) login(password string) error { func (mcc *MobileConnect) login() error {
uri := fmt.Sprintf("%s/%s", mcc.uri, mccAPILogin) uri := fmt.Sprintf("%s/%s", mcc.uri, mccAPILogin)
data := url.Values{ data := url.Values{
@ -170,7 +170,7 @@ func (mcc *MobileConnect) request(method, uri string) (*http.Request, error) {
// do we need to login? // do we need to login?
if mcc.token == "" { if mcc.token == "" {
if err := mcc.login(mcc.password); err != nil { if err := mcc.login(); err != nil {
return nil, err return nil, err
} }
} }

View file

@ -31,7 +31,7 @@ type apiResponse struct {
// NewTestClient returns *http.Client with Transport replaced to avoid making real calls // NewTestClient returns *http.Client with Transport replaced to avoid making real calls
func NewTestClient(fn roundTripFunc) *http.Client { func NewTestClient(fn roundTripFunc) *http.Client {
return &http.Client{ return &http.Client{
Transport: roundTripFunc(fn), Transport: fn,
} }
} }
@ -50,13 +50,13 @@ func NewTestMobileConnect(t *testing.T, responses []apiResponse) *MobileConnect
// response string for the currently invoked call // response string for the currently invoked call
var responseString string var responseString string
for _, s := range responses { for _, s := range responses {
if strings.Contains("/"+string(s.apiCall), req.URL.Path) { if strings.Contains("/"+s.apiCall, req.URL.Path) {
responseString = s.apiResponse responseString = s.apiResponse
} }
} }
return &http.Response{ return &http.Response{
StatusCode: 200, StatusCode: http.StatusOK,
// Send response to be tested // Send response to be tested
Body: io.NopCloser(bytes.NewBufferString(responseString)), Body: io.NopCloser(bytes.NewBufferString(responseString)),
// Must be set to non-nil value or it panics // Must be set to non-nil value or it panics
@ -83,7 +83,7 @@ func TestMobileConnectLogin(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
mcc := NewTestMobileConnect(t, tc.responses) mcc := NewTestMobileConnect(t, tc.responses)
if err := mcc.login(tc.password); (err != nil) != tc.wantErr { if err := mcc.login(); (err != nil) != tc.wantErr {
t.Errorf("MobileConnect.login() error = %v, wantErr %v", err, tc.wantErr) t.Errorf("MobileConnect.login() error = %v, wantErr %v", err, tc.wantErr)
} }
}) })
@ -263,13 +263,15 @@ func TestMobileConnectCurrentPower(t *testing.T) {
"no data response", "no data response",
[]apiResponse{ []apiResponse{
{mccAPIEnergy, "\"\"\n"}, {mccAPIEnergy, "\"\"\n"},
}, 0, false, },
0, false,
}, },
{ {
"home plug - error response", "home plug - error response",
[]apiResponse{ []apiResponse{
{mccAPIEnergy, "\"{\\n \\\"L1\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 246.60000000000002\\n },\\n \\\"L2\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 16.800000000000001\\n },\\n \\\"L3\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 16.300000000000001\\n }\\n}\\n\""}, {mccAPIEnergy, "\"{\\n \\\"L1\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 246.60000000000002\\n },\\n \\\"L2\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 16.800000000000001\\n },\\n \\\"L3\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 16.300000000000001\\n }\\n}\\n\""},
}, 0, false, },
0, false,
}, },
} }
for _, tc := range tests { for _, tc := range tests {
@ -300,19 +302,22 @@ func TestMobileConnectChargedEnergy(t *testing.T) {
"valid response", "valid response",
[]apiResponse{ []apiResponse{
{mccAPICurrentSession, "\"{\\n \\\"account\\\": \\\"PRIVATE\\\",\\n \\\"chargingRate\\\": 0,\\n \\\"chargingType\\\": \\\"AC\\\",\\n \\\"clockSrc\\\": \\\"NTP\\\",\\n \\\"costs\\\": 0,\\n \\\"currency\\\": \\\"\\\",\\n \\\"departTime\\\": \\\"\\\",\\n \\\"duration\\\": 30789,\\n \\\"endOfChargeTime\\\": \\\"\\\",\\n \\\"endSoc\\\": 0,\\n \\\"endTime\\\": \\\"\\\",\\n \\\"energySumKwh\\\": 18.832000000000001,\\n \\\"evChargingRatekW\\\": 0,\\n \\\"evTargetSoc\\\": -1,\\n \\\"evVasAvailability\\\": false,\\n \\\"pcid\\\": \\\"\\\",\\n \\\"powerRange\\\": 0,\\n \\\"selfEnergy\\\": 0,\\n \\\"sessionId\\\": 13,\\n \\\"soc\\\": -1,\\n \\\"solarEnergyShare\\\": 0,\\n \\\"startSoc\\\": 0,\\n \\\"startTime\\\": \\\"2020-04-15T10:07:22+02:00\\\",\\n \\\"totalRange\\\": 0,\\n \\\"vehicleBrand\\\": \\\"\\\",\\n \\\"vehicleModel\\\": \\\"\\\",\\n \\\"whitelist\\\": false\\n}\\n\""}, {mccAPICurrentSession, "\"{\\n \\\"account\\\": \\\"PRIVATE\\\",\\n \\\"chargingRate\\\": 0,\\n \\\"chargingType\\\": \\\"AC\\\",\\n \\\"clockSrc\\\": \\\"NTP\\\",\\n \\\"costs\\\": 0,\\n \\\"currency\\\": \\\"\\\",\\n \\\"departTime\\\": \\\"\\\",\\n \\\"duration\\\": 30789,\\n \\\"endOfChargeTime\\\": \\\"\\\",\\n \\\"endSoc\\\": 0,\\n \\\"endTime\\\": \\\"\\\",\\n \\\"energySumKwh\\\": 18.832000000000001,\\n \\\"evChargingRatekW\\\": 0,\\n \\\"evTargetSoc\\\": -1,\\n \\\"evVasAvailability\\\": false,\\n \\\"pcid\\\": \\\"\\\",\\n \\\"powerRange\\\": 0,\\n \\\"selfEnergy\\\": 0,\\n \\\"sessionId\\\": 13,\\n \\\"soc\\\": -1,\\n \\\"solarEnergyShare\\\": 0,\\n \\\"startSoc\\\": 0,\\n \\\"startTime\\\": \\\"2020-04-15T10:07:22+02:00\\\",\\n \\\"totalRange\\\": 0,\\n \\\"vehicleBrand\\\": \\\"\\\",\\n \\\"vehicleModel\\\": \\\"\\\",\\n \\\"whitelist\\\": false\\n}\\n\""},
}, 18.832000000000001, false, },
18.832000000000001, false,
}, },
{ {
"no data response", "no data response",
[]apiResponse{ []apiResponse{
{mccAPICurrentSession, "\"\"\n"}, {mccAPICurrentSession, "\"\"\n"},
}, 0, false, },
0, false,
}, },
{ {
"error response", "error response",
[]apiResponse{ []apiResponse{
{mccAPICurrentSession, "invalidjson"}, {mccAPICurrentSession, "invalidjson"},
}, 0, true, },
0, true,
}, },
} }
for _, tc := range tests { for _, tc := range tests {
@ -342,19 +347,22 @@ func TestMobileConnectChargingTime(t *testing.T) {
"valid response", "valid response",
[]apiResponse{ []apiResponse{
{mccAPICurrentSession, "\"{\\n \\\"account\\\": \\\"PRIVATE\\\",\\n \\\"chargingRate\\\": 0,\\n \\\"chargingType\\\": \\\"AC\\\",\\n \\\"clockSrc\\\": \\\"NTP\\\",\\n \\\"costs\\\": 0,\\n \\\"currency\\\": \\\"\\\",\\n \\\"departTime\\\": \\\"\\\",\\n \\\"duration\\\": 30789,\\n \\\"endOfChargeTime\\\": \\\"\\\",\\n \\\"endSoc\\\": 0,\\n \\\"endTime\\\": \\\"\\\",\\n \\\"energySumKwh\\\": 18.832000000000001,\\n \\\"evChargingRatekW\\\": 0,\\n \\\"evTargetSoc\\\": -1,\\n \\\"evVasAvailability\\\": false,\\n \\\"pcid\\\": \\\"\\\",\\n \\\"powerRange\\\": 0,\\n \\\"selfEnergy\\\": 0,\\n \\\"sessionId\\\": 13,\\n \\\"soc\\\": -1,\\n \\\"solarEnergyShare\\\": 0,\\n \\\"startSoc\\\": 0,\\n \\\"startTime\\\": \\\"2020-04-15T10:07:22+02:00\\\",\\n \\\"totalRange\\\": 0,\\n \\\"vehicleBrand\\\": \\\"\\\",\\n \\\"vehicleModel\\\": \\\"\\\",\\n \\\"whitelist\\\": false\\n}\\n\""}, {mccAPICurrentSession, "\"{\\n \\\"account\\\": \\\"PRIVATE\\\",\\n \\\"chargingRate\\\": 0,\\n \\\"chargingType\\\": \\\"AC\\\",\\n \\\"clockSrc\\\": \\\"NTP\\\",\\n \\\"costs\\\": 0,\\n \\\"currency\\\": \\\"\\\",\\n \\\"departTime\\\": \\\"\\\",\\n \\\"duration\\\": 30789,\\n \\\"endOfChargeTime\\\": \\\"\\\",\\n \\\"endSoc\\\": 0,\\n \\\"endTime\\\": \\\"\\\",\\n \\\"energySumKwh\\\": 18.832000000000001,\\n \\\"evChargingRatekW\\\": 0,\\n \\\"evTargetSoc\\\": -1,\\n \\\"evVasAvailability\\\": false,\\n \\\"pcid\\\": \\\"\\\",\\n \\\"powerRange\\\": 0,\\n \\\"selfEnergy\\\": 0,\\n \\\"sessionId\\\": 13,\\n \\\"soc\\\": -1,\\n \\\"solarEnergyShare\\\": 0,\\n \\\"startSoc\\\": 0,\\n \\\"startTime\\\": \\\"2020-04-15T10:07:22+02:00\\\",\\n \\\"totalRange\\\": 0,\\n \\\"vehicleBrand\\\": \\\"\\\",\\n \\\"vehicleModel\\\": \\\"\\\",\\n \\\"whitelist\\\": false\\n}\\n\""},
}, 30789 * time.Second, false, },
30789 * time.Second, false,
}, },
{ {
"no data response", "no data response",
[]apiResponse{ []apiResponse{
{mccAPICurrentSession, "\"\"\n"}, {mccAPICurrentSession, "\"\"\n"},
}, 0, false, },
0, false,
}, },
{ {
"error response", "error response",
[]apiResponse{ []apiResponse{
{mccAPICurrentSession, "invalidjson"}, {mccAPICurrentSession, "invalidjson"},
}, 0, true, },
0, true,
}, },
} }
for _, tc := range tests { for _, tc := range tests {
@ -399,13 +407,15 @@ func TestMobileConnectCurrents(t *testing.T) {
"no data response", "no data response",
[]apiResponse{ []apiResponse{
{mccAPIEnergy, "\"\"\n"}, {mccAPIEnergy, "\"\"\n"},
}, 0, 0, 0, false, },
0, 0, 0, false,
}, },
{ {
"home plug - error response", "home plug - error response",
[]apiResponse{ []apiResponse{
{mccAPIEnergy, "\"{\\n \\\"L1\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 246.60000000000002\\n },\\n \\\"L2\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 16.800000000000001\\n },\\n \\\"L3\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 16.300000000000001\\n }\\n}\\n\""}, {mccAPIEnergy, "\"{\\n \\\"L1\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 246.60000000000002\\n },\\n \\\"L2\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 16.800000000000001\\n },\\n \\\"L3\\\": {\\n \\\"Ampere\\\": 0,\\n \\\"Power\\\": 0,\\n \\\"Volts\\\": 16.300000000000001\\n }\\n}\\n\""},
}, 0, 0, 0, false, },
0, 0, 0, false,
}, },
} }
for _, tc := range tests { for _, tc := range tests {

View file

@ -109,16 +109,16 @@ func (suite *ocppTestSuite) TestConnect() {
// status // status
_, err = c1.Status() _, err = c1.Status()
suite.NoError(err) suite.Require().NoError(err)
// power // power
f, err := c1.currentPower() f, err := c1.currentPower()
suite.NoError(err) suite.Require().NoError(err)
suite.Equal(1e3, f) suite.Equal(1e3, f)
// energy // energy
f, err = c1.totalEnergy() f, err = c1.totalEnergy()
suite.NoError(err) suite.Require().NoError(err)
suite.Equal(1.2, f) suite.Equal(1.2, f)
} }
@ -148,7 +148,7 @@ func (suite *ocppTestSuite) TestConnect() {
res, err := cp1.StopTransaction(0, types.NewDateTime(suite.clock.Now()), expectedTxn) res, err := cp1.StopTransaction(0, types.NewDateTime(suite.clock.Now()), expectedTxn)
suite.Require().NoError(err) suite.Require().NoError(err)
suite.Equal(res.IdTagInfo.Status, types.AuthorizationStatusAccepted) suite.Equal(types.AuthorizationStatusAccepted, res.IdTagInfo.Status)
} }
// 2nd charge point - remote // 2nd charge point - remote
@ -166,7 +166,7 @@ func (suite *ocppTestSuite) TestConnect() {
// status // status
_, err = c2.Status() _, err = c2.Status()
suite.NoError(err) suite.Require().NoError(err)
} }
// error on unconfigured 2nd charge point // error on unconfigured 2nd charge point

View file

@ -2,6 +2,7 @@ package charger
import ( import (
"fmt" "fmt"
"strconv"
"strings" "strings"
"github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/api"
@ -69,7 +70,7 @@ func (wb *Pantabox) Enabled() (bool, error) {
// Enable implements the api.Charger interface // Enable implements the api.Charger interface
func (wb *Pantabox) Enable(enable bool) error { func (wb *Pantabox) Enable(enable bool) error {
resp, err := wb.Post(wb.uri+"/charger/enable", request.PlainContent, strings.NewReader(fmt.Sprintf("%t", enable))) resp, err := wb.Post(wb.uri+"/charger/enable", request.PlainContent, strings.NewReader(strconv.FormatBool(enable)))
if err == nil { if err == nil {
defer resp.Body.Close() defer resp.Body.Close()
@ -83,7 +84,7 @@ func (wb *Pantabox) Enable(enable bool) error {
// MaxCurrent implements the api.Charger interface // MaxCurrent implements the api.Charger interface
func (wb *Pantabox) MaxCurrent(current int64) error { func (wb *Pantabox) MaxCurrent(current int64) error {
resp, err := wb.Post(wb.uri+"/charger/current", request.PlainContent, strings.NewReader(fmt.Sprintf("%d", current))) resp, err := wb.Post(wb.uri+"/charger/current", request.PlainContent, strings.NewReader(strconv.FormatInt(current, 10)))
if err == nil { if err == nil {
defer resp.Body.Close() defer resp.Body.Close()

View file

@ -54,7 +54,7 @@ type SendParameter struct {
Values []Value `json:"values"` Values []Value `json:"values"`
} }
// part of Paramter Send structure // part of Parameter Send structure
type Value struct { type Value struct {
Timestamp string `json:"timestamp"` Timestamp string `json:"timestamp"`
ChannelId string `json:"channelId"` ChannelId string `json:"channelId"`

View file

@ -34,6 +34,7 @@ var acceptable = []string{
func TestTemplates(t *testing.T) { func TestTemplates(t *testing.T) {
templates.TestClass(t, templates.Charger, func(t *testing.T, values map[string]any) { templates.TestClass(t, templates.Charger, func(t *testing.T, values map[string]any) {
t.Helper()
if _, err := NewFromConfig("template", values); err != nil && !test.Acceptable(err, acceptable) { if _, err := NewFromConfig("template", values); err != nil && !test.Acceptable(err, acceptable) {
t.Log(values) t.Log(values)
t.Error(err) t.Error(err)

View file

@ -55,7 +55,7 @@ type Vitals struct {
SessionEnergyWh float64 `json:"session_energy_wh"` // 22864.699 SessionEnergyWh float64 `json:"session_energy_wh"` // 22864.699
ConfigStatus int `json:"config_status"` // 5 ConfigStatus int `json:"config_status"` // 5
EvseState int `json:"evse_state"` // 1 EvseState int `json:"evse_state"` // 1
CurrentAlerts []any `json:"current_alerts"` //[] CurrentAlerts []any `json:"current_alerts"` // []
} }
// NewTwc3FromConfig creates a new vehicle // NewTwc3FromConfig creates a new vehicle

View file

@ -163,7 +163,6 @@ func (c *CmdConfigure) configureLinkedTemplate(templateItem templates.Template,
if c.askConfigFailureNextStep() { if c.askConfigFailureNextStep() {
continue continue
} }
} else { } else {
c.configuration.AddDevice(deviceItem, category) c.configuration.AddDevice(deviceItem, category)
c.processDeviceCapabilities(templateItem.Capabilities) c.processDeviceCapabilities(templateItem.Capabilities)

View file

@ -76,10 +76,8 @@ func (c *CmdConfigure) processDeviceValues(values map[string]interface{}, templa
c.addedDeviceIndex-- c.addedDeviceIndex--
return device, c.errDeviceNotValid return device, c.errDeviceNotValid
} }
} else { } else if deviceCategory == DeviceCategoryCharger && testResult == DeviceTestResultValid {
if deviceCategory == DeviceCategoryCharger && testResult == DeviceTestResultValid { device.ChargerHasMeter = true
device.ChargerHasMeter = true
}
} }
templateItem.Params = append(templateItem.Params, templates.Param{Name: "name", Value: device.Name}) templateItem.Params = append(templateItem.Params, templates.Param{Name: "name", Value: device.Name})
@ -330,7 +328,7 @@ func (c *CmdConfigure) fetchElements(deviceCategory DeviceCategory) []templates.
} }
} }
sort.Slice(items[:], func(i, j int) bool { sort.Slice(items, func(i, j int) bool {
// sort generic templates to the bottom // sort generic templates to the bottom
if items[i].Group != "" && items[j].Group == "" { if items[i].Group != "" && items[j].Group == "" {
return false return false
@ -376,7 +374,7 @@ func (c *CmdConfigure) processConfig(templateItem *templates.Template, deviceCat
fmt.Println(c.localizedString("Config_Title")) fmt.Println(c.localizedString("Config_Title"))
fmt.Println() fmt.Println()
c.processModbusConfig(templateItem, deviceCategory) c.processModbusConfig(templateItem)
return c.processParams(templateItem, deviceCategory) return c.processParams(templateItem, deviceCategory)
} }
@ -485,7 +483,7 @@ func (c *CmdConfigure) processInputConfig(param templates.Param) string {
// processModbusConfig adds default values from the modbus Param to the template // processModbusConfig adds default values from the modbus Param to the template
// and handles user input for interface type selection // and handles user input for interface type selection
func (c *CmdConfigure) processModbusConfig(templateItem *templates.Template, deviceCategory DeviceCategory) { func (c *CmdConfigure) processModbusConfig(templateItem *templates.Template) {
var choices []string var choices []string
var choiceTypes []string var choiceTypes []string

View file

@ -209,10 +209,8 @@ func (c *CmdConfigure) flowNewConfigFile() {
// in case of permission error, we can't write to the file anyway // in case of permission error, we can't write to the file anyway
if os.IsPermission(err) { if os.IsPermission(err) {
fmt.Println(c.localizedString("File_Permissions", localizeMap{"FileName": filename})) fmt.Println(c.localizedString("File_Permissions", localizeMap{"FileName": filename}))
} else { } else if c.askYesNo(c.localizedString("File_Exists", localizeMap{"FileName": filename})) {
if c.askYesNo(c.localizedString("File_Exists", localizeMap{"FileName": filename})) { break
break
}
} }
filename = c.askValue(question{ filename = c.askValue(question{
@ -302,7 +300,6 @@ func (c *CmdConfigure) configureLoadpoints() {
fmt.Println(c.localizedString("Loadpoint_Setup")) fmt.Println(c.localizedString("Loadpoint_Setup"))
for { for {
loadpointTitle := c.askValue(question{ loadpointTitle := c.askValue(question{
label: c.localizedString("Loadpoint_Title"), label: c.localizedString("Loadpoint_Title"),
defaultValue: c.localizedString("Loadpoint_DefaultTitle"), defaultValue: c.localizedString("Loadpoint_DefaultTitle"),

View file

@ -74,7 +74,7 @@ type ModbusHandler struct {
Timeout time.Duration Timeout time.Duration
} }
func (h *ModbusHandler) testRegister(log *util.Logger, conn gridx.Client) bool { func (h *ModbusHandler) testRegister(_ *util.Logger, conn gridx.Client) bool {
var bytes []byte var bytes []byte
var err error var err error

View file

@ -35,7 +35,7 @@ type TcpHandler struct {
dialer net.Dialer dialer net.Dialer
} }
func (h *TcpHandler) Test(log *util.Logger, in ResultDetails) (res []ResultDetails) { func (h *TcpHandler) Test(_ *util.Logger, in ResultDetails) (res []ResultDetails) {
for _, port := range h.Ports { for _, port := range h.Ports {
addr := net.JoinHostPort(in.IP, strconv.Itoa(port)) addr := net.JoinHostPort(in.IP, strconv.Itoa(port))
conn, err := h.dialer.Dial("tcp", addr) conn, err := h.dialer.Dial("tcp", addr)

View file

@ -57,7 +57,8 @@ func redact(src string) string {
"token", "access", "refresh", "accesstoken", "refreshtoken", // tokens, including template variations "token", "access", "refresh", "accesstoken", "refreshtoken", // tokens, including template variations
"ain", "secret", "serial", "deviceid", "machineid", "idtag", // devices "ain", "secret", "serial", "deviceid", "machineid", "idtag", // devices
"app", "chats", "recipients", // push messaging "app", "chats", "recipients", // push messaging
"vin"} // vehicles "vin", // vehicles
}
return regexp. return regexp.
MustCompile(fmt.Sprintf(`(?i)\b(%s)\b.*?:.*`, strings.Join(secrets, "|"))). MustCompile(fmt.Sprintf(`(?i)\b(%s)\b.*?:.*`, strings.Join(secrets, "|"))).
ReplaceAllString(src, "$1: *****") ReplaceAllString(src, "$1: *****")

View file

@ -24,12 +24,11 @@ import (
"github.com/evcc-io/evcc/util/telemetry" "github.com/evcc-io/evcc/util/telemetry"
"github.com/fatih/structs" "github.com/fatih/structs"
"github.com/jeremywohl/flatten" "github.com/jeremywohl/flatten"
"golang.org/x/exp/maps"
_ "github.com/joho/godotenv/autoload" _ "github.com/joho/godotenv/autoload"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"golang.org/x/exp/maps"
) )
const rebootDelay = 5 * time.Minute // delayed reboot on error const rebootDelay = 5 * time.Minute // delayed reboot on error

View file

@ -645,19 +645,14 @@ func configureSite(conf map[string]interface{}, loadpoints []*core.Loadpoint, ta
} }
func configureLoadpoints(conf globalConfig) (loadpoints []*core.Loadpoint, err error) { func configureLoadpoints(conf globalConfig) (loadpoints []*core.Loadpoint, err error) {
lpInterfaces, ok := viper.AllSettings()["loadpoints"].([]interface{}) if len(conf.Loadpoints) == 0 {
if !ok || len(lpInterfaces) == 0 {
return nil, errors.New("missing loadpoints") return nil, errors.New("missing loadpoints")
} }
for id, lpcI := range lpInterfaces { for id, lpc := range conf.Loadpoints {
var lpc map[string]interface{}
if err := util.DecodeOther(lpcI, &lpc); err != nil {
return nil, fmt.Errorf("failed decoding loadpoint configuration: %w", err)
}
log := util.NewLoggerWithLoadpoint("lp-"+strconv.Itoa(id+1), id+1) log := util.NewLoggerWithLoadpoint("lp-"+strconv.Itoa(id+1), id+1)
settings := &core.Settings{Key: "lp" + strconv.Itoa(id+1) + "."} settings := &core.Settings{Key: "lp" + strconv.Itoa(id+1) + "."}
lp, err := core.NewLoadpointFromConfig(log, settings, lpc) lp, err := core.NewLoadpointFromConfig(log, settings, lpc)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed configuring loadpoint: %w", err) return nil, fmt.Errorf("failed configuring loadpoint: %w", err)

View file

@ -33,7 +33,6 @@ func tokenExchangeHandler(oc *oauth2.Config, state string, resC chan *oauth2.Tok
token, err := oc.Exchange(ctx, code, token, err := oc.Exchange(ctx, code,
oauth2.SetAuthURLParam("grant_type", "code"), // app oauth2.SetAuthURLParam("grant_type", "code"), // app
) )
if err != nil { if err != nil {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
fmt.Fprintln(w, err) fmt.Fprintln(w, err)

View file

@ -49,9 +49,7 @@ func generate(out io.Writer, packageName, functionName, baseType string, dynamic
return dict, nil return dict, nil
}, },
// contains checks if slice contains string // contains checks if slice contains string
"contains": func(combo []string, typ string) bool { "contains": slices.Contains[[]string, string],
return slices.Contains(combo, typ)
},
// ordered returns a slice of typeStructs ordered by dynamicType // ordered returns a slice of typeStructs ordered by dynamicType
"ordered": func() []typeStruct { "ordered": func() []typeStruct {
ordered := make([]typeStruct, 0) ordered := make([]typeStruct, 0)

View file

@ -13,7 +13,7 @@ type API interface {
// Owner returns the loadpoint that currently owns the vehicle // Owner returns the loadpoint that currently owns the vehicle
Owner(api.Vehicle) loadpoint.API Owner(api.Vehicle) loadpoint.API
// Aquire acquires the vehicle for the loadpoint and releases it at any other loadpoint // Acquire acquires the vehicle for the loadpoint and releases it at any other loadpoint
Acquire(api.Vehicle) Acquire(api.Vehicle)
// Release releases a vehicle from a loadpoint // Release releases a vehicle from a loadpoint

View file

@ -3,7 +3,7 @@ package core
// EnergyMetrics calculates stats about the charged energy and gives you details about price or co2s // EnergyMetrics calculates stats about the charged energy and gives you details about price or co2s
type EnergyMetrics struct { type EnergyMetrics struct {
totalKWh float64 // Total amount of energy used (kWh) totalKWh float64 // Total amount of energy used (kWh)
solarKWh float64 // Self-produced energy energy (kWh) solarKWh float64 // Self-produced energy (kWh)
price *float64 // Total cost (Currency) price *float64 // Total cost (Currency)
co2 *float64 // Amount of emitted CO2 (gCO2eq) co2 *float64 // Amount of emitted CO2 (gCO2eq)
currentGreenShare float64 // Current share of solar energy of site (0-1) currentGreenShare float64 // Current share of solar energy of site (0-1)

View file

@ -28,72 +28,83 @@ func TestEnergyMetrics(t *testing.T) {
totalWh, solarPercentage float64 totalWh, solarPercentage float64
price, pricePerKWh, co2PerKWh *float64 price, pricePerKWh, co2PerKWh *float64
}{ }{
{"initial state", {
"initial state",
[]tcStep{}, []tcStep{},
0, 0, nil, nil, nil, 0, 0, nil, nil, nil,
}, },
{"energy value", {
"energy value",
[]tcStep{ []tcStep{
{0.1, 0, nil, nil}, {0.1, 0, nil, nil},
{0.2, 0, nil, nil}, {0.2, 0, nil, nil},
}, },
200, 0, nil, nil, nil, 200, 0, nil, nil, nil,
}, },
{"ignore lower energy value", {
"ignore lower energy value",
[]tcStep{ []tcStep{
{0.2, 0, nil, nil}, {0.2, 0, nil, nil},
{0.1, 0, nil, nil}, {0.1, 0, nil, nil},
}, },
200, 0, nil, nil, nil, 200, 0, nil, nil, nil,
}, },
{"half solar", {
"half solar",
[]tcStep{ []tcStep{
{0.1, 1, nil, nil}, {0.1, 1, nil, nil},
{0.2, 0, nil, nil}, {0.2, 0, nil, nil},
}, },
200, 50, nil, nil, nil, 200, 50, nil, nil, nil,
}, },
{"only solar", {
"only solar",
[]tcStep{ []tcStep{
{0.1, 1, nil, nil}, {0.1, 1, nil, nil},
{0.2, 1, nil, nil}, {0.2, 1, nil, nil},
}, },
200, 100, nil, nil, nil, 200, 100, nil, nil, nil,
}, },
{"static price", {
"static price",
[]tcStep{ []tcStep{
{1, 0, f(0.5), nil}, {1, 0, f(0.5), nil},
}, },
1000, 0, f(0.5), f(0.5), nil, 1000, 0, f(0.5), f(0.5), nil,
}, },
{"dynamic price", {
"dynamic price",
[]tcStep{ []tcStep{
{1, 0, f(1), nil}, {1, 0, f(1), nil},
{2, 0, f(0), nil}, {2, 0, f(0), nil},
}, },
2000, 0, f(1), f(0.5), nil, 2000, 0, f(1), f(0.5), nil,
}, },
{"dynamic price", {
"dynamic price",
[]tcStep{ []tcStep{
{2, 0, f(1), nil}, {2, 0, f(1), nil},
{4, 0, f(0), nil}, {4, 0, f(0), nil},
}, },
4000, 0, f(2), f(0.5), nil, 4000, 0, f(2), f(0.5), nil,
}, },
{"static co2", {
"static co2",
[]tcStep{ []tcStep{
{1, 0, nil, f(500)}, {1, 0, nil, f(500)},
}, },
1000, 0, nil, nil, f(500), 1000, 0, nil, nil, f(500),
}, },
{"dynamic co2", {
"dynamic co2",
[]tcStep{ []tcStep{
{1, 0, nil, f(1000)}, {1, 0, nil, f(1000)},
{2, 0, nil, f(0)}, {2, 0, nil, f(0)},
}, },
2000, 0, nil, nil, f(500), 2000, 0, nil, nil, f(500),
}, },
{"grid only, half, full solar, half, grid only", {
"grid only, half, full solar, half, grid only",
[]tcStep{ []tcStep{
{1, 0, f(2), f(200)}, {1, 0, f(2), f(200)},
{2, 0.5, f(1), f(50)}, {2, 0.5, f(1), f(50)},
@ -106,8 +117,6 @@ func TestEnergyMetrics(t *testing.T) {
} }
for _, tc := range tc { for _, tc := range tc {
//t.Logf("%+v", tc)
s := NewEnergyMetrics() s := NewEnergyMetrics()
for _, tc := range tc.steps { for _, tc := range tc.steps {

View file

@ -13,7 +13,7 @@ type Health struct {
} }
// NewHealth creates new health checker // NewHealth creates new health checker
func NewHealth(timeout time.Duration) (health *Health) { func NewHealth(timeout time.Duration) *Health {
return &Health{timeout: timeout} return &Health{timeout: timeout}
} }

View file

@ -9,6 +9,9 @@ import (
"sync" "sync"
"time" "time"
evbus "github.com/asaskevich/EventBus"
"github.com/avast/retry-go/v4"
"github.com/benbjohnson/clock"
"github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/core/coordinator" "github.com/evcc-io/evcc/core/coordinator"
"github.com/evcc-io/evcc/core/keys" "github.com/evcc-io/evcc/core/keys"
@ -23,10 +26,6 @@ import (
"github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/config" "github.com/evcc-io/evcc/util/config"
"github.com/evcc-io/evcc/util/telemetry" "github.com/evcc-io/evcc/util/telemetry"
evbus "github.com/asaskevich/EventBus"
"github.com/avast/retry-go/v4"
"github.com/benbjohnson/clock"
) )
const ( const (

View file

@ -123,6 +123,8 @@ func TestMaxActivePhases(t *testing.T) {
} }
func testScale(t *testing.T, lp *Loadpoint, sitePower float64, direction string, tc testCase) { func testScale(t *testing.T, lp *Loadpoint, sitePower float64, direction string, tc testCase) {
t.Helper()
act := lp.activePhases() act := lp.activePhases()
max := lp.maxActivePhases() max := lp.maxActivePhases()

View file

@ -11,15 +11,16 @@ import (
"github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestSession(t *testing.T) { func TestSession(t *testing.T) {
var err error var err error
serverdb.Instance, err = serverdb.New("sqlite", ":memory:") serverdb.Instance, err = serverdb.New("sqlite", ":memory:")
assert.NoError(t, err) require.NoError(t, err)
db, err := session.NewStore("foo", serverdb.Instance) db, err := session.NewStore("foo", serverdb.Instance)
assert.NoError(t, err) require.NoError(t, err)
clock := clock.NewMock() clock := clock.NewMock()
@ -68,7 +69,7 @@ func TestSession(t *testing.T) {
assert.Equal(t, clock.Now(), lp.session.Finished) assert.Equal(t, clock.Now(), lp.session.Finished)
s, err := db.Sessions() s, err := db.Sessions()
assert.NoError(t, err) require.NoError(t, err)
assert.Len(t, s, 1) assert.Len(t, s, 1)
t.Logf("session: %+v", s) t.Logf("session: %+v", s)
@ -82,7 +83,7 @@ func TestSession(t *testing.T) {
assert.Equal(t, clock.Now(), lp.session.Finished) assert.Equal(t, clock.Now(), lp.session.Finished)
s, err = db.Sessions() s, err = db.Sessions()
assert.NoError(t, err) require.NoError(t, err)
assert.Len(t, s, 1) assert.Len(t, s, 1)
t.Logf("session: %+v", s) t.Logf("session: %+v", s)
} }
@ -90,32 +91,32 @@ func TestSession(t *testing.T) {
func TestCloseSessionsOnStartup_emptyDb(t *testing.T) { func TestCloseSessionsOnStartup_emptyDb(t *testing.T) {
var err error var err error
serverdb.Instance, err = serverdb.New("sqlite", ":memory:") serverdb.Instance, err = serverdb.New("sqlite", ":memory:")
assert.NoError(t, err) require.NoError(t, err)
db, err := session.NewStore("foo", serverdb.Instance) db, err := session.NewStore("foo", serverdb.Instance)
assert.NoError(t, err) require.NoError(t, err)
// assert empty DB is no problem // assert empty DB is no problem
err = db.ClosePendingSessionsInHistory(1000) err = db.ClosePendingSessionsInHistory(1000)
assert.NoError(t, err) require.NoError(t, err)
} }
func TestCloseSessionsOnStartup(t *testing.T) { func TestCloseSessionsOnStartup(t *testing.T) {
var err error var err error
serverdb.Instance, err = serverdb.New("sqlite", ":memory:") serverdb.Instance, err = serverdb.New("sqlite", ":memory:")
assert.NoError(t, err) require.NoError(t, err)
db1, err := session.NewStore("foo", serverdb.Instance) db1, err := session.NewStore("foo", serverdb.Instance)
assert.NoError(t, err) require.NoError(t, err)
db2, err := session.NewStore("bar", serverdb.Instance) db2, err := session.NewStore("bar", serverdb.Instance)
assert.NoError(t, err) require.NoError(t, err)
clock := clock.NewMock() clock := clock.NewMock()
// test data, creates 6 sessions for each loadpoint, 3rd and 6th are "unfinished" // test data, creates 6 sessions for each loadpoint, 3rd and 6th are "unfinished"
var sessions1 []*session.Session = createMockSessions(db1, clock) sessions1 := createMockSessions(db1, clock)
var sessions2 []*session.Session = createMockSessions(db2, clock) sessions2 := createMockSessions(db2, clock)
// write interleaved for two loadpoints // write interleaved for two loadpoints
for index, session := range sessions1 { for index, session := range sessions1 {
@ -124,12 +125,12 @@ func TestCloseSessionsOnStartup(t *testing.T) {
} }
err = db1.ClosePendingSessionsInHistory(1000) err = db1.ClosePendingSessionsInHistory(1000)
assert.NoError(t, err) require.NoError(t, err)
// check fixed sessions for db1 // check fixed sessions for db1
var db1Sessions session.Sessions var db1Sessions session.Sessions
err = serverdb.Instance.Where("Loadpoint = ?", "foo").Order("ID").Find(&db1Sessions).Error err = serverdb.Instance.Where("Loadpoint = ?", "foo").Order("ID").Find(&db1Sessions).Error
assert.NoError(t, err) require.NoError(t, err)
assert.Len(t, db1Sessions, 6) assert.Len(t, db1Sessions, 6)
// check fixed history // check fixed history
@ -146,7 +147,7 @@ func TestCloseSessionsOnStartup(t *testing.T) {
// ensure no side effects on loadpoint 2 data, i.e. data left unfixed // ensure no side effects on loadpoint 2 data, i.e. data left unfixed
var db2Sessions session.Sessions var db2Sessions session.Sessions
err = serverdb.Instance.Where("Loadpoint = ?", "bar").Order("ID").Find(&db2Sessions).Error err = serverdb.Instance.Where("Loadpoint = ?", "bar").Order("ID").Find(&db2Sessions).Error
assert.NoError(t, err) require.NoError(t, err)
assert.Len(t, db2Sessions, 6) assert.Len(t, db2Sessions, 6)
for i, s := range db2Sessions { for i, s := range db2Sessions {
@ -163,8 +164,7 @@ func TestCloseSessionsOnStartup(t *testing.T) {
func createMockSessions(db *session.DB, clock *clock.Mock) []*session.Session { func createMockSessions(db *session.DB, clock *clock.Mock) []*session.Session {
var sessions []*session.Session var sessions []*session.Session
for i := 1; i <= 6; i++ { for i := 1; i <= 6; i++ {
meter1Start := float64(i * 10)
var meter1Start float64 = float64(i * 10)
session := db.New(meter1Start) session := db.New(meter1Start)
session.Created = clock.Now().Add(1 * time.Minute) session.Created = clock.Now().Add(1 * time.Minute)
@ -175,7 +175,7 @@ func createMockSessions(db *session.DB, clock *clock.Mock) []*session.Session {
} }
session.Finished = clock.Now().Add(2 * time.Minute) session.Finished = clock.Now().Add(2 * time.Minute)
meterStop := float64(meter1Start + 10) meterStop := meter1Start + 10
session.MeterStop = &meterStop session.MeterStop = &meterStop
session.ChargedEnergy = 10 session.ChargedEnergy = 10
sessions = append(sessions, session) sessions = append(sessions, session)

View file

@ -8,6 +8,7 @@ import (
"github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestSyncCharger(t *testing.T) { func TestSyncCharger(t *testing.T) {
@ -42,7 +43,7 @@ func TestSyncCharger(t *testing.T) {
enabled: tc.expected, enabled: tc.expected,
} }
assert.NoError(t, lp.syncCharger()) require.NoError(t, lp.syncCharger())
assert.Equal(t, tc.corrected, lp.enabled) assert.Equal(t, tc.corrected, lp.enabled)
} }
} }

View file

@ -33,6 +33,8 @@ func (n *Null) ChargingTime() (time.Duration, error) {
} }
func createChannels(t *testing.T) (chan util.Param, chan push.Event, chan *Loadpoint) { func createChannels(t *testing.T) (chan util.Param, chan push.Event, chan *Loadpoint) {
t.Helper()
uiChan := make(chan util.Param) uiChan := make(chan util.Param)
pushChan := make(chan push.Event) pushChan := make(chan push.Event)
lpChan := make(chan *Loadpoint) lpChan := make(chan *Loadpoint)
@ -67,6 +69,8 @@ func attachChannels(lp *Loadpoint, uiChan chan util.Param, pushChan chan push.Ev
} }
func attachListeners(t *testing.T, lp *Loadpoint) { func attachListeners(t *testing.T, lp *Loadpoint) {
t.Helper()
Voltage = 230 // V Voltage = 230 // V
if charger, ok := lp.charger.(*api.MockCharger); ok && charger != nil { if charger, ok := lp.charger.(*api.MockCharger); ok && charger != nil {
@ -421,7 +425,7 @@ func TestDisableAndEnableAtTargetSoc(t *testing.T) {
attachListeners(t, lp) attachListeners(t, lp)
lp.enabled = true lp.enabled = true
lp.chargeCurrent = float64(minA) lp.chargeCurrent = minA
lp.status = api.StatusC lp.status = api.StatusC
t.Log("charging below soc target") t.Log("charging below soc target")
@ -487,7 +491,7 @@ func TestSetModeAndSocAtDisconnect(t *testing.T) {
attachListeners(t, lp) attachListeners(t, lp)
lp.enabled = true lp.enabled = true
lp.chargeCurrent = float64(minA) lp.chargeCurrent = minA
lp.mode = api.ModeNow lp.mode = api.ModeNow
t.Log("charging at min") t.Log("charging at min")
@ -512,6 +516,8 @@ func TestSetModeAndSocAtDisconnect(t *testing.T) {
// cacheExpecter can be used to verify asynchronously written values from cache // cacheExpecter can be used to verify asynchronously written values from cache
func cacheExpecter(t *testing.T, lp *Loadpoint) (*util.Cache, func(key string, val interface{})) { func cacheExpecter(t *testing.T, lp *Loadpoint) (*util.Cache, func(key string, val interface{})) {
t.Helper()
// attach cache for verifying values // attach cache for verifying values
paramC := make(chan util.Param) paramC := make(chan util.Param)
lp.uiChan = paramC lp.uiChan = paramC
@ -554,7 +560,7 @@ func TestChargedEnergyAtDisconnect(t *testing.T) {
attachListeners(t, lp) attachListeners(t, lp)
lp.enabled = true lp.enabled = true
lp.chargeCurrent = float64(maxA) lp.chargeCurrent = maxA
lp.mode = api.ModeNow lp.mode = api.ModeNow
// attach cache for verifying values // attach cache for verifying values

View file

@ -10,6 +10,7 @@ import (
"github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func rates(prices []float64, start time.Time, slotDuration time.Duration) api.Rates { func rates(prices []float64, start time.Time, slotDuration time.Duration) api.Rates {
@ -43,13 +44,13 @@ func TestPlan(t *testing.T) {
} }
rates, err := trf.Rates() rates, err := trf.Rates()
assert.NoError(t, err) require.NoError(t, err)
slices.SortStableFunc(rates, sortByCost) slices.SortStableFunc(rates, sortByCost)
{ {
plan := p.plan(rates, time.Hour, clock.Now()) plan := p.plan(rates, time.Hour, clock.Now())
assert.Equal(t, 0, len(plan)) assert.Empty(t, plan)
} }
tc := []struct { tc := []struct {
@ -81,7 +82,7 @@ func TestPlan(t *testing.T) {
}, },
{ {
"plan (30)30-0-60-0-0-0", "plan (30)30-0-60-0-0-0",
time.Duration(90 * time.Minute), 90 * time.Minute,
clock.Now(), clock.Now(),
clock.Now().Add(6 * time.Hour), clock.Now().Add(6 * time.Hour),
clock.Now().Add(30 * time.Minute), clock.Now().Add(30 * time.Minute),
@ -105,7 +106,7 @@ func TestPlan(t *testing.T) {
}, },
{ {
"plan (30)30-0-60-0-0-0", "plan (30)30-0-60-0-0-0",
time.Duration(90 * time.Minute), 90 * time.Minute,
clock.Now().Add(30 * time.Minute), clock.Now().Add(30 * time.Minute),
clock.Now().Add(6 * time.Hour), clock.Now().Add(6 * time.Hour),
clock.Now().Add(30 * time.Minute), clock.Now().Add(30 * time.Minute),
@ -133,12 +134,12 @@ func TestNilTariff(t *testing.T) {
} }
plan, err := p.Plan(time.Hour, clock.Now().Add(30*time.Minute)) plan, err := p.Plan(time.Hour, clock.Now().Add(30*time.Minute))
assert.NoError(t, err) require.NoError(t, err)
assert.True(t, !SlotAt(clock.Now(), plan).IsEmpty(), "should start past start time") assert.False(t, SlotAt(clock.Now(), plan).IsEmpty(), "should start past start time")
plan, err = p.Plan(time.Hour, clock.Now().Add(-30*time.Minute)) plan, err = p.Plan(time.Hour, clock.Now().Add(-30*time.Minute))
assert.NoError(t, err) require.NoError(t, err)
assert.False(t, !SlotAt(clock.Now(), plan).IsEmpty(), "should not start past target time") assert.True(t, SlotAt(clock.Now(), plan).IsEmpty(), "should not start past target time")
} }
func TestFlatTariffTargetInThePast(t *testing.T) { func TestFlatTariffTargetInThePast(t *testing.T) {
@ -155,12 +156,12 @@ func TestFlatTariffTargetInThePast(t *testing.T) {
} }
plan, err := p.Plan(time.Hour, clock.Now().Add(30*time.Minute)) plan, err := p.Plan(time.Hour, clock.Now().Add(30*time.Minute))
assert.NoError(t, err) require.NoError(t, err)
assert.True(t, !SlotAt(clock.Now(), plan).IsEmpty(), "should start past start time") assert.False(t, SlotAt(clock.Now(), plan).IsEmpty(), "should start past start time")
plan, err = p.Plan(time.Hour, clock.Now().Add(-30*time.Minute)) plan, err = p.Plan(time.Hour, clock.Now().Add(-30*time.Minute))
assert.NoError(t, err) require.NoError(t, err)
assert.False(t, !SlotAt(clock.Now(), plan).IsEmpty(), "should not start past target time") assert.True(t, SlotAt(clock.Now(), plan).IsEmpty(), "should not start past target time")
} }
func TestFlatTariffLongSlots(t *testing.T) { func TestFlatTariffLongSlots(t *testing.T) {
@ -181,13 +182,13 @@ func TestFlatTariffLongSlots(t *testing.T) {
// expect 00:00-01:00 UTC // expect 00:00-01:00 UTC
plan, err := p.Plan(time.Hour, clock.Now().Add(2*time.Hour)) plan, err := p.Plan(time.Hour, clock.Now().Add(2*time.Hour))
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, api.Rate{Start: clock.Now(), End: clock.Now().Add(time.Hour)}, SlotAt(clock.Now(), plan)) assert.Equal(t, api.Rate{Start: clock.Now(), End: clock.Now().Add(time.Hour)}, SlotAt(clock.Now(), plan))
assert.Equal(t, api.Rate{}, SlotAt(clock.Now().Add(time.Hour), plan)) assert.Equal(t, api.Rate{}, SlotAt(clock.Now().Add(time.Hour), plan))
// expect 00:00-01:00 UTC // expect 00:00-01:00 UTC
plan, err = p.Plan(time.Hour, clock.Now().Add(time.Hour)) plan, err = p.Plan(time.Hour, clock.Now().Add(time.Hour))
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, api.Rate{Start: clock.Now(), End: clock.Now().Add(time.Hour)}, SlotAt(clock.Now(), plan)) assert.Equal(t, api.Rate{Start: clock.Now(), End: clock.Now().Add(time.Hour)}, SlotAt(clock.Now(), plan))
} }
@ -205,11 +206,11 @@ func TestTargetAfterKnownPrices(t *testing.T) {
} }
plan, err := p.Plan(40*time.Minute, clock.Now().Add(2*time.Hour)) // charge efficiency does not allow to test with 1h plan, err := p.Plan(40*time.Minute, clock.Now().Add(2*time.Hour)) // charge efficiency does not allow to test with 1h
assert.NoError(t, err) require.NoError(t, err)
assert.False(t, !SlotAt(clock.Now(), plan).IsEmpty(), "should not start if car can be charged completely after known prices ") assert.False(t, !SlotAt(clock.Now(), plan).IsEmpty(), "should not start if car can be charged completely after known prices ")
plan, err = p.Plan(2*time.Hour, clock.Now().Add(2*time.Hour)) plan, err = p.Plan(2*time.Hour, clock.Now().Add(2*time.Hour))
assert.NoError(t, err) require.NoError(t, err)
assert.True(t, !SlotAt(clock.Now(), plan).IsEmpty(), "should start if car can not be charged completely after known prices ") assert.True(t, !SlotAt(clock.Now(), plan).IsEmpty(), "should start if car can not be charged completely after known prices ")
} }
@ -227,10 +228,10 @@ func TestChargeAfterTargetTime(t *testing.T) {
} }
plan, err := p.Plan(time.Hour, clock.Now()) plan, err := p.Plan(time.Hour, clock.Now())
assert.NoError(t, err) require.NoError(t, err)
assert.False(t, !SlotAt(clock.Now(), plan).IsEmpty(), "should not start past target time") assert.True(t, SlotAt(clock.Now(), plan).IsEmpty(), "should not start past target time")
plan, err = p.Plan(time.Hour, clock.Now().Add(-time.Hour)) plan, err = p.Plan(time.Hour, clock.Now().Add(-time.Hour))
assert.NoError(t, err) require.NoError(t, err)
assert.False(t, !SlotAt(clock.Now(), plan).IsEmpty(), "should not start past target time") assert.True(t, SlotAt(clock.Now(), plan).IsEmpty(), "should not start past target time")
} }

View file

@ -8,6 +8,7 @@ import (
"github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestDetermineBatteryMode(t *testing.T) { func TestDetermineBatteryMode(t *testing.T) {
@ -49,7 +50,6 @@ func TestDetermineBatteryMode(t *testing.T) {
} }
func TestUpdateBatteryMode(t *testing.T) { func TestUpdateBatteryMode(t *testing.T) {
expBatMode := api.BatteryHold expBatMode := api.BatteryHold
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
@ -70,6 +70,6 @@ func TestUpdateBatteryMode(t *testing.T) {
} }
err := s.updateBatteryMode(expBatMode) err := s.updateBatteryMode(expBatMode)
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, expBatMode, s.GetBatteryMode()) assert.Equal(t, expBatMode, s.GetBatteryMode())
} }

View file

@ -37,51 +37,81 @@ func TestGreenShare(t *testing.T) {
grid, pv, battery, home, lp float64 grid, pv, battery, home, lp float64
greenShareTotal, greenShareHome, greenShareLoadpoints float64 greenShareTotal, greenShareHome, greenShareLoadpoints float64
}{ }{
{"half grid, half pv, green home", {
"half grid, half pv, green home",
1000, 1000, 0, 1000, 1000, 1000, 1000, 0, 1000, 1000,
0.5, 1, 0}, 0.5, 1, 0,
{"half grid, half pv, no home", },
{
"half grid, half pv, no home",
1000, 1000, 0, 0, 2000, 1000, 1000, 0, 0, 2000,
0.5, 1, 0.5}, 0.5, 1, 0.5,
{"half grid, half pv, no lp", },
{
"half grid, half pv, no lp",
2500, 2500, 0, 5000, 0, 2500, 2500, 0, 5000, 0,
0.5, 0.5, 0}, 0.5, 0.5, 0,
{"full pv", },
{
"full pv",
0, 5000, 0, 1000, 4000, 0, 5000, 0, 1000, 4000,
1, 1, 1}, 1, 1, 1,
{"full grid", },
{
"full grid",
5000, 0, 0, 1000, 4000, 5000, 0, 0, 1000, 4000,
0, 0, 0}, 0, 0, 0,
{"half grid, half battery, green home", },
{
"half grid, half battery, green home",
1000, 0, 1000, 1000, 1000, 1000, 0, 1000, 1000, 1000,
0.5, 1, 0}, 0.5, 1, 0,
{"half grid, half battery, no home", },
{
"half grid, half battery, no home",
1000, 0, 1000, 0, 2000, 1000, 0, 1000, 0, 2000,
0.5, 1, 0.5}, 0.5, 1, 0.5,
{"half grid, half battery, no lp", },
{
"half grid, half battery, no lp",
1000, 0, 1000, 2000, 0, 1000, 0, 1000, 2000, 0,
0.5, 0.5, 0}, 0.5, 0.5, 0,
{"full pv, pv export", },
{
"full pv, pv export",
-5000, 10000, 0, 1000, 4000, -5000, 10000, 0, 1000, 4000,
1, 1, 1}, 1, 1, 1,
{"full pv, pv export, no lp", },
{
"full pv, pv export, no lp",
-5000, 10000, 0, 5000, 0, -5000, 10000, 0, 5000, 0,
1, 1, 1}, 1, 1, 1,
{"full pv, pv export, battery charge", },
{
"full pv, pv export, battery charge",
-2500, 10000, -2500, 1000, 4000, -2500, 10000, -2500, 1000, 4000,
1, 1, 1}, 1, 1, 1,
{"full grid, battery charge", },
{
"full grid, battery charge",
3000, 0, -1000, 1000, 1000, 3000, 0, -1000, 1000, 1000,
0, 0, 0}, 0, 0, 0,
{"full grid, battery charge, no lp", },
{
"full grid, battery charge, no lp",
2000, 0, -1000, 1000, 0, 2000, 0, -1000, 1000, 0,
0, 0, 0}, 0, 0, 0,
{"half grid, half pv, battery charge, no lp", },
{
"half grid, half pv, battery charge, no lp",
1000, 1000, -1000, 1000, 0, 1000, 1000, -1000, 1000, 0,
1, 1, 0}, 1, 1, 0,
{"half grid, half pv, battery charge, home, lp", },
{
"half grid, half pv, battery charge, home, lp",
1000, 1000, -1000, 500, 500, 1000, 1000, -1000, 500, 500,
1, 1, 1}, 1, 1, 1,
},
} }
for _, tc := range tc { for _, tc := range tc {

View file

@ -50,7 +50,7 @@ func (s *Estimator) Reset() {
s.prevSoc = 0 s.prevSoc = 0
s.prevChargedEnergy = 0 s.prevChargedEnergy = 0
s.initialSoc = 0 s.initialSoc = 0
s.capacity = float64(s.vehicle.Capacity()) * 1e3 // cache to simplify debugging s.capacity = s.vehicle.Capacity() * 1e3 // cache to simplify debugging
s.virtualCapacity = s.capacity / ChargeEfficiency // initial capacity taking efficiency into account s.virtualCapacity = s.capacity / ChargeEfficiency // initial capacity taking efficiency into account
s.energyPerSocStep = s.virtualCapacity / 100 s.energyPerSocStep = s.virtualCapacity / 100
s.minChargePower = 1000 // default 1 kW s.minChargePower = 1000 // default 1 kW

View file

@ -17,14 +17,14 @@ func TestTimer(t *testing.T) {
// start // start
at.Start() at.Start()
clck.Add(10 * time.Second) clck.Add(10 * time.Second)
require.Equal(t, at.Expired(), false) require.False(t, at.Expired())
// wait another 20 sec to expire the timer - this will reset the timer as well // wait another 20 sec to expire the timer - this will reset the timer as well
clck.Add(wakeupTimeout + 10*time.Second) clck.Add(wakeupTimeout + 10*time.Second)
require.Equal(t, at.Expired(), true) require.True(t, at.Expired())
// start // start
at.Start() at.Start()
clck.Add(time.Minute) clck.Add(time.Minute)
require.Equal(t, at.Expired(), true) require.True(t, at.Expired())
} }

View file

@ -11,7 +11,6 @@ import (
"github.com/evcc-io/evcc/hems/ocpp/profile" "github.com/evcc-io/evcc/hems/ocpp/profile"
"github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/machine" "github.com/evcc-io/evcc/util/machine"
ocpp16 "github.com/lorenzodonini/ocpp-go/ocpp1.6" ocpp16 "github.com/lorenzodonini/ocpp-go/ocpp1.6"
ocppcore "github.com/lorenzodonini/ocpp-go/ocpp1.6/core" ocppcore "github.com/lorenzodonini/ocpp-go/ocpp1.6/core"
"github.com/lorenzodonini/ocpp-go/ws" "github.com/lorenzodonini/ocpp-go/ws"

View file

@ -84,7 +84,6 @@ func (c *API) updateValues() error {
req, err := request.New(http.MethodPost, uri, strings.NewReader(data), map[string]string{ req, err := request.New(http.MethodPost, uri, strings.NewReader(data), map[string]string{
"Content-Type": "text/plain", "Content-Type": "text/plain",
}) })
if err != nil { if err != nil {
return err return err
} }

View file

@ -6,17 +6,17 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
// Test MethodResponse response // Test MethodResponse response
func TestUnmarshalMethodResponse(t *testing.T) { func TestUnmarshalMethodResponse(t *testing.T) {
{ {
// BidCos-RF (Port 2001) getParamset measure-channel response test // BidCos-RF (Port 2001) getParamset measure-channel response test
var res MethodResponse var res MethodResponse
xmlstr := `<?xml version="1.0" encoding="iso-8859-1"?><methodResponse><params><param><value><struct><member><name>IEC_ENERGY_COUNTER</name><value><double>689.586500</double></value></member><member><name>IEC_POWER</name><value><double>166.390000</double></value></member></struct></value></param></params></methodResponse>` xmlstr := `<?xml version="1.0" encoding="iso-8859-1"?><methodResponse><params><param><value><struct><member><name>IEC_ENERGY_COUNTER</name><value><double>689.586500</double></value></member><member><name>IEC_POWER</name><value><double>166.390000</double></value></member></struct></value></param></params></methodResponse>`
assert.NoError(t, xml.Unmarshal([]byte(strings.Replace(string(xmlstr), "iso-8859-1", "UTF-8", 1)), &res)) require.NoError(t, xml.Unmarshal([]byte(strings.Replace(xmlstr, "iso-8859-1", "UTF-8", 1)), &res))
assert.Equal(t, "IEC_ENERGY_COUNTER", res.Member[0].Name) assert.Equal(t, "IEC_ENERGY_COUNTER", res.Member[0].Name)
assert.Equal(t, float64(689.586500), res.Member[0].Value.CCUFloat) assert.Equal(t, float64(689.586500), res.Member[0].Value.CCUFloat)
@ -27,7 +27,7 @@ func TestUnmarshalMethodResponse(t *testing.T) {
var res MethodResponse var res MethodResponse
xmlstr := `<?xml version="1.0" encoding="ISO-8859-1"?><methodResponse><params><param><value><struct><member><name>VOLTAGE</name><value><double>230.6</double></value></member><member><name>POWER_STATUS</name><value><i4>0</i4></value></member><member><name>ENERGY_COUNTER</name><value><double>10888.7</double></value></member><member><name>CURRENT_STATUS</name><value><i4>0</i4></value></member><member><name>FREQUENCY</name><value><double>49.97</double></value></member><member><name>ENERGY_COUNTER_OVERFLOW</name><value><boolean>0</boolean></value></member><member><name>POWER</name><value><double>0.05</double></value></member><member><name>VOLTAGE_STATUS</name><value><i4>0</i4></value></member><member><name>CURRENT</name><value><double>0.0</double></value></member><member><name>FREQUENCY_STATUS</name><value><i4>0</i4></value></member></struct></value></param></params></methodResponse>` xmlstr := `<?xml version="1.0" encoding="ISO-8859-1"?><methodResponse><params><param><value><struct><member><name>VOLTAGE</name><value><double>230.6</double></value></member><member><name>POWER_STATUS</name><value><i4>0</i4></value></member><member><name>ENERGY_COUNTER</name><value><double>10888.7</double></value></member><member><name>CURRENT_STATUS</name><value><i4>0</i4></value></member><member><name>FREQUENCY</name><value><double>49.97</double></value></member><member><name>ENERGY_COUNTER_OVERFLOW</name><value><boolean>0</boolean></value></member><member><name>POWER</name><value><double>0.05</double></value></member><member><name>VOLTAGE_STATUS</name><value><i4>0</i4></value></member><member><name>CURRENT</name><value><double>0.0</double></value></member><member><name>FREQUENCY_STATUS</name><value><i4>0</i4></value></member></struct></value></param></params></methodResponse>`
assert.NoError(t, xml.Unmarshal([]byte(strings.Replace(string(xmlstr), "ISO-8859-1", "UTF-8", 1)), &res)) require.NoError(t, xml.Unmarshal([]byte(strings.Replace(xmlstr, "ISO-8859-1", "UTF-8", 1)), &res))
assert.Equal(t, "ENERGY_COUNTER", res.Member[2].Name) assert.Equal(t, "ENERGY_COUNTER", res.Member[2].Name)
assert.Equal(t, float64(10888.7), res.Member[2].Value.CCUFloat) assert.Equal(t, float64(10888.7), res.Member[2].Value.CCUFloat)
@ -40,10 +40,10 @@ func TestUnmarshalMethodResponse(t *testing.T) {
var res MethodResponse var res MethodResponse
xmlstr := `<?xml version="1.0" encoding="ISO-8859-1"?><methodResponse><params><param><value><struct><member><name>SECTION_STATUS</name><value><i4>0</i4></value></member><member><name>PROCESS</name><value><i4>0</i4></value></member><member><name>STATE</name><value><boolean>1</boolean></value></member><member><name>SECTION</name><value><i4>2</i4></value></member></struct></value></param></params></methodResponse>` xmlstr := `<?xml version="1.0" encoding="ISO-8859-1"?><methodResponse><params><param><value><struct><member><name>SECTION_STATUS</name><value><i4>0</i4></value></member><member><name>PROCESS</name><value><i4>0</i4></value></member><member><name>STATE</name><value><boolean>1</boolean></value></member><member><name>SECTION</name><value><i4>2</i4></value></member></struct></value></param></params></methodResponse>`
assert.NoError(t, xml.Unmarshal([]byte(strings.Replace(string(xmlstr), "ISO-8859-1", "UTF-8", 1)), &res)) require.NoError(t, xml.Unmarshal([]byte(strings.Replace(xmlstr, "ISO-8859-1", "UTF-8", 1)), &res))
assert.Equal(t, "STATE", res.Member[2].Name) assert.Equal(t, "STATE", res.Member[2].Name)
assert.Equal(t, true, res.Member[2].Value.CCUBool) assert.True(t, res.Member[2].Value.CCUBool)
} }
{ {
@ -51,12 +51,11 @@ func TestUnmarshalMethodResponse(t *testing.T) {
var res MethodResponse var res MethodResponse
xmlstr := `<?xml version="1.0" encoding="ISO-8859-1"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><i4>-2</i4></value></member><member><name>faultString</name><value>Invalid device</value></member></struct></value></fault></methodResponse>` xmlstr := `<?xml version="1.0" encoding="ISO-8859-1"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><i4>-2</i4></value></member><member><name>faultString</name><value>Invalid device</value></member></struct></value></fault></methodResponse>`
assert.NoError(t, xml.Unmarshal([]byte(strings.Replace(string(xmlstr), "ISO-8859-1", "UTF-8", 1)), &res)) require.NoError(t, xml.Unmarshal([]byte(strings.Replace(xmlstr, "ISO-8859-1", "UTF-8", 1)), &res))
assert.Equal(t, "faultCode", res.Fault[0].Name) assert.Equal(t, "faultCode", res.Fault[0].Name)
assert.Equal(t, int64(-2), res.Fault[0].Value.CCUInt) assert.Equal(t, int64(-2), res.Fault[0].Value.CCUInt)
assert.Equal(t, "faultString", res.Fault[1].Name) assert.Equal(t, "faultString", res.Fault[1].Name)
assert.Equal(t, "Invalid device", res.Fault[1].Value.CCUString) assert.Equal(t, "Invalid device", res.Fault[1].Value.CCUString)
} }
} }

View file

@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
// Test ApiResponse // Test ApiResponse
@ -13,7 +14,7 @@ func TestUnmarshalApiResponse(t *testing.T) {
var res ApiResponse var res ApiResponse
jsonstr := `{"product_type": "HWE-SKT","product_name": "P1 Meter","serial": "3c39e7aabbcc","firmware_version": "2.11","api_version": "v1"}` jsonstr := `{"product_type": "HWE-SKT","product_name": "P1 Meter","serial": "3c39e7aabbcc","firmware_version": "2.11","api_version": "v1"}`
assert.NoError(t, json.Unmarshal([]byte(jsonstr), &res)) require.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
assert.Equal(t, "HWE-SKT", res.ProductType) assert.Equal(t, "HWE-SKT", res.ProductType)
assert.Equal(t, "v1", res.ApiVersion) assert.Equal(t, "v1", res.ApiVersion)
@ -26,9 +27,9 @@ func TestUnmarshalStateResponse(t *testing.T) {
var res StateResponse var res StateResponse
jsonstr := `{"power_on": true,"switch_lock": false,"brightness": 255}` jsonstr := `{"power_on": true,"switch_lock": false,"brightness": 255}`
assert.NoError(t, json.Unmarshal([]byte(jsonstr), &res)) require.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
assert.Equal(t, true, res.PowerOn) assert.True(t, res.PowerOn)
} }
} }
@ -38,7 +39,7 @@ func TestUnmarshalDataResponse(t *testing.T) {
var res DataResponse var res DataResponse
jsonstr := `{"wifi_ssid": "My Wi-Fi","wifi_strength": 100,"total_power_import_t1_kwh": 30.511,"total_power_export_t1_kwh": 85.951,"active_power_w": 543,"active_power_l1_w": 676}` jsonstr := `{"wifi_ssid": "My Wi-Fi","wifi_strength": 100,"total_power_import_t1_kwh": 30.511,"total_power_export_t1_kwh": 85.951,"active_power_w": 543,"active_power_l1_w": 676}`
assert.NoError(t, json.Unmarshal([]byte(jsonstr), &res)) require.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
assert.Equal(t, float64(30.511), res.TotalPowerImportT1kWh+res.TotalPowerImportT2kWh+res.TotalPowerImportT3kWh+res.TotalPowerImportT4kWh) assert.Equal(t, float64(30.511), res.TotalPowerImportT1kWh+res.TotalPowerImportT2kWh+res.TotalPowerImportT3kWh+res.TotalPowerImportT4kWh)
assert.Equal(t, float64(543), res.ActivePowerW) assert.Equal(t, float64(543), res.ActivePowerW)

View file

@ -155,7 +155,7 @@ func NewPowerWall(uri, usage, user, password string, cache time.Duration, refres
} }
batteryCapacity = func() float64 { batteryCapacity = func() float64 {
return float64(res.NominalFullPackEnergy) / 1e3 return res.NominalFullPackEnergy / 1e3
} }
} }
@ -212,7 +212,7 @@ func (m *PowerWall) batterySoc() (float64, error) {
return 0, err return 0, err
} }
return float64(res.Percentage), err return res.Percentage, err
} }
// decorate soc // decorate soc

View file

@ -9,7 +9,6 @@ import (
"github.com/cenkalti/backoff/v4" "github.com/cenkalti/backoff/v4"
"github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util"
"github.com/mlnoga/rct" "github.com/mlnoga/rct"
) )

View file

@ -145,7 +145,7 @@ func (sh *Switch) TotalEnergy() (float64, error) {
func gen1Energy(devicetype string, energy float64) float64 { func gen1Energy(devicetype string, energy float64) float64 {
// Gen 1 Shelly EM devices are providing Watt hours, Gen 1 Shelly PM devices are providing Watt minutes // Gen 1 Shelly EM devices are providing Watt hours, Gen 1 Shelly PM devices are providing Watt minutes
if !strings.Contains(devicetype, "EM") { if !strings.Contains(devicetype, "EM") {
energy = energy / 60 energy /= 60
} }
return energy return energy
} }

View file

@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
// Test Gen1StatusResponse response // Test Gen1StatusResponse response
@ -14,7 +15,7 @@ func TestUnmarshalGen1StatusResponse(t *testing.T) {
var res Gen1StatusResponse var res Gen1StatusResponse
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}` 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)) require.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
assert.Equal(t, 107875.21666666666, gen1Energy("SHSW-PM", res.Meters[0].Total)) assert.Equal(t, 107875.21666666666, gen1Energy("SHSW-PM", res.Meters[0].Total))
assert.Equal(t, 4711.12, res.Meters[0].Power) assert.Equal(t, 4711.12, res.Meters[0].Power)
@ -25,7 +26,7 @@ func TestUnmarshalGen1StatusResponse(t *testing.T) {
var res Gen1StatusResponse 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}` 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)) require.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
assert.Equal(t, 0.0, gen1Energy("SHSW-1", res.Meters[0].Total)) assert.Equal(t, 0.0, gen1Energy("SHSW-1", res.Meters[0].Total))
assert.Equal(t, 81.5, res.Meters[0].Power) assert.Equal(t, 81.5, res.Meters[0].Power)
@ -36,7 +37,7 @@ func TestUnmarshalGen1StatusResponse(t *testing.T) {
var res Gen1StatusResponse 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}` 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)) require.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
assert.Equal(t, 401472.9, gen1Energy("SHEM", res.EMeters[0].Total)) assert.Equal(t, 401472.9, gen1Energy("SHEM", res.EMeters[0].Total))
assert.Equal(t, -620.34, res.EMeters[0].Power) assert.Equal(t, -620.34, res.EMeters[0].Power)
@ -49,7 +50,7 @@ func TestUnmarshalGen2StatusResponse(t *testing.T) {
var res Gen2StatusResponse var res Gen2StatusResponse
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}}` 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}}`
assert.NoError(t, json.Unmarshal([]byte(jsonstr), &res)) require.NoError(t, json.Unmarshal([]byte(jsonstr), &res))
assert.Equal(t, 5.125, res.Switch0.Aenergy.Total) assert.Equal(t, 5.125, res.Switch0.Aenergy.Total)
assert.Equal(t, 47.11, res.Switch0.Apower) assert.Equal(t, 47.11, res.Switch0.Apower)

View file

@ -100,7 +100,7 @@ func (c *Connection) ChargedEnergy() (float64, error) {
} }
if int64(resp.TodayEnergy) > c.lasttodayenergy { if int64(resp.TodayEnergy) > c.lasttodayenergy {
c.energy = c.energy + (int64(resp.TodayEnergy) - c.lasttodayenergy) c.energy += (int64(resp.TodayEnergy) - c.lasttodayenergy)
} }
c.lasttodayenergy = int64(resp.TodayEnergy) c.lasttodayenergy = int64(resp.TodayEnergy)

View file

@ -114,7 +114,6 @@ func (c *Connection) RelayExists() error {
// Enable implements the api.Charger interface // Enable implements the api.Charger interface
func (c *Connection) Enable(enable bool) error { func (c *Connection) Enable(enable bool) error {
for _, channel := range c.channels { for _, channel := range c.channels {
cmd := fmt.Sprintf("Power%d off", channel) cmd := fmt.Sprintf("Power%d off", channel)
if enable { if enable {
cmd = fmt.Sprintf("Power%d on", channel) cmd = fmt.Sprintf("Power%d on", channel)
@ -191,7 +190,6 @@ func (c *Connection) Enabled() (bool, error) {
enabled = strings.ToUpper(res.StatusSTS.Power8) == "ON" enabled = strings.ToUpper(res.StatusSTS.Power8) == "ON"
default: default:
enabled = strings.ToUpper(res.StatusSTS.Power) == "ON" || strings.ToUpper(res.StatusSTS.Power1) == "ON" enabled = strings.ToUpper(res.StatusSTS.Power) == "ON" || strings.ToUpper(res.StatusSTS.Power1) == "ON"
} }
} }
return enabled, nil return enabled, nil

View file

@ -31,6 +31,8 @@ var acceptable = []string{
func TestTemplates(t *testing.T) { func TestTemplates(t *testing.T) {
templates.TestClass(t, templates.Meter, func(t *testing.T, values map[string]any) { templates.TestClass(t, templates.Meter, func(t *testing.T, values map[string]any) {
t.Helper()
if _, err := NewFromConfig("template", values); err != nil && !test.Acceptable(err, acceptable) { if _, err := NewFromConfig("template", values); err != nil && !test.Acceptable(err, acceptable) {
t.Log(values) t.Log(values)
t.Error(err) t.Error(err)

View file

@ -37,7 +37,7 @@ func (d *Connection) ExecCmd(cmd string, res interface{}) error {
buf := bytes.NewBuffer([]byte{0, 0, 0, 0}) buf := bytes.NewBuffer([]byte{0, 0, 0, 0})
var key byte = 171 // initialization vector var key byte = 171 // initialization vector
for i := 0; i < len(cmd); i++ { for i := 0; i < len(cmd); i++ {
key = key ^ cmd[i] key ^= cmd[i]
_ = buf.WriteByte(key) _ = buf.WriteByte(key)
} }

View file

@ -67,7 +67,7 @@ func process(filepath string) error {
fmt.Fprintln(out, sc.Text()) fmt.Fprintln(out, sc.Text())
} }
if err := os.WriteFile(filepath, []byte(out.String()), 0644); err != nil { if err := os.WriteFile(filepath, []byte(out.String()), 0o644); err != nil {
return fmt.Errorf("%s: %v", filepath, err) return fmt.Errorf("%s: %v", filepath, err)
} }

View file

@ -36,7 +36,8 @@ func TestCachedGetter(t *testing.T) {
expect := func(s struct { expect := func(s struct {
f float64 f float64
e error e error
}) { },
) {
f, e := c.Get() f, e := c.Get()
if f != s.f || e != s.e { if f != s.f || e != s.e {
t.Errorf("unexpected cache value: %f, %v\n", f, e) t.Errorf("unexpected cache value: %f, %v\n", f, e)

View file

@ -37,7 +37,6 @@ func RegisteredClient(log *util.Logger, broker, user, password, clientID string,
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
client, err := registry.Get(key) client, err := registry.Get(key)
if err != nil { if err != nil {
if clientID == "" { if clientID == "" {
clientID = ClientID() clientID = ClientID()

View file

@ -60,7 +60,7 @@ func (h *msgHandler) stringGetter() (string, error) {
return "", err return "", err
} }
return string(v), nil return v, nil
} }
func (h *msgHandler) boolGetter() (bool, error) { func (h *msgHandler) boolGetter() (bool, error) {

View file

@ -31,7 +31,6 @@ func TestRegexDefault(t *testing.T) {
func TestJq(t *testing.T) { func TestJq(t *testing.T) {
for _, uuid := range []string{"a8232ee0-a4ab-11ec-8d36-211f6b082dc8", "08232ee0-a4ab-11ec-8d36-211f6b082dc8"} { for _, uuid := range []string{"a8232ee0-a4ab-11ec-8d36-211f6b082dc8", "08232ee0-a4ab-11ec-8d36-211f6b082dc8"} {
p, err := new(Pipeline).WithJq(fmt.Sprintf(`.data[] | select(.uuid=="%s") | .tuples[0][1]`, uuid)) p, err := new(Pipeline).WithJq(fmt.Sprintf(`.data[] | select(.uuid=="%s") | .tuples[0][1]`, uuid))
require.NoError(t, err) require.NoError(t, err)

View file

@ -5,13 +5,14 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestString(t *testing.T) { func TestString(t *testing.T) {
v := "foo" v := "foo"
SetString("string", v) SetString("string", v)
res, err := String("string") res, err := String("string")
assert.Nil(t, err) require.NoError(t, err)
assert.Equal(t, v, res) assert.Equal(t, v, res)
} }
@ -19,7 +20,7 @@ func TestInt(t *testing.T) {
v := int64(math.MaxInt64) v := int64(math.MaxInt64)
SetInt("int64", v) SetInt("int64", v)
res, err := Int("int64") res, err := Int("int64")
assert.Nil(t, err) require.NoError(t, err)
assert.Equal(t, v, res) assert.Equal(t, v, res)
} }
@ -27,6 +28,6 @@ func TestFloat(t *testing.T) {
v := 3.141 v := 3.141
SetFloat("float64", v) SetFloat("float64", v)
res, err := Float("float64") res, err := Float("float64")
assert.Nil(t, err) require.NoError(t, err)
assert.Equal(t, v, res) assert.Equal(t, v, res)
} }

View file

@ -26,7 +26,7 @@ func (w *influxWriter) WritePoint(p *write.Point) {
} }
func (w *influxWriter) finish() { func (w *influxWriter) finish() {
assert.Equal(w.t, len(w.p), w.idx, "not enough points") assert.Len(w.t, w.p, w.idx, "not enough points")
} }
func TestInfluxTypes(t *testing.T) { func TestInfluxTypes(t *testing.T) {

View file

@ -12,23 +12,24 @@ import (
"github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/modbus" "github.com/evcc-io/evcc/util/modbus"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestConcurrentRead(t *testing.T) { func TestConcurrentRead(t *testing.T) {
l, err := net.Listen("tcp", "localhost:0") l, err := net.Listen("tcp", "localhost:0")
assert.NoError(t, err) require.NoError(t, err)
defer l.Close() defer l.Close()
srv, _ := mbserver.New(&echoHandler{ srv, _ := mbserver.New(&echoHandler{
id: 0, id: 0,
RequestHandler: new(mbserver.DummyHandler), RequestHandler: new(mbserver.DummyHandler),
}) })
assert.NoError(t, srv.Start(l)) require.NoError(t, srv.Start(l))
defer func() { _ = srv.Stop() }() defer func() { _ = srv.Stop() }()
// client // client
conn, err := modbus.NewConnection(l.Addr().String(), "", "", 0, modbus.Tcp, 1) conn, err := modbus.NewConnection(l.Addr().String(), "", "", 0, modbus.Tcp, 1)
assert.NoError(t, err) require.NoError(t, err)
var wg sync.WaitGroup var wg sync.WaitGroup
@ -41,7 +42,7 @@ func TestConcurrentRead(t *testing.T) {
qty := uint16(rand.Int31n(32) + 1) qty := uint16(rand.Int31n(32) + 1)
b, err := conn.ReadInputRegistersWithSlave(uint8(id), addr, qty) b, err := conn.ReadInputRegistersWithSlave(uint8(id), addr, qty)
assert.NoError(t, err) require.NoError(t, err)
if err == nil { if err == nil {
for u := uint16(0); u < qty; u++ { for u := uint16(0); u < qty; u++ {
@ -62,56 +63,56 @@ func TestConcurrentRead(t *testing.T) {
func TestReadCoils(t *testing.T) { func TestReadCoils(t *testing.T) {
// downstream server // downstream server
l, err := net.Listen("tcp", "localhost:0") l, err := net.Listen("tcp", "localhost:0")
assert.NoError(t, err) require.NoError(t, err)
defer l.Close() defer l.Close()
srv, _ := mbserver.New(&echoHandler{ srv, _ := mbserver.New(&echoHandler{
id: 0, id: 0,
RequestHandler: new(mbserver.DummyHandler), RequestHandler: new(mbserver.DummyHandler),
}) })
assert.NoError(t, srv.Start(l)) require.NoError(t, srv.Start(l))
defer func() { _ = srv.Stop() }() defer func() { _ = srv.Stop() }()
// proxy server // proxy server
pl, err := net.Listen("tcp", "localhost:0") pl, err := net.Listen("tcp", "localhost:0")
assert.NoError(t, err) require.NoError(t, err)
defer pl.Close() defer pl.Close()
downstreamConn, err := modbus.NewConnection(l.Addr().String(), "", "", 0, modbus.Tcp, 1) downstreamConn, err := modbus.NewConnection(l.Addr().String(), "", "", 0, modbus.Tcp, 1)
assert.NoError(t, err) require.NoError(t, err)
proxy, _ := mbserver.New(&handler{ proxy, _ := mbserver.New(&handler{
log: util.NewLogger("foo"), log: util.NewLogger("foo"),
conn: downstreamConn, conn: downstreamConn,
}) })
assert.NoError(t, proxy.Start(pl)) require.NoError(t, proxy.Start(pl))
defer func() { _ = proxy.Stop() }() defer func() { _ = proxy.Stop() }()
// test client // test client
{ {
conn, err := modbus.NewConnection(pl.Addr().String(), "", "", 0, modbus.Tcp, 1) conn, err := modbus.NewConnection(pl.Addr().String(), "", "", 0, modbus.Tcp, 1)
assert.NoError(t, err) require.NoError(t, err)
{ // read { // read
b, err := conn.ReadCoilsWithSlave(1, 1, 1) b, err := conn.ReadCoilsWithSlave(1, 1, 1)
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []byte{0x01}, b) assert.Equal(t, []byte{0x01}, b)
b, err = conn.ReadCoilsWithSlave(1, 1, 2) b, err = conn.ReadCoilsWithSlave(1, 1, 2)
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []byte{0x03}, b) assert.Equal(t, []byte{0x03}, b)
b, err = conn.ReadCoilsWithSlave(1, 1, 9) b, err = conn.ReadCoilsWithSlave(1, 1, 9)
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []byte{0xFF, 0x01}, b) assert.Equal(t, []byte{0xFF, 0x01}, b)
} }
{ // write { // write
b, err := conn.WriteSingleCoilWithSlave(1, 1, 0xFF00) b, err := conn.WriteSingleCoilWithSlave(1, 1, 0xFF00)
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []byte{0xFF, 0x00}, b) assert.Equal(t, []byte{0xFF, 0x00}, b)
b, err = conn.WriteMultipleCoilsWithSlave(1, 1, 9, []byte{0xFF, 0x01}) b, err = conn.WriteMultipleCoilsWithSlave(1, 1, 9, []byte{0xFF, 0x01})
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []byte{0x00, 0x09}, b) assert.Equal(t, []byte{0x00, 0x09}, b)
} }
} }

View file

@ -167,7 +167,7 @@ func (m *MQTT) listenSiteSetters(topic string, site site.API) error {
var err error var err error
if err == nil { if err == nil {
err = m.Handler.ListenSetter(m.root+"/site/prioritySoc", func(payload string) error { err = m.Handler.ListenSetter(topic+"/site/prioritySoc", func(payload string) error {
val, err := parseFloat(payload) val, err := parseFloat(payload)
if err == nil { if err == nil {
err = site.SetPrioritySoc(val) err = site.SetPrioritySoc(val)
@ -177,7 +177,7 @@ func (m *MQTT) listenSiteSetters(topic string, site site.API) error {
} }
if err == nil { if err == nil {
err = m.Handler.ListenSetter(m.root+"/site/bufferSoc", func(payload string) error { err = m.Handler.ListenSetter(topic+"/site/bufferSoc", func(payload string) error {
val, err := parseFloat(payload) val, err := parseFloat(payload)
if err == nil { if err == nil {
err = site.SetBufferSoc(val) err = site.SetBufferSoc(val)
@ -187,7 +187,7 @@ func (m *MQTT) listenSiteSetters(topic string, site site.API) error {
} }
if err == nil { if err == nil {
err = m.Handler.ListenSetter(m.root+"/site/bufferStartSoc", func(payload string) error { err = m.Handler.ListenSetter(topic+"/site/bufferStartSoc", func(payload string) error {
val, err := parseFloat(payload) val, err := parseFloat(payload)
if err == nil { if err == nil {
err = site.SetBufferStartSoc(val) err = site.SetBufferStartSoc(val)
@ -197,7 +197,7 @@ func (m *MQTT) listenSiteSetters(topic string, site site.API) error {
} }
if err == nil { if err == nil {
err = m.Handler.ListenSetter(m.root+"/site/residualPower", func(payload string) error { err = m.Handler.ListenSetter(topic+"/site/residualPower", func(payload string) error {
val, err := parseFloat(payload) val, err := parseFloat(payload)
if err == nil { if err == nil {
err = site.SetResidualPower(val) err = site.SetResidualPower(val)
@ -207,7 +207,7 @@ func (m *MQTT) listenSiteSetters(topic string, site site.API) error {
} }
if err == nil { if err == nil {
err = m.Handler.ListenSetter(m.root+"/site/smartCostLimit", func(payload string) error { err = m.Handler.ListenSetter(topic+"/site/smartCostLimit", func(payload string) error {
val, err := parseFloat(payload) val, err := parseFloat(payload)
if err == nil { if err == nil {
err = site.SetSmartCostLimit(val) err = site.SetSmartCostLimit(val)

View file

@ -6,6 +6,7 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestEncode(t *testing.T) { func TestEncode(t *testing.T) {
@ -27,7 +28,7 @@ func TestEncode(t *testing.T) {
for _, tc := range tc { for _, tc := range tc {
out, err := encode(tc.in) out, err := encode(tc.in)
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, tc.out, out) assert.Equal(t, tc.out, out)
} }
} }
@ -44,7 +45,7 @@ func TestEncodeSlice(t *testing.T) {
for _, tc := range tc { for _, tc := range tc {
out, err := encodeSlice(tc.in) out, err := encodeSlice(tc.in)
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, tc.out, out) assert.Equal(t, tc.out, out)
} }
} }

View file

@ -113,7 +113,7 @@ var zones = map[string][]string{
"10YPT-REN------W": {"LFA|PT", "LFB|PT", "MBA|PT", "SCA|PT", "CTA|PT", "Portugal (PT)", "BZN|PT"}, "10YPT-REN------W": {"LFA|PT", "LFB|PT", "MBA|PT", "SCA|PT", "CTA|PT", "Portugal (PT)", "BZN|PT"},
"10YRO-TEL------P": {"BZN|RO", "Romania (RO)", "CTA|RO", "SCA|RO", "MBA|RO", "LFB|RO", "LFA|RO"}, "10YRO-TEL------P": {"BZN|RO", "Romania (RO)", "CTA|RO", "SCA|RO", "MBA|RO", "LFB|RO", "LFA|RO"},
"10YSE-1--------K": {"MBA|SE", "SCA|SE", "CTA|SE", "Sweden (SE)"}, "10YSE-1--------K": {"MBA|SE", "SCA|SE", "CTA|SE", "Sweden (SE)"},
"10YSI-ELES-----O": {"Slovenia (SI)", "BZN|SI", "CTA|SI", "SCA|SI", "MBA|SI", "LFA|SI"}, "10YSI-EELS-----O": {"Slovenia (SI)", "BZN|SI", "CTA|SI", "SCA|SI", "MBA|SI", "LFA|SI"},
"10YSK-SEPS-----K": {"LFA|SK", "LFB|SK", "MBA|SK", "SCA|SK", "CTA|SK", "BZN|SK", "Slovakia (SK)"}, "10YSK-SEPS-----K": {"LFA|SK", "LFB|SK", "MBA|SK", "SCA|SK", "CTA|SK", "BZN|SK", "Slovakia (SK)"},
"10YTR-TEIAS----W": {"Turkey (TR)", "BZN|TR", "CTA|TR", "SCA|TR", "MBA|TR", "LFB|TR", "LFA|TR"}, "10YTR-TEIAS----W": {"Turkey (TR)", "BZN|TR", "CTA|TR", "SCA|TR", "MBA|TR", "LFB|TR", "LFA|TR"},
"10YUA-WEPS-----0": {"LFA|UA-BEI", "LFB|UA-BEI", "MBA|UA-BEI", "SCA|UA-BEI", "CTA|UA-BEI", "BZN|UA-BEI"}, "10YUA-WEPS-----0": {"LFA|UA-BEI", "LFB|UA-BEI", "MBA|UA-BEI", "SCA|UA-BEI", "CTA|UA-BEI", "BZN|UA-BEI"},

View file

@ -88,7 +88,7 @@ func ParseDays(s string) ([]Day, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
res = append(res, Day(from%7)) res = append(res, from%7)
if len(fromto) == 2 { if len(fromto) == 2 {
to, err := ParseDay(fromto[1]) to, err := ParseDay(fromto[1])

View file

@ -4,43 +4,44 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestParseDays(t *testing.T) { func TestParseDays(t *testing.T) {
d, err := ParseDays(" sunday ") d, err := ParseDays(" sunday ")
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []Day{Sunday}, d) assert.Equal(t, []Day{Sunday}, d)
d, err = ParseDays("sun") d, err = ParseDays("sun")
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []Day{Sunday}, d) assert.Equal(t, []Day{Sunday}, d)
d, err = ParseDays("so") d, err = ParseDays("so")
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []Day{Sunday}, d) assert.Equal(t, []Day{Sunday}, d)
d, err = ParseDays("0 ") d, err = ParseDays("0 ")
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []Day{Sunday}, d) assert.Equal(t, []Day{Sunday}, d)
d, err = ParseDays(" 7") d, err = ParseDays(" 7")
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []Day{Sunday}, d) assert.Equal(t, []Day{Sunday}, d)
d, err = ParseDays(" 6-7 ") d, err = ParseDays(" 6-7 ")
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []Day{Saturday, Sunday}, d) assert.Equal(t, []Day{Saturday, Sunday}, d)
d, err = ParseDays("1- 7") d, err = ParseDays("1- 7")
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []Day{Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday}, d) assert.Equal(t, []Day{Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday}, d)
_, err = ParseDays(" ") _, err = ParseDays(" ")
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []Day{Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday}, d) assert.Equal(t, []Day{Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday}, d)
d, err = ParseDays("1, 3-7") d, err = ParseDays("1, 3-7")
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []Day{Monday, Wednesday, Thursday, Friday, Saturday, Sunday}, d) assert.Equal(t, []Day{Monday, Wednesday, Thursday, Friday, Saturday, Sunday}, d)
_, err = ParseDays("-") _, err = ParseDays("-")

View file

@ -4,15 +4,16 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestParseTimeRange(t *testing.T) { func TestParseTimeRange(t *testing.T) {
d, err := ParseTimeRanges(" 7:00 - 12:30 ") d, err := ParseTimeRanges(" 7:00 - 12:30 ")
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []TimeRange{{HourMin{7, 0}, HourMin{12, 30}}}, d, "hour:min range") assert.Equal(t, []TimeRange{{HourMin{7, 0}, HourMin{12, 30}}}, d, "hour:min range")
d, err = ParseTimeRanges(" 8-10 ") d, err = ParseTimeRanges(" 8-10 ")
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []TimeRange{{HourMin{8, 0}, HourMin{10, 0}}}, d, "hour range") assert.Equal(t, []TimeRange{{HourMin{8, 0}, HourMin{10, 0}}}, d, "hour range")
_, err = ParseTimeRanges("-1-10") _, err = ParseTimeRanges("-1-10")
@ -22,14 +23,14 @@ func TestParseTimeRange(t *testing.T) {
assert.EqualError(t, err, "invalid time: 25", "<to> after midnight") assert.EqualError(t, err, "invalid time: 25", "<to> after midnight")
d, err = ParseTimeRanges("8-0") d, err = ParseTimeRanges("8-0")
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []TimeRange{{HourMin{8, 0}, HourMin{0, 0}}}, d, "hour till midnight (0)") assert.Equal(t, []TimeRange{{HourMin{8, 0}, HourMin{0, 0}}}, d, "hour till midnight (0)")
_, err = ParseTimeRanges("10-8") _, err = ParseTimeRanges("10-8")
assert.EqualError(t, err, "invalid time range: 10-8, <from> must be before <to>") assert.EqualError(t, err, "invalid time range: 10-8, <from> must be before <to>")
d, err = ParseTimeRanges("8-10, 20-22:30") d, err = ParseTimeRanges("8-10, 20-22:30")
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []TimeRange{ assert.Equal(t, []TimeRange{
{HourMin{8, 0}, HourMin{10, 0}}, {HourMin{8, 0}, HourMin{10, 0}},
{HourMin{20, 0}, HourMin{22, 30}}, {HourMin{20, 0}, HourMin{22, 30}},

View file

@ -9,6 +9,7 @@ import (
"github.com/evcc-io/evcc/tariff/fixed" "github.com/evcc-io/evcc/tariff/fixed"
"github.com/jinzhu/now" "github.com/jinzhu/now"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestFixed(t *testing.T) { func TestFixed(t *testing.T) {
@ -33,7 +34,7 @@ func TestFixed(t *testing.T) {
} }
rates, err := tf.Rates() rates, err := tf.Rates()
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, expect, rates) assert.Equal(t, expect, rates)
} }
@ -47,7 +48,7 @@ func TestFixedSplitZones(t *testing.T) {
{0.1, "0-5:30,21-0"}, {0.1, "0-5:30,21-0"},
}, },
}) })
assert.NoError(t, err) require.NoError(t, err)
tf := at.(*Fixed) tf := at.(*Fixed)
tf.clock = clock.NewMock() tf.clock = clock.NewMock()
@ -99,6 +100,6 @@ func TestFixedSplitZones(t *testing.T) {
} }
rates, err := tf.Rates() rates, err := tf.Rates()
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, expect, rates) assert.Equal(t, expect, rates)
} }

View file

@ -5,6 +5,7 @@ import (
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestDecodeNil(t *testing.T) { func TestDecodeNil(t *testing.T) {
@ -23,12 +24,12 @@ func TestDecodeNil(t *testing.T) {
} }
decoder, err := mapstructure.NewDecoder(decoderConfig) decoder, err := mapstructure.NewDecoder(decoderConfig)
assert.NoError(t, err) require.NoError(t, err)
err = decoder.Decode(map[string]any{ err = decoder.Decode(map[string]any{
"user": nil, "user": nil,
}) })
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, struct { assert.Equal(t, struct {
User, Password string User, Password string

View file

@ -54,7 +54,7 @@ func newLogger(area string, lp int) *Logger {
padded := area padded := area
for len(padded) < LogAreaPadding { for len(padded) < LogAreaPadding {
padded = padded + " " padded += " "
} }
level := LogLevelForArea(area) level := LogLevelForArea(area)
@ -188,10 +188,7 @@ func captureLogger(l *Logger) {
} }
func captureLogLevel(level string, lp int, l *log.Logger) { func captureLogLevel(level string, lp int, l *log.Logger) {
re, err := regexp.Compile(`^\[[a-zA-Z0-9-]+\s*\] \w+ .{19} `) re := regexp.MustCompile(`^\[[a-zA-Z0-9-]+\s*\] \w+ .{19} `)
if err != nil {
panic(err)
}
ui := uiWriter{ ui := uiWriter{
lp: lp, lp: lp,

View file

@ -7,9 +7,8 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/samber/lo"
"github.com/denisbrodbeck/machineid" "github.com/denisbrodbeck/machineid"
"github.com/samber/lo"
) )
var id string var id string

View file

@ -53,7 +53,7 @@ func decodeBool16(mask uint64) func(b []byte) float64 {
return func(b []byte) float64 { return func(b []byte) float64 {
u := binary.BigEndian.Uint16(b) u := binary.BigEndian.Uint16(b)
if mask > 0 { if mask > 0 {
u = u & uint16(mask) u &= uint16(mask)
} }
if u > 0 { if u > 0 {
return 1 return 1

View file

@ -390,7 +390,6 @@ func RegisterOperation(r Register) (rs485.Operation, error) {
} }
switch strings.ToLower(r.Decode) { switch strings.ToLower(r.Decode) {
// 8 bit (coil) // 8 bit (coil)
case "bool8": case "bool8":
op.Transform = decodeBool8 op.Transform = decodeBool8

View file

@ -3,7 +3,7 @@ package templates
import ( import (
"bytes" "bytes"
_ "embed" _ "embed"
"fmt" "strconv"
"strings" "strings"
"text/template" "text/template"
@ -36,7 +36,7 @@ func (t *Template) RenderDocumentation(product Product, lang string) ([]byte, er
case string: case string:
t.Params[index].Value = yamlQuote(v) t.Params[index].Value = yamlQuote(v)
case int: case int:
t.Params[index].Value = fmt.Sprintf("%d", v) t.Params[index].Value = strconv.Itoa(v)
} }
} }
} }

View file

@ -11,6 +11,8 @@ import (
// test renders and instantiates plus yaml-parses the template per usage // test renders and instantiates plus yaml-parses the template per usage
func test(t *testing.T, tmpl Template, values map[string]interface{}, cb func(values map[string]interface{})) { func test(t *testing.T, tmpl Template, values map[string]interface{}, cb func(values map[string]interface{})) {
t.Helper()
b, _, err := tmpl.RenderResult(TemplateRenderModeInstance, values) b, _, err := tmpl.RenderResult(TemplateRenderModeInstance, values)
if err != nil { if err != nil {
t.Log(string(b)) t.Log(string(b))
@ -32,6 +34,8 @@ func test(t *testing.T, tmpl Template, values map[string]interface{}, cb func(va
} }
func TestClass(t *testing.T, class Class, instantiate func(t *testing.T, values map[string]interface{})) { func TestClass(t *testing.T, class Class, instantiate func(t *testing.T, values map[string]interface{})) {
t.Parallel()
for _, tmpl := range ByClass(class) { for _, tmpl := range ByClass(class) {
tmpl := tmpl tmpl := tmpl

View file

@ -5,6 +5,7 @@ import (
_ "embed" _ "embed"
"fmt" "fmt"
"slices" "slices"
"strconv"
"strings" "strings"
"text/template" "text/template"
@ -230,7 +231,7 @@ func (t *Template) RenderProxyWithValues(values map[string]interface{}, lang str
case string: case string:
t.Params[index].Value = yamlQuote(v) t.Params[index].Value = yamlQuote(v)
case int: case int:
t.Params[index].Value = fmt.Sprintf("%d", v) t.Params[index].Value = strconv.Itoa(v)
} }
} }
} }

View file

@ -57,9 +57,7 @@ func FuncMap(tmpl *template.Template) *template.Template {
} }
return buf.String(), nil return buf.String(), nil
}, },
"urlEncode": func(v string) string { "urlEncode": url.QueryEscape,
return url.QueryEscape(v)
},
"toDuration": func(v string) time.Duration { "toDuration": func(v string) time.Duration {
d, err := time.ParseDuration(v) d, err := time.ParseDuration(v)
if err != nil { if err != nil {

View file

@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -19,7 +20,7 @@ func TestYamlDecode(t *testing.T) {
} }
err := yaml.Unmarshal([]byte(input), &res) err := yaml.Unmarshal([]byte(input), &res)
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, value, res.Value) assert.Equal(t, value, res.Value)
}) })
} }

View file

@ -6,6 +6,8 @@ import (
) )
func SkipCI(t *testing.T) { func SkipCI(t *testing.T) {
t.Helper()
if os.Getenv("CI") != "" { if os.Getenv("CI") != "" {
t.Skip("Skipping testing in CI environment") t.Skip("Skipping testing in CI environment")
} }

View file

@ -11,7 +11,6 @@ import (
"github.com/evcc-io/evcc/provider" "github.com/evcc-io/evcc/provider"
"github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request" "github.com/evcc-io/evcc/util/request"
"github.com/joeshaw/carwings" "github.com/joeshaw/carwings"
) )
@ -111,7 +110,7 @@ func (v *CarWings) status() (carwings.BatteryStatus, error) {
// api result is stale // api result is stale
if v.refreshKey != "" { if v.refreshKey != "" {
if err := v.refreshResult(); err != nil { if err := v.refreshResult(); err != nil {
return *new(carwings.BatteryStatus), err return carwings.BatteryStatus{}, err
} }
} }
@ -120,7 +119,7 @@ func (v *CarWings) status() (carwings.BatteryStatus, error) {
if err == nil { if err == nil {
if elapsed := time.Since(bs.Timestamp); elapsed > carwingsStatusExpiry { if elapsed := time.Since(bs.Timestamp); elapsed > carwingsStatusExpiry {
if err = v.refreshRequest(); err != nil { if err = v.refreshRequest(); err != nil {
return *new(carwings.BatteryStatus), err return carwings.BatteryStatus{}, err
} }
err = api.ErrMustRetry err = api.ErrMustRetry

View file

@ -94,7 +94,7 @@ func (ct *TimeMillis) UnmarshalJSON(data []byte) error {
if err == nil { if err == nil {
t := time.Unix(0, i*1e6) t := time.Unix(0, i*1e6)
(*ct).Time = t ct.Time = t
} }
return err return err

View file

@ -103,7 +103,7 @@ func (v *Identity) login() (*oauth.Token, error) {
var settings Settings var settings Settings
if err := json.Unmarshal([]byte(match[1]), &settings); err != nil { if err := json.Unmarshal(match[1], &settings); err != nil {
return nil, err return nil, err
} }

View file

@ -64,7 +64,7 @@ func (ct *Timestamp) UnmarshalJSON(data []byte) error {
t, err := time.Parse(TimeFormat, s) t, err := time.Parse(TimeFormat, s)
if err == nil { if err == nil {
(*ct).Time = t ct.Time = t
} }
return err return err

View file

@ -86,7 +86,7 @@ func (v *Identity) Login(user, password string) error {
req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding) req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding)
if err == nil { if err == nil {
if err = v.DoJSON(req, &res); err != nil && len(res.Errors) > 0 { if err = v.DoJSON(req, &res); err != nil && len(res.Errors) > 0 {
err = fmt.Errorf("%s: %w", string(res.Errors[0].Key), err) err = fmt.Errorf("%s: %w", res.Errors[0].Key, err)
} }
} }
@ -98,7 +98,7 @@ func (v *Identity) Login(user, password string) error {
req, err = request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding) req, err = request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding)
if err == nil { if err == nil {
if err = v.DoJSON(req, &res); err != nil && len(res.Errors) > 0 { if err = v.DoJSON(req, &res); err != nil && len(res.Errors) > 0 {
err = fmt.Errorf("%s: %w", string(res.Errors[0].Key), err) err = fmt.Errorf("%s: %w", res.Errors[0].Key, err)
} }
} }
} }

View file

@ -111,7 +111,7 @@ func (ct *Timestamp) UnmarshalJSON(data []byte) error {
t, err := time.Parse(timeFormat, s) t, err := time.Parse(timeFormat, s)
if err == nil { if err == nil {
(*ct).Time = t ct.Time = t
} }
return err return err

View file

@ -2,6 +2,7 @@ package vehicle
import ( import (
"fmt" "fmt"
"net"
"net/http" "net/http"
"net/http/cookiejar" "net/http/cookiejar"
"net/url" "net/url"
@ -102,31 +103,31 @@ func (v *Ovms) loginToServer() (err error) {
return err return err
} }
func (v *Ovms) uri(path string) string {
return fmt.Sprintf("https://%s/api/%s/%s", net.JoinHostPort(v.server, "6869"), path, v.vehicleId)
}
func (v *Ovms) connectRequest() (ovmsConnectResponse, error) { func (v *Ovms) connectRequest() (ovmsConnectResponse, error) {
uri := fmt.Sprintf("https://%s:6869/api/vehicle/%s", v.server, v.vehicleId)
var res ovmsConnectResponse var res ovmsConnectResponse
err := v.GetJSON(uri, &res) err := v.GetJSON(v.uri("vehicle"), &res)
return res, err return res, err
} }
func (v *Ovms) chargeRequest() (ovmsChargeResponse, error) { func (v *Ovms) chargeRequest() (ovmsChargeResponse, error) {
uri := fmt.Sprintf("https://%s:6869/api/charge/%s", v.server, v.vehicleId)
var res ovmsChargeResponse var res ovmsChargeResponse
err := v.GetJSON(uri, &res) err := v.GetJSON(v.uri("charge"), &res)
return res, err return res, err
} }
func (v *Ovms) statusRequest() (ovmsStatusResponse, error) { func (v *Ovms) statusRequest() (ovmsStatusResponse, error) {
uri := fmt.Sprintf("https://%s:6869/api/status/%s", v.server, v.vehicleId)
var res ovmsStatusResponse var res ovmsStatusResponse
err := v.GetJSON(uri, &res) err := v.GetJSON(v.uri("status"), &res)
return res, err return res, err
} }
func (v *Ovms) locationRequest() (ovmsLocationResponse, error) { func (v *Ovms) locationRequest() (ovmsLocationResponse, error) {
uri := fmt.Sprintf("https://%s:6869/api/location/%s", v.server, v.vehicleId)
var res ovmsLocationResponse var res ovmsLocationResponse
err := v.GetJSON(uri, &res) err := v.GetJSON(v.uri("location"), &res)
return res, err return res, err
} }

View file

@ -102,7 +102,6 @@ func newPSA(log *util.Logger, brand, realm, id, secret string, other map[string]
return v.VIN return v.VIN
}, },
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -58,7 +58,6 @@ func (v *API) request_(uri string, body io.Reader) (Response, error) {
func (v *API) request(uri string, body io.Reader) (Response, error) { func (v *API) request(uri string, body io.Reader) (Response, error) {
res, err := v.request_(uri, body) res, err := v.request_(uri, body)
// repeat auth if error // repeat auth if error
if err != nil { if err != nil {
if err = v.login(); err == nil { if err = v.login(); err == nil {

View file

@ -32,7 +32,7 @@ var _ api.Battery = (*Provider)(nil)
// Soc implements the api.Vehicle interface // Soc implements the api.Vehicle interface
func (v *Provider) Soc() (float64, error) { func (v *Provider) Soc() (float64, error) {
res, err := v.statusG() res, err := v.statusG()
return float64(res.Engines.Primary.Level), err return res.Engines.Primary.Level, err
} }
var _ api.ChargeState = (*Provider)(nil) var _ api.ChargeState = (*Provider)(nil)
@ -89,7 +89,7 @@ var _ api.VehicleOdometer = (*Provider)(nil)
// Odometer implements the api.VehicleOdometer interface // Odometer implements the api.VehicleOdometer interface
func (v *Provider) Odometer() (float64, error) { func (v *Provider) Odometer() (float64, error) {
res, err := v.statusG() res, err := v.statusG()
return float64(res.Measurements.MileageKm), err return res.Measurements.MileageKm, err
} }
var _ api.VehicleClimater = (*Provider)(nil) var _ api.VehicleClimater = (*Provider)(nil)

View file

@ -55,7 +55,7 @@ func (ct *TimeSecs) UnmarshalJSON(data []byte) error {
if err == nil { if err == nil {
t := time.Unix(i, 0) t := time.Unix(i, 0)
(*ct).Time = t ct.Time = t
} }
return err return err

View file

@ -26,6 +26,8 @@ var acceptable = []string{
func TestTemplates(t *testing.T) { func TestTemplates(t *testing.T) {
templates.TestClass(t, templates.Vehicle, func(t *testing.T, values map[string]any) { templates.TestClass(t, templates.Vehicle, func(t *testing.T, values map[string]any) {
t.Helper()
if _, err := NewFromConfig("template", values); err != nil && !test.Acceptable(err, acceptable) { if _, err := NewFromConfig("template", values); err != nil && !test.Acceptable(err, acceptable) {
t.Log(values) t.Log(values)
t.Error(err) t.Error(err)

View file

@ -94,7 +94,7 @@ func (ct *Timestamp) UnmarshalJSON(data []byte) error {
t, err := time.Parse(timeFormat, s) t, err := time.Parse(timeFormat, s)
if err == nil { if err == nil {
(*ct).Time = t ct.Time = t
} }
return err return err

View file

@ -331,7 +331,7 @@ func (ct *Timestamp) UnmarshalJSON(data []byte) error {
t, err := time.Parse(time.RFC3339, s) t, err := time.Parse(time.RFC3339, s)
if err == nil { if err == nil {
(*ct).Time = t ct.Time = t
} }
return err return err

View file

@ -133,12 +133,12 @@ func (t *TimedTemperature) UnmarshalJSON(data []byte) error {
err := json.Unmarshal(data, &temp) err := json.Unmarshal(data, &temp)
if err == nil { if err == nil {
(*t).Timestamp = temp.Timestamp t.Timestamp = temp.Timestamp
if val, err := strconv.Atoi(string(temp.Content)); err == nil { if val, err := strconv.Atoi(string(temp.Content)); err == nil {
(*t).Content = temp2Float(val) t.Content = temp2Float(val)
} else { } else {
(*t).Content = math.NaN() t.Content = math.NaN()
} }
} }