Mack charger settings permanent
This commit is contained in:
@@ -52,12 +52,82 @@ func NewChargerController(host string, pool *pgxpool.Pool) *ChargerController {
|
||||
params: ChargingParams{Mode: ModeOff},
|
||||
status: "initializing",
|
||||
}
|
||||
// Always restore RFID-gated idle state on startup. nmo/acs settings persist
|
||||
// on the charger hardware across app restarts, so we must reset them explicitly.
|
||||
go c.initSafeState()
|
||||
c.initParamsSchema(context.Background())
|
||||
go c.startup()
|
||||
return c
|
||||
}
|
||||
|
||||
// startup runs once after construction. If an active charging mode was
|
||||
// persisted (e.g. solar_battery at 90% SOC), it restores the goroutine
|
||||
// without touching the charger hardware — charging simply continues.
|
||||
// Only when mode is off do we reset the charger to RFID-gated safe state,
|
||||
// since that hardware state (acs=0, nmo=true, frc=2) persists across reboots.
|
||||
func (c *ChargerController) startup() {
|
||||
ctx := context.Background()
|
||||
p, err := c.loadParams(ctx)
|
||||
if err != nil {
|
||||
log.Printf("charger ctrl: load params on startup: %v", err)
|
||||
p = ChargingParams{Mode: ModeOff}
|
||||
}
|
||||
if p.Mode == ModeOff {
|
||||
c.initSafeState()
|
||||
return
|
||||
}
|
||||
log.Printf("charger ctrl: restoring mode=%s (max=%dA, minSOC=%d%%) after restart", p.Mode, p.MaxAmp, p.MinBatterySoc)
|
||||
if err := c.SetParams(p); err != nil {
|
||||
log.Printf("charger ctrl: restore params: %v", err)
|
||||
c.initSafeState()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ChargerController) initParamsSchema(ctx context.Context) {
|
||||
_, err := c.pool.Exec(ctx, `
|
||||
CREATE TABLE IF NOT EXISTS charger_params (
|
||||
id INT PRIMARY KEY DEFAULT 1,
|
||||
mode TEXT NOT NULL DEFAULT 'off',
|
||||
max_amp INT NOT NULL DEFAULT 16,
|
||||
min_battery_soc INT NOT NULL DEFAULT 0,
|
||||
hysteresis INT NOT NULL DEFAULT 0,
|
||||
target_kwh DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
target_soc INT NOT NULL DEFAULT 0,
|
||||
phases INT NOT NULL DEFAULT 0
|
||||
);
|
||||
INSERT INTO charger_params (id, mode) VALUES (1, 'off') ON CONFLICT DO NOTHING;
|
||||
`)
|
||||
if err != nil {
|
||||
log.Printf("charger ctrl: schema init: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ChargerController) saveParams(ctx context.Context, p ChargingParams) error {
|
||||
_, err := c.pool.Exec(ctx, `
|
||||
INSERT INTO charger_params
|
||||
(id, mode, max_amp, min_battery_soc, hysteresis, target_kwh, target_soc, phases)
|
||||
VALUES (1, $1, $2, $3, $4, $5, $6, $7)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
mode = EXCLUDED.mode,
|
||||
max_amp = EXCLUDED.max_amp,
|
||||
min_battery_soc = EXCLUDED.min_battery_soc,
|
||||
hysteresis = EXCLUDED.hysteresis,
|
||||
target_kwh = EXCLUDED.target_kwh,
|
||||
target_soc = EXCLUDED.target_soc,
|
||||
phases = EXCLUDED.phases
|
||||
`, string(p.Mode), p.MaxAmp, p.MinBatterySoc, p.Hysteresis, p.TargetKwh, p.TargetSoc, p.Phases)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ChargerController) loadParams(ctx context.Context) (ChargingParams, error) {
|
||||
var p ChargingParams
|
||||
var mode string
|
||||
err := c.pool.QueryRow(ctx, `
|
||||
SELECT mode, max_amp, min_battery_soc, hysteresis, target_kwh, target_soc, phases
|
||||
FROM charger_params WHERE id = 1
|
||||
`).Scan(&mode, &p.MaxAmp, &p.MinBatterySoc, &p.Hysteresis, &p.TargetKwh, &p.TargetSoc, &p.Phases)
|
||||
p.Mode = ChargingMode(mode)
|
||||
return p, err
|
||||
}
|
||||
|
||||
|
||||
func (c *ChargerController) initSafeState() {
|
||||
if err := setChargerFrc(c.host, 1); err != nil {
|
||||
log.Printf("charger ctrl: init: %v", err)
|
||||
@@ -85,6 +155,10 @@ func (c *ChargerController) SetParams(p ChargingParams) error {
|
||||
p.Phases = 3
|
||||
}
|
||||
|
||||
if err := c.saveParams(context.Background(), p); err != nil {
|
||||
log.Printf("charger ctrl: save params: %v", err)
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
if c.cancel != nil {
|
||||
c.cancel()
|
||||
@@ -286,6 +360,24 @@ func (c *ChargerController) adjust(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// car=3 with alw=true: the CP line is stuck in IEC state B even though
|
||||
// authorization was already granted (alw=true, frc=2). Simply re-setting
|
||||
// frc=2 every tick has no effect — a full charger reset is required to
|
||||
// force a fresh IEC 61851 handshake.
|
||||
if st.Car == 3 && st.Alw {
|
||||
c.mu.RLock()
|
||||
paused := c.battPaused
|
||||
c.mu.RUnlock()
|
||||
if !paused {
|
||||
c.setStatus("resetting — car stuck in IEC state B")
|
||||
if err := c.enableCharging(params.MaxAmp); err != nil {
|
||||
log.Printf("charger ctrl: car=3 stuck: %v", err)
|
||||
c.setStatus("reset failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if params.TargetKwh > 0 || params.TargetSoc > 0 {
|
||||
if params.TargetSoc > 0 && st.Soc > 0 && st.Soc >= params.TargetSoc {
|
||||
c.stopForTarget(fmt.Sprintf("car SOC %d%% reached", params.TargetSoc))
|
||||
@@ -445,6 +537,9 @@ func (c *ChargerController) stopOnDisconnect() {
|
||||
c.cancel = nil
|
||||
}
|
||||
c.mu.Unlock()
|
||||
if err := c.saveParams(context.Background(), ChargingParams{Mode: ModeOff}); err != nil {
|
||||
log.Printf("charger ctrl: save disconnect params: %v", err)
|
||||
}
|
||||
c.setStatus("off — car disconnected")
|
||||
}
|
||||
|
||||
@@ -468,6 +563,9 @@ func (c *ChargerController) stopForTarget(reason string) {
|
||||
c.cancel = nil
|
||||
}
|
||||
c.mu.Unlock()
|
||||
if err := c.saveParams(context.Background(), ChargingParams{Mode: ModeOff}); err != nil {
|
||||
log.Printf("charger ctrl: save target-stop params: %v", err)
|
||||
}
|
||||
c.setStatus(reason)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user