Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue/easee prevent parallel start stop #8433

Merged
Merged
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
f246be8
wait for pause/resume to be acknoledged
GrimmiMeloni Jun 4, 2023
df6bec8
do not expose internal helper function
GrimmiMeloni Jun 4, 2023
0224154
unified http api handling and actively wait for 202 reponses
GrimmiMeloni Jun 8, 2023
64cb372
adjust GHA config
GrimmiMeloni Jun 9, 2023
c738eba
Revert "adjust GHA config"
GrimmiMeloni Jun 9, 2023
78d6c4b
fix array parsing error
GrimmiMeloni Jun 9, 2023
a2ca8e7
handle empty response
GrimmiMeloni Jun 9, 2023
38dc995
handle different result types
GrimmiMeloni Jun 10, 2023
9b9335a
handle potential seek fail
GrimmiMeloni Jun 10, 2023
028fdc0
raise timeout
GrimmiMeloni Jun 10, 2023
b91e147
reset current after enable
GrimmiMeloni Jun 10, 2023
14deb37
adjusted retry handling for more concise logging
GrimmiMeloni Jun 10, 2023
7b5f45a
add 1s delay between timeouts
GrimmiMeloni Jun 10, 2023
1acfae9
wip
andig Jun 11, 2023
84f4b37
Update charger/easee.go
GrimmiMeloni Jun 11, 2023
b0104e2
Update charger/easee.go
GrimmiMeloni Jun 11, 2023
c9a0d59
Update charger/easee.go
GrimmiMeloni Jun 11, 2023
beb21f8
Update charger/easee.go
GrimmiMeloni Jun 11, 2023
c7e4e83
incorporate review comments by andig
GrimmiMeloni Jun 11, 2023
3cad12f
Update charger/easee.go
GrimmiMeloni Jun 11, 2023
fe4792c
Update charger/easee.go
GrimmiMeloni Jun 11, 2023
eb269fa
remove log message for late CommandResponse
GrimmiMeloni Jun 11, 2023
bf59f72
more logging cleanup
GrimmiMeloni Jun 11, 2023
1675c55
Update charger/easee.go
GrimmiMeloni Jun 11, 2023
69e2024
Update charger/easee.go
GrimmiMeloni Jun 11, 2023
7486331
Update charger/easee.go
GrimmiMeloni Jun 11, 2023
39dd440
Update charger/easee.go
GrimmiMeloni Jun 11, 2023
de3547f
Update charger/easee.go
GrimmiMeloni Jun 11, 2023
675b947
refactor JSON marshalling
GrimmiMeloni Jun 11, 2023
78c2ff3
simplify
andig Jun 12, 2023
df75e22
wip
andig Jun 12, 2023
4967833
remove retries on empty API response
GrimmiMeloni Jun 12, 2023
30f32a1
Update charger/easee.go
andig Jun 13, 2023
bdfa97c
simplify json decoding
GrimmiMeloni Jun 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 105 additions & 37 deletions charger/easee.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"net/http"
"os"
"strconv"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -57,8 +58,9 @@ type Easee struct {
phaseMode int
currentPower, sessionEnergy, totalEnergy,
currentL1, currentL2, currentL3 float64
rfid string
lp loadpoint.API
rfid string
lp loadpoint.API
respChan chan easee.SignalRCommandResponse
}

func init() {
Expand Down Expand Up @@ -96,11 +98,12 @@ func NewEasee(user, password, charger string, timeout time.Duration) (*Easee, er
}

c := &Easee{
Helper: request.NewHelper(log),
charger: charger,
log: log,
current: 6, // default current
done: make(chan struct{}),
Helper: request.NewHelper(log),
charger: charger,
log: log,
current: 6, // default current
done: make(chan struct{}),
respChan: make(chan easee.SignalRCommandResponse),
}

c.Client.Timeout = timeout
Expand Down Expand Up @@ -320,6 +323,11 @@ func (c *Easee) CommandResponse(i json.RawMessage) {
return
}
c.log.TRACE.Printf("CommandResponse %s: %+v", res.SerialNumber, res)

select {
case c.respChan <- res:
default:
}
}

func (c *Easee) chargers() ([]easee.Charger, error) {
Expand Down Expand Up @@ -379,22 +387,95 @@ func (c *Easee) Enable(enable bool) error {
}

uri := fmt.Sprintf("%s/chargers/%s/settings", easee.API, c.charger)
resp, err := c.Post(uri, request.JSONContent, request.MarshalJSON(data))
if err != nil {
if err := c.postJSONAndWait(uri, data); err != nil {
return err
}
resp.Body.Close()
}

// resume/stop charger
action := easee.ChargePause
if enable {
action = easee.ChargeResume
}

uri := fmt.Sprintf("%s/chargers/%s/commands/%s", easee.API, c.charger, action)
_, err := c.Post(uri, request.JSONContent, nil)
if err := c.postJSONAndWait(uri, nil); err != nil {
return err
}

return err
if enable {
// reset currents after enable, as easee automatically resets to maxA
return c.MaxCurrent(int64(c.current))
}

return nil
}

// posts JSON to the Easee API endpoint and waits for the async response
func (c *Easee) postJSONAndWait(uri string, data any) error {

andig marked this conversation as resolved.
Show resolved Hide resolved
resp, err := c.Post(uri, request.JSONContent, request.MarshalJSON(data))
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode == 200 { //sync call
return nil
}

if resp.StatusCode == 202 { //async call, wait for response
var cmd easee.RestCommandResponse

if strings.Contains(uri, "/commands/") { //command endpoint
if err := decodeJSON(resp, &cmd); err != nil {
return err
}
} else { //settings endpoint
var cmdArr []easee.RestCommandResponse
if err := decodeJSON(resp, &cmdArr); err != nil {
return err
}

if len(cmdArr) != 0 {
cmd = cmdArr[0]
}
}

if cmd.Ticks == 0 { //Easee API thinks this was a noop
return nil
}
return c.waitForTickResponse(cmd.Ticks)
}

// all other response codes lead to an error
return fmt.Errorf("invalid status: %d", resp.StatusCode)
}

// decodeJSON reads HTTP response and decodes JSON body if error is nil
func decodeJSON(resp *http.Response, res interface{}) error {
GrimmiMeloni marked this conversation as resolved.
Show resolved Hide resolved
if err := request.ResponseError(resp); err != nil {
_ = json.NewDecoder(resp.Body).Decode(&res)
return err
}

return json.NewDecoder(resp.Body).Decode(&res)
}

func (c *Easee) waitForTickResponse(expectedTick int64) error {
for {
select {
case cmdResp := <-c.respChan:
if cmdResp.Ticks == expectedTick {
if !cmdResp.WasAccepted {
return fmt.Errorf("command rejected: %d", cmdResp.Ticks)
}
return nil
}
case <-time.After(10 * time.Second):
return api.ErrTimeout
}
}
}

// MaxCurrent implements the api.Charger interface
Expand All @@ -405,21 +486,16 @@ func (c *Easee) MaxCurrent(current int64) error {
}

uri := fmt.Sprintf("%s/chargers/%s/settings", easee.API, c.charger)
resp, err := c.Post(uri, request.JSONContent, request.MarshalJSON(data))
if err == nil {
resp.Body.Close()
if resp.StatusCode == 202 && resp.ContentLength <= 2 {
// no tick id, Easee effectively ignored this update
return api.ErrMustRetry
}

c.mux.Lock()
defer c.mux.Unlock()
c.current = cur
c.currentUpdated = time.Now()
if err := c.postJSONAndWait(uri, data); err != nil {
return err
}

return err
c.mux.Lock()
defer c.mux.Unlock()
c.current = cur
c.currentUpdated = time.Now()

return nil
}

var _ api.PhaseSwitcher = (*Easee)(nil)
Expand Down Expand Up @@ -456,10 +532,7 @@ func (c *Easee) Phases1p3p(phases int) error {
data.DynamicCircuitCurrentP3 = &max3
}

var resp *http.Response
if resp, err = c.Post(uri, request.JSONContent, request.MarshalJSON(data)); err == nil {
resp.Body.Close()
}
err = c.postJSONAndWait(uri, data)
} else {
// charger level
if phases == 3 {
Expand All @@ -474,10 +547,7 @@ func (c *Easee) Phases1p3p(phases int) error {

uri := fmt.Sprintf("%s/chargers/%s/settings", easee.API, c.charger)

var resp *http.Response
if resp, err = c.Post(uri, request.JSONContent, request.MarshalJSON(data)); err == nil {
resp.Body.Close()
}
err = c.postJSONAndWait(uri, data)
}
}

Expand Down Expand Up @@ -553,10 +623,8 @@ func (c *Easee) updateSmartCharging() {
}

uri := fmt.Sprintf("%s/chargers/%s/settings", easee.API, c.charger)
req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding)
if err == nil {
_, err = c.DoBody(req)
}

err := c.postJSONAndWait(uri, data)
if err != nil {
c.log.WARN.Printf("smart charging: %v", err)
}
Expand Down