Alpitronic: update handling for power absorption and fix status logic (v3.1.0) (#32820)
This commit is contained in:
parent
2201066430
commit
eaa49fceb3
2 changed files with 58 additions and 51 deletions
|
|
@ -36,6 +36,7 @@ type AlpitronicHYC struct {
|
|||
conn *modbus.Connection
|
||||
inputG func() ([]byte, error)
|
||||
curr float64
|
||||
enabled bool
|
||||
connector uint16
|
||||
}
|
||||
|
||||
|
|
@ -61,22 +62,20 @@ const (
|
|||
const (
|
||||
hycRegState = 0 // UINT16
|
||||
hycRegChargingVoltage = 1 // UINT32, cV
|
||||
hycRegChargingCurrent = 3 // UINT16, cA
|
||||
hycRegChargingPower = 4 // UINT32, W
|
||||
hycRegChargingCurrent = 3 // UINT16, cA up to 3.0, A from 3.1.0 on
|
||||
hycRegPowerAbsorptionAC = 4 // UINT32, W- DC charging power up to 3.0, AC absorption from 3.1.0 on
|
||||
hycRegChargeTime = 6 // UINT16, s
|
||||
hycRegChargedEnergy = 7 // UINT16, kWh/100
|
||||
hycRegChargedEnergy = 7 // UINT16, kWh/100 up to 3.0, kWh/10 from 3.1.0 on
|
||||
hycRegSoC = 8 // UINT16, %/100
|
||||
hycRegConnectorType = 9 // UINT16, 0-ChargePoint, 1-CCS2, 2-CCS1, 3-CHAdeMO, 4-CCS_AC, 5-GBT, 6-MCS, 7-NACS
|
||||
hycRegMaxChargingPowerDC = 10 // UINT32, W- includes the limit written by us
|
||||
hycRegMinChargingPowerDC = 12 // UINT32, W
|
||||
hycRegVarInductive = 14 // UINT32, var- deprecated, use hycRegStationVarInductive
|
||||
hycRegVarCapacitive = 16 // UINT32, var- deprecated, use hycRegStationVarCapacitive
|
||||
hycRegMaxPowerAbsorption = 10 // UINT32, W- includes the limit written by us
|
||||
hycRegMinPowerAbsorption = 12 // UINT32, W
|
||||
hycRegVID = 18 // 8 bytes
|
||||
hycRegIdTag = 22 // 20 bytes
|
||||
hycRegTotalChargedEnergy = 32 // INT64, Wh
|
||||
hycRegMaxChargingPowerAC = 36 // UINT32, W- includes the limit written by us
|
||||
|
||||
// the connector block is gapless- read up to the last register in use (x32..x35)
|
||||
// read up to the last register in use (x32..x35). x14/x16 (VAR) are covered but
|
||||
// no longer documented since 3.1.0- to be verified against the device
|
||||
hycInputLength = 36
|
||||
)
|
||||
|
||||
|
|
@ -104,7 +103,7 @@ const (
|
|||
|
||||
// a zero power limit makes the station report the connector as unavailable and
|
||||
// abort a running session, so charging is inhibited with a limit that is too low
|
||||
// for any connector to actually charge- see hycRegMinChargingPowerDC
|
||||
// for any connector to actually charge- see hycRegMinPowerAbsorption
|
||||
const (
|
||||
hycPowerPerAmp = 230 * 3 // W/A on the AC side
|
||||
hycMinPowerAC = 1547 // W
|
||||
|
|
@ -174,6 +173,7 @@ func newAlpitronicHYC(conn *modbus.Connection, connector uint16) (*AlpitronicHYC
|
|||
|
||||
if power := encoding.Uint32(b); power > hycMinPowerAC {
|
||||
wb.curr = float64(power) / hycPowerPerAmp
|
||||
wb.enabled = true
|
||||
}
|
||||
|
||||
return wb, nil
|
||||
|
|
@ -200,6 +200,12 @@ func (wb *AlpitronicHYC) setPower(power uint32) error {
|
|||
encoding.PutUint32(b, power)
|
||||
|
||||
_, err := wb.conn.WriteMultipleRegisters(wb.reg(hycRegMaxPowerAC), 2, b)
|
||||
if err == nil {
|
||||
// Status is polled before Enabled, so track our own writes to keep
|
||||
// the charging state from lagging a cycle behind
|
||||
wb.enabled = power > hycMinPowerAC
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -241,6 +247,9 @@ func (wb *AlpitronicHYC) Status() (api.ChargeStatus, error) {
|
|||
hycStateFinishing:
|
||||
return api.StatusB, nil
|
||||
case hycStateCharging:
|
||||
if !wb.enabled {
|
||||
return api.StatusB, nil
|
||||
}
|
||||
return api.StatusC, nil
|
||||
case hycStateFaulted:
|
||||
return api.StatusNone, errors.New("connector state: faulted")
|
||||
|
|
@ -249,6 +258,25 @@ func (wb *AlpitronicHYC) Status() (api.ChargeStatus, error) {
|
|||
}
|
||||
}
|
||||
|
||||
var _ api.StatusReasoner = (*AlpitronicHYC)(nil)
|
||||
|
||||
// StatusReason implements the api.StatusReasoner interface
|
||||
func (wb *AlpitronicHYC) StatusReason() (api.Reason, error) {
|
||||
s, err := wb.state()
|
||||
if err != nil {
|
||||
return api.ReasonUnknown, err
|
||||
}
|
||||
|
||||
switch s {
|
||||
case hycStatePreparingEVReady:
|
||||
return api.ReasonWaitingForAuthorization, nil
|
||||
case hycStateFinishing:
|
||||
return api.ReasonDisconnectRequired, nil
|
||||
default:
|
||||
return api.ReasonUnknown, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Enabled implements the api.Charger interface
|
||||
func (wb *AlpitronicHYC) Enabled() (bool, error) {
|
||||
b, err := wb.conn.ReadHoldingRegisters(wb.reg(hycRegMaxPowerAC), 2)
|
||||
|
|
@ -256,7 +284,9 @@ func (wb *AlpitronicHYC) Enabled() (bool, error) {
|
|||
return false, err
|
||||
}
|
||||
|
||||
return encoding.Uint32(b) > hycMinPowerAC, nil
|
||||
wb.enabled = encoding.Uint32(b) > hycMinPowerAC
|
||||
|
||||
return wb.enabled, nil
|
||||
}
|
||||
|
||||
// Enable implements the api.Charger interface
|
||||
|
|
@ -303,7 +333,7 @@ func (wb *AlpitronicHYC) CurrentPower() (float64, error) {
|
|||
return 0, err
|
||||
}
|
||||
|
||||
return float64(encoding.Uint32(hycInput(b, hycRegChargingPower, 4))), nil
|
||||
return float64(encoding.Uint32(hycInput(b, hycRegPowerAbsorptionAC, 4))), nil
|
||||
}
|
||||
|
||||
var _ api.ChargeTimer = (*AlpitronicHYC)(nil)
|
||||
|
|
@ -318,17 +348,9 @@ func (wb *AlpitronicHYC) ChargeDuration() (time.Duration, error) {
|
|||
return time.Duration(encoding.Uint16(hycInput(b, hycRegChargeTime, 2))) * time.Second, nil
|
||||
}
|
||||
|
||||
var _ api.ChargeRater = (*AlpitronicHYC)(nil)
|
||||
|
||||
// ChargedEnergy implements the api.ChargeRater interface
|
||||
func (wb *AlpitronicHYC) ChargedEnergy() (float64, error) {
|
||||
b, err := wb.inputG()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return float64(encoding.Uint16(hycInput(b, hycRegChargedEnergy, 2))) / 100, nil
|
||||
}
|
||||
// api.ChargeRater is not implemented: hycRegChargedEnergy is scaled kWh/100 up to
|
||||
// firmware 3.0 and kWh/10 from 3.1.0 on, so the session energy is derived from
|
||||
// TotalEnergy instead
|
||||
|
||||
var _ api.MeterEnergy = (*AlpitronicHYC)(nil)
|
||||
|
||||
|
|
@ -342,25 +364,6 @@ func (wb *AlpitronicHYC) TotalEnergy() (float64, error) {
|
|||
return float64(encoding.Int64(hycInput(b, hycRegTotalChargedEnergy, 8))) / 1e3, nil
|
||||
}
|
||||
|
||||
var _ api.StatusReasoner = (*AlpitronicHYC)(nil)
|
||||
|
||||
// StatusReason implements the api.StatusReasoner interface
|
||||
func (wb *AlpitronicHYC) StatusReason() (api.Reason, error) {
|
||||
s, err := wb.state()
|
||||
if err != nil {
|
||||
return api.ReasonUnknown, err
|
||||
}
|
||||
|
||||
switch s {
|
||||
case hycStatePreparingEVReady:
|
||||
return api.ReasonWaitingForAuthorization, nil
|
||||
case hycStateFinishing:
|
||||
return api.ReasonDisconnectRequired, nil
|
||||
default:
|
||||
return api.ReasonUnknown, nil
|
||||
}
|
||||
}
|
||||
|
||||
var _ api.Identifier = (*AlpitronicHYC)(nil)
|
||||
|
||||
// Identify implements the api.Identifier interface
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ func TestAlpitronicStatus(t *testing.T) {
|
|||
{hycStateAvailable, api.StatusA, false},
|
||||
{hycStatePreparingTagIdReady, api.StatusA, false}, // authorized, nothing plugged in yet
|
||||
{hycStatePreparingEVReady, api.StatusB, false},
|
||||
{hycStateCharging, api.StatusC, false},
|
||||
{hycStateCharging, api.StatusB, false}, // charging while disabled, see below
|
||||
{hycStateSuspendedEV, api.StatusB, false},
|
||||
{hycStateSuspendedEVSE, api.StatusB, false},
|
||||
{hycStateFinishing, api.StatusB, false},
|
||||
|
|
@ -153,6 +153,14 @@ func TestAlpitronicStatus(t *testing.T) {
|
|||
}
|
||||
assert.Equal(t, tc.status, status, "state %d", tc.state)
|
||||
}
|
||||
|
||||
// charging while enabled
|
||||
holding := map[uint16]uint16{hycReg(1, hycRegMaxPowerAC) + 1: 11040}
|
||||
wb, _ := hycTestChargerWithLimit(t, 1, hycRegs(1, hycStateCharging), holding)
|
||||
|
||||
status, err := wb.Status()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, api.StatusC, status)
|
||||
}
|
||||
|
||||
func TestAlpitronicStatusReason(t *testing.T) {
|
||||
|
|
@ -178,9 +186,8 @@ func TestAlpitronicStatusReason(t *testing.T) {
|
|||
|
||||
func TestAlpitronicMeasurements(t *testing.T) {
|
||||
regs := hycRegs(1, hycStateCharging)
|
||||
regs[hycReg(1, hycRegChargingPower)+1] = 11500 // 11500 W
|
||||
regs[hycReg(1, hycRegPowerAbsorptionAC)+1] = 11500 // 11500 W
|
||||
regs[hycReg(1, hycRegChargeTime)] = 3600 // 1h
|
||||
regs[hycReg(1, hycRegChargedEnergy)] = 1234 // 12.34 kWh
|
||||
regs[hycReg(1, hycRegSoC)] = 6550 // 65.5 %
|
||||
regs[hycReg(1, hycRegTotalChargedEnergy)+3] = 18705 // 18.705 kWh
|
||||
|
||||
|
|
@ -194,10 +201,6 @@ func TestAlpitronicMeasurements(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
assert.Equal(t, time.Hour, dur)
|
||||
|
||||
charged, err := wb.ChargedEnergy()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 12.34, charged)
|
||||
|
||||
soc, err := wb.Soc()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 65.5, soc)
|
||||
|
|
@ -319,9 +322,10 @@ func TestAlpitronicMaxCurrentInvalid(t *testing.T) {
|
|||
func TestAlpitronicConnector(t *testing.T) {
|
||||
// second connector reads and writes the 2xx block
|
||||
regs := hycRegs(2, hycStateCharging)
|
||||
regs[hycReg(2, hycRegChargingPower)+1] = 50000
|
||||
regs[hycReg(2, hycRegPowerAbsorptionAC)+1] = 50000
|
||||
|
||||
wb, h := hycTestCharger(t, 2, regs)
|
||||
holding := map[uint16]uint16{hycReg(2, hycRegMaxPowerAC) + 1: 50000}
|
||||
wb, h := hycTestChargerWithLimit(t, 2, regs, holding)
|
||||
|
||||
status, err := wb.Status()
|
||||
require.NoError(t, err)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue