package api import ( "encoding/json" "fmt" "io" "net/http" "time" ) type ChargerStatus struct { Car int `json:"car"` Frc int `json:"frc"` SessionWh float64 `json:"session_wh"` Amp int `json:"amp"` Soc int `json:"soc"` // car battery %, 0 = not reported by car Alw bool `json:"alw"` // charging allowed (false = access control blocking) } type goeStatus struct { Car int `json:"car"` Frc int `json:"frc"` Wh float64 `json:"wh"` Amp int `json:"amp"` Soc int `json:"soc"` Alw bool `json:"alw"` } var chargerHTTP = &http.Client{Timeout: 15 * time.Second} func fetchChargerStatus(host string) (ChargerStatus, error) { url := "http://" + host + "/api/status?filter=car,frc,wh,amp,soc,alw" resp, err := chargerHTTP.Get(url) if err != nil { return ChargerStatus{}, fmt.Errorf("charger unreachable: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return ChargerStatus{}, err } var s goeStatus if err := json.Unmarshal(body, &s); err != nil { return ChargerStatus{}, err } return ChargerStatus{Car: s.Car, Frc: s.Frc, SessionWh: s.Wh, Amp: s.Amp, Soc: s.Soc, Alw: s.Alw}, nil } // setChargerAcs sets access control: 0 = open (app controls), 1 = RFID required. func setChargerAcs(host string, acs int) error { url := fmt.Sprintf("http://%s/api/set?acs=%d", host, acs) resp, err := chargerHTTP.Get(url) if err != nil { return fmt.Errorf("charger unreachable: %w", err) } defer resp.Body.Close() return nil } // setChargerNmo sets Norwegian mode: true = charge without RFID, false = normal. func setChargerNmo(host string, enabled bool) error { val := "false" if enabled { val = "true" } url := "http://" + host + "/api/set?nmo=" + val resp, err := chargerHTTP.Get(url) if err != nil { return fmt.Errorf("charger unreachable: %w", err) } defer resp.Body.Close() return nil } // setChargerTrx starts a charging session (trx=0). With acs=0 this bypasses // RFID authentication but still initiates the IEC 61851 handshake — without // it the charger stays in modelStatus "paused" and never closes its relay. func setChargerTrx(host string) error { url := "http://" + host + "/api/set?trx=0" resp, err := chargerHTTP.Get(url) if err != nil { return fmt.Errorf("charger unreachable: %w", err) } defer resp.Body.Close() return nil } func setChargerFrc(host string, frc int) error { url := fmt.Sprintf("http://%s/api/set?frc=%d", host, frc) resp, err := chargerHTTP.Get(url) if err != nil { return fmt.Errorf("charger unreachable: %w", err) } defer resp.Body.Close() return nil } func setChargerAmp(host string, amps int) error { url := fmt.Sprintf("http://%s/api/set?amp=%d", host, amps) resp, err := chargerHTTP.Get(url) if err != nil { return fmt.Errorf("charger unreachable: %w", err) } defer resp.Body.Close() return nil } // resetCharger sends rst=1 and waits for the charger to reboot. // frc and trx are cleared by the firmware on reset; callers must re-apply mode after this. func resetCharger(host string) error { url := "http://" + host + "/api/set?rst=1" resp, _ := chargerHTTP.Get(url) // charger may close connection before responding if resp != nil { resp.Body.Close() } time.Sleep(12 * time.Second) // v59.x firmware takes longer to reboot if _, err := fetchChargerStatus(host); err != nil { return fmt.Errorf("charger did not recover from reset: %w", err) } return nil } // setChargerPhases switches between 1-phase (psm=1) and 3-phase (psm=2). // Requires phase-switching hardware in the charger. func setChargerPhases(host string, phases int) error { psm := 2 if phases == 1 { psm = 1 } url := fmt.Sprintf("http://%s/api/set?psm=%d", host, psm) resp, err := chargerHTTP.Get(url) if err != nil { return fmt.Errorf("charger unreachable: %w", err) } defer resp.Body.Close() return nil }