fix(dispenser): retry on transient prepare failure
This commit is contained in:
parent
1969aaa1d2
commit
aeb86f55b9
@ -33,7 +33,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
buildVersion = "v2.0.1"
|
||||
buildVersion = "v2.0.2"
|
||||
serviceName = "hardlink"
|
||||
pollingFrequency = 8 * time.Second
|
||||
)
|
||||
|
||||
@ -201,10 +201,14 @@ func (c *Client) handle(req cmdReq) {
|
||||
|
||||
case cmdToEncoder:
|
||||
err := cardToEncoderPosition(c.port)
|
||||
// A movement command makes any previously cached position unreliable.
|
||||
c.invalidateStatusCache()
|
||||
req.respCh <- cmdResp{err: err}
|
||||
|
||||
case cmdOutOfMouth:
|
||||
err := cardOutOfMouth(c.port)
|
||||
// A movement command makes any previously cached position unreliable.
|
||||
c.invalidateStatusCache()
|
||||
req.respCh <- cmdResp{err: err}
|
||||
|
||||
default:
|
||||
@ -246,6 +250,13 @@ func (c *Client) CheckStatus(ctx context.Context) ([]byte, error) {
|
||||
return c.do(ctx, cmdStatus)
|
||||
}
|
||||
|
||||
func (c *Client) invalidateStatusCache() {
|
||||
c.mu.Lock()
|
||||
c.lastStatus = nil
|
||||
c.lastStatusT = time.Time{}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *Client) ToEncoder(ctx context.Context) error {
|
||||
_, err := c.do(ctx, cmdToEncoder)
|
||||
return err
|
||||
@ -339,6 +350,27 @@ func preparationStatus(operation string, status []byte) (bool, error) {
|
||||
if isPreparationMoving(status) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Some dispenser firmware briefly reports 0x32 ("Preparing card fails")
|
||||
// while the card is still travelling to the encoder. Treat that one
|
||||
// diagnostic as transient during an active preparation sequence and let
|
||||
// pollForEncoderPosition decide success (0x33) or timeout. Do not mask
|
||||
// independent hard errors reported in the other status bytes.
|
||||
if status[0] == 0x32 {
|
||||
statusWithoutPrepareFailure := append([]byte(nil), status...)
|
||||
statusWithoutPrepareFailure[0] = 0x30
|
||||
if err := dispenserStatusError(statusWithoutPrepareFailure); err != nil {
|
||||
return false, fmt.Errorf("[%s] %w", operation, err)
|
||||
}
|
||||
|
||||
log.Warnf(
|
||||
"[%s] transient Preparing card fails; waiting for encoder position, raw status: % X",
|
||||
operation,
|
||||
status,
|
||||
)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err := dispenserStatusError(status); err != nil {
|
||||
return false, fmt.Errorf("[%s] %w", operation, err)
|
||||
}
|
||||
|
||||
@ -147,6 +147,40 @@ func TestPrepareCurrentCardAcceptsEncoderPositionWithStaleDiagnostics(t *testing
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareCurrentCardToleratesTransientPrepareFailureUntilEncoder(t *testing.T) {
|
||||
client, device := newSequenceTestClient(t,
|
||||
cmdResp{status: []byte{0x32, 0x30, 0x30, 0x30}},
|
||||
cmdResp{status: []byte{0x32, 0x30, 0x30, 0x30}},
|
||||
cmdResp{status: []byte{0x32, 0x30, 0x30, 0x30}},
|
||||
cmdResp{status: []byte{0x32, 0x30, 0x30, 0x33}},
|
||||
)
|
||||
|
||||
stock, err := client.PrepareCurrentCard(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stock != "Preparing card fails" {
|
||||
t.Fatalf("stock status = %q, want Preparing card fails", stock)
|
||||
}
|
||||
if got := commandCount(device.commands, cmdToEncoder); got != 1 {
|
||||
t.Fatalf("to-encoder commands = %d, want 1", got)
|
||||
}
|
||||
if got := commandCount(device.commands, cmdStatus); got != 4 {
|
||||
t.Fatalf("status reads = %d, want 4", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareCurrentCardDoesNotMaskHardErrorAlongsideTransientPrepareFailure(t *testing.T) {
|
||||
client, _ := newSequenceTestClient(t,
|
||||
cmdResp{status: []byte{0x32, 0x30, 0x32, 0x30}},
|
||||
)
|
||||
|
||||
_, err := client.PrepareCurrentCard(context.Background())
|
||||
if err == nil || !strings.Contains(err.Error(), "Card jammed") {
|
||||
t.Fatalf("error = %v, want Card jammed", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareCurrentCardPollingAllowsTransientMovementWithStaleErrors(t *testing.T) {
|
||||
client, device := newSequenceTestClient(t,
|
||||
cmdResp{status: status(0x34)},
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
|
||||
builtVersion is a const in main.go
|
||||
|
||||
#### v2.0.2 - 22 September 2026
|
||||
fix(dispenser): retry on transient prepare failure
|
||||
|
||||
#### v2.0.1 - 10 September 2026
|
||||
feat: add CreditCall preauth streaming
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user