implement some features of Venus OS
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
585
mk2driver/mk2.go
585
mk2driver/mk2.go
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -133,9 +134,22 @@ type mk2Ser struct {
|
||||
stateAck chan struct{}
|
||||
ifaceAck chan byte
|
||||
wg sync.WaitGroup
|
||||
|
||||
diagMu sync.Mutex
|
||||
traceLimit int
|
||||
traces []ProtocolTrace
|
||||
lastFrameAt time.Time
|
||||
recentErrors []string
|
||||
|
||||
commandTimeouts atomic.Uint64
|
||||
commandFailures atomic.Uint64
|
||||
checksumErrors atomic.Uint64
|
||||
}
|
||||
|
||||
var _ ProtocolControl = (*mk2Ser)(nil)
|
||||
var _ MetadataControl = (*mk2Ser)(nil)
|
||||
var _ SnapshotControl = (*mk2Ser)(nil)
|
||||
var _ DiagnosticsControl = (*mk2Ser)(nil)
|
||||
|
||||
func parseFrameLength(raw byte) (payloadLength byte, hasLEDStatus bool) {
|
||||
if raw&frameLengthLEDBit != 0 {
|
||||
@@ -175,6 +189,9 @@ func NewMk2Connection(dev io.ReadWriter) (Mk2, error) {
|
||||
mk2.winmonAck = make(chan winmonResponse, 32)
|
||||
mk2.stateAck = make(chan struct{}, 1)
|
||||
mk2.ifaceAck = make(chan byte, 1)
|
||||
mk2.traceLimit = 200
|
||||
mk2.traces = make([]ProtocolTrace, 0, mk2.traceLimit)
|
||||
mk2.recentErrors = make([]string, 0, 20)
|
||||
mk2.setTarget()
|
||||
mk2.run = make(chan struct{})
|
||||
mk2.infochan = make(chan *Mk2Info)
|
||||
@@ -270,6 +287,298 @@ func (m *mk2Ser) C() chan *Mk2Info {
|
||||
return m.infochan
|
||||
}
|
||||
|
||||
func (m *mk2Ser) RegisterMetadata(kind RegisterKind, id uint16) (RegisterMetadata, bool) {
|
||||
return lookupRegisterMetadata(kind, id)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) ListRegisterMetadata() []RegisterMetadata {
|
||||
return listRegisterMetadata()
|
||||
}
|
||||
|
||||
func (m *mk2Ser) ReadRegister(kind RegisterKind, id uint16) (int16, error) {
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
return m.readRegisterLocked(kind, id)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) WriteRegister(kind RegisterKind, id uint16, value int16, opts TransactionOptions) (RegisterTransactionResult, error) {
|
||||
options := normalizeTransactionOptions(opts)
|
||||
commandTimeout := resolveCommandTimeout(options)
|
||||
start := time.Now()
|
||||
result := RegisterTransactionResult{
|
||||
Kind: kind,
|
||||
ID: id,
|
||||
TargetValue: value,
|
||||
Timeout: commandTimeout,
|
||||
}
|
||||
|
||||
if meta, ok := lookupRegisterMetadata(kind, id); ok {
|
||||
if !meta.Writable {
|
||||
return result, fmt.Errorf("register %s:%d (%s) is marked read-only", kind, id, meta.Name)
|
||||
}
|
||||
if err := validateValueAgainstMetadata(meta, value); err != nil {
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
|
||||
if options.ReadBeforeWrite {
|
||||
prev, err := m.readRegisterLockedWithTimeout(kind, id, commandTimeout)
|
||||
if err != nil {
|
||||
result.Duration = time.Since(start)
|
||||
return result, fmt.Errorf("could not read current value for %s:%d: %w", kind, id, err)
|
||||
}
|
||||
result.PreviousValue = int16Ptr(prev)
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
maxAttempts := options.Retries + 1
|
||||
for attempt := 1; attempt <= maxAttempts; attempt++ {
|
||||
result.Attempts = attempt
|
||||
|
||||
err := m.writeRegisterLockedWithTimeout(kind, id, value, commandTimeout)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
if attempt < maxAttempts {
|
||||
delay := retryDelayForAttempt(options, attempt)
|
||||
if delay > 0 {
|
||||
time.Sleep(delay)
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if !options.VerifyAfterWrite {
|
||||
result.Duration = time.Since(start)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
verified, err := m.readRegisterLockedWithTimeout(kind, id, commandTimeout)
|
||||
if err != nil {
|
||||
lastErr = fmt.Errorf("verification read failed for %s:%d: %w", kind, id, err)
|
||||
if attempt < maxAttempts {
|
||||
delay := retryDelayForAttempt(options, attempt)
|
||||
if delay > 0 {
|
||||
time.Sleep(delay)
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
result.VerifiedValue = int16Ptr(verified)
|
||||
if verified != value {
|
||||
lastErr = fmt.Errorf("verification mismatch for %s:%d expected %d got %d", kind, id, value, verified)
|
||||
if attempt < maxAttempts {
|
||||
delay := retryDelayForAttempt(options, attempt)
|
||||
if delay > 0 {
|
||||
time.Sleep(delay)
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
result.Duration = time.Since(start)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
if lastErr == nil {
|
||||
lastErr = fmt.Errorf("transaction failed for %s:%d", kind, id)
|
||||
}
|
||||
result.Duration = time.Since(start)
|
||||
return result, lastErr
|
||||
}
|
||||
|
||||
func (m *mk2Ser) CaptureSnapshot(addresses []RegisterAddress) (RegisterSnapshot, error) {
|
||||
if len(addresses) == 0 {
|
||||
addresses = defaultWritableRegisterAddresses()
|
||||
}
|
||||
snapshotTime := time.Now().UTC()
|
||||
snapshot := RegisterSnapshot{
|
||||
CapturedAt: snapshotTime,
|
||||
Entries: make([]RegisterSnapshotEntry, 0, len(addresses)),
|
||||
}
|
||||
for _, address := range addresses {
|
||||
value, err := m.ReadRegister(address.Kind, address.ID)
|
||||
if err != nil {
|
||||
return snapshot, fmt.Errorf("capture snapshot read failed for %s:%d: %w", address.Kind, address.ID, err)
|
||||
}
|
||||
meta, ok := m.RegisterMetadata(address.Kind, address.ID)
|
||||
entry := RegisterSnapshotEntry{
|
||||
Kind: address.Kind,
|
||||
ID: address.ID,
|
||||
Value: value,
|
||||
CapturedAt: snapshotTime,
|
||||
}
|
||||
if ok {
|
||||
entry.Name = meta.Name
|
||||
entry.Writable = meta.Writable
|
||||
entry.Safety = meta.SafetyClass
|
||||
}
|
||||
snapshot.Entries = append(snapshot.Entries, entry)
|
||||
}
|
||||
return snapshot, nil
|
||||
}
|
||||
|
||||
func (m *mk2Ser) DiffSnapshot(snapshot RegisterSnapshot) ([]SnapshotDiff, error) {
|
||||
diffs := make([]SnapshotDiff, 0, len(snapshot.Entries))
|
||||
for _, entry := range snapshot.Entries {
|
||||
current, err := m.ReadRegister(entry.Kind, entry.ID)
|
||||
if err != nil {
|
||||
return diffs, fmt.Errorf("snapshot diff read failed for %s:%d: %w", entry.Kind, entry.ID, err)
|
||||
}
|
||||
meta, ok := m.RegisterMetadata(entry.Kind, entry.ID)
|
||||
name := entry.Name
|
||||
writable := entry.Writable
|
||||
safety := entry.Safety
|
||||
if ok {
|
||||
name = meta.Name
|
||||
writable = meta.Writable
|
||||
safety = meta.SafetyClass
|
||||
}
|
||||
diff := SnapshotDiff{
|
||||
Kind: entry.Kind,
|
||||
ID: entry.ID,
|
||||
Name: name,
|
||||
Current: current,
|
||||
Target: entry.Value,
|
||||
Changed: current != entry.Value,
|
||||
Writable: writable,
|
||||
Safety: safety,
|
||||
DiffValue: int32(entry.Value) - int32(current),
|
||||
}
|
||||
diffs = append(diffs, diff)
|
||||
}
|
||||
return diffs, nil
|
||||
}
|
||||
|
||||
func (m *mk2Ser) RestoreSnapshot(snapshot RegisterSnapshot, opts TransactionOptions) (SnapshotRestoreResult, error) {
|
||||
restoreResult := SnapshotRestoreResult{
|
||||
Applied: make([]RegisterTransactionResult, 0, len(snapshot.Entries)),
|
||||
}
|
||||
diffs, err := m.DiffSnapshot(snapshot)
|
||||
if err != nil {
|
||||
return restoreResult, err
|
||||
}
|
||||
for _, diff := range diffs {
|
||||
if !diff.Changed || !diff.Writable {
|
||||
continue
|
||||
}
|
||||
txResult, txErr := m.WriteRegister(diff.Kind, diff.ID, diff.Target, opts)
|
||||
if txErr == nil {
|
||||
restoreResult.Applied = append(restoreResult.Applied, txResult)
|
||||
continue
|
||||
}
|
||||
|
||||
restoreResult.RolledBack = true
|
||||
rollbackErrs := m.rollbackAppliedTransactions(restoreResult.Applied, opts)
|
||||
restoreResult.RollbackErrors = append(restoreResult.RollbackErrors, rollbackErrs...)
|
||||
return restoreResult, fmt.Errorf("restore failed for %s:%d: %w", diff.Kind, diff.ID, txErr)
|
||||
}
|
||||
return restoreResult, nil
|
||||
}
|
||||
|
||||
func (m *mk2Ser) rollbackAppliedTransactions(applied []RegisterTransactionResult, opts TransactionOptions) []string {
|
||||
errs := make([]string, 0)
|
||||
rollbackOpts := normalizeTransactionOptions(TransactionOptions{
|
||||
Retries: 1,
|
||||
RetryDelay: opts.RetryDelay,
|
||||
BackoffFactor: opts.BackoffFactor,
|
||||
VerifyAfterWrite: true,
|
||||
TimeoutClass: opts.TimeoutClass,
|
||||
CommandTimeout: opts.CommandTimeout,
|
||||
})
|
||||
for i := len(applied) - 1; i >= 0; i-- {
|
||||
tx := applied[i]
|
||||
if tx.PreviousValue == nil {
|
||||
continue
|
||||
}
|
||||
if _, err := m.WriteRegister(tx.Kind, tx.ID, *tx.PreviousValue, rollbackOpts); err != nil {
|
||||
errs = append(errs, fmt.Sprintf("rollback failed for %s:%d: %v", tx.Kind, tx.ID, err))
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func (m *mk2Ser) DriverDiagnostics(limit int) DriverDiagnostics {
|
||||
m.diagMu.Lock()
|
||||
defer m.diagMu.Unlock()
|
||||
|
||||
traceLimit := limit
|
||||
if traceLimit <= 0 || traceLimit > len(m.traces) {
|
||||
traceLimit = len(m.traces)
|
||||
}
|
||||
traces := make([]ProtocolTrace, traceLimit)
|
||||
if traceLimit > 0 {
|
||||
copy(traces, m.traces[len(m.traces)-traceLimit:])
|
||||
}
|
||||
recentErrors := append([]string(nil), m.recentErrors...)
|
||||
|
||||
diag := DriverDiagnostics{
|
||||
GeneratedAt: time.Now().UTC(),
|
||||
CommandTimeouts: m.commandTimeouts.Load(),
|
||||
CommandFailures: m.commandFailures.Load(),
|
||||
ChecksumFailures: m.checksumErrors.Load(),
|
||||
RecentErrors: recentErrors,
|
||||
Traces: traces,
|
||||
}
|
||||
if !m.lastFrameAt.IsZero() {
|
||||
last := m.lastFrameAt
|
||||
diag.LastFrameAt = &last
|
||||
}
|
||||
diag.HealthScore = calculateDriverHealthScore(diag)
|
||||
return diag
|
||||
}
|
||||
|
||||
func (m *mk2Ser) readRegisterLocked(kind RegisterKind, id uint16) (int16, error) {
|
||||
return m.readRegisterLockedWithTimeout(kind, id, writeResponseTimeout)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) readRegisterLockedWithTimeout(kind RegisterKind, id uint16, timeout time.Duration) (int16, error) {
|
||||
switch kind {
|
||||
case RegisterKindRAMVar:
|
||||
return m.readValueByIDWithTimeout(commandReadRAMVar, commandReadRAMResponse, id, timeout)
|
||||
case RegisterKindSetting:
|
||||
return m.readValueByIDWithTimeout(commandReadSetting, commandReadSettingResponse, id, timeout)
|
||||
default:
|
||||
return 0, fmt.Errorf("unsupported register kind %q", kind)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mk2Ser) writeRegisterLocked(kind RegisterKind, id uint16, value int16) error {
|
||||
return m.writeRegisterLockedWithTimeout(kind, id, value, writeResponseTimeout)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) writeRegisterLockedWithTimeout(kind RegisterKind, id uint16, value int16, timeout time.Duration) error {
|
||||
switch kind {
|
||||
case RegisterKindRAMVar:
|
||||
err := m.writeByIDOnlyWithTimeout(commandWriteRAMViaID, commandWriteRAMViaIDResponse, id, value, timeout)
|
||||
if err != nil && !errors.Is(err, errWriteRejected) {
|
||||
mk2log.WithFields(logrus.Fields{
|
||||
"id": id,
|
||||
"value": value,
|
||||
}).WithError(err).Warn("WriteRegister RAM by-id failed, trying legacy write path")
|
||||
err = m.writeBySelectionWithTimeout(commandWriteRAMVar, commandWriteRAMResponse, id, value, timeout)
|
||||
}
|
||||
return err
|
||||
case RegisterKindSetting:
|
||||
err := m.writeByIDOnlyWithTimeout(commandWriteViaID, commandWriteViaIDResponse, id, value, timeout)
|
||||
if err != nil && !errors.Is(err, errWriteRejected) {
|
||||
mk2log.WithFields(logrus.Fields{
|
||||
"id": id,
|
||||
"value": value,
|
||||
}).WithError(err).Warn("WriteRegister setting by-id failed, trying legacy write path")
|
||||
err = m.writeBySelectionWithTimeout(commandWriteSetting, commandWriteSettingResponse, id, value, timeout)
|
||||
}
|
||||
return err
|
||||
default:
|
||||
return fmt.Errorf("unsupported register kind %q", kind)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mk2Ser) WriteRAMVar(id uint16, value int16) error {
|
||||
mk2log.WithFields(logrus.Fields{
|
||||
"id": id,
|
||||
@@ -340,6 +649,39 @@ func (m *mk2Ser) WriteRAMVarByID(id uint16, value int16) error {
|
||||
return m.writeByIDOnly(commandWriteRAMViaID, commandWriteRAMViaIDResponse, id, value)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) WriteSettingBySelection(id uint16, value int16) error {
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
return m.writeBySelection(commandWriteSetting, commandWriteSettingResponse, id, value)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) WriteRAMVarBySelection(id uint16, value int16) error {
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
return m.writeBySelection(commandWriteRAMVar, commandWriteRAMResponse, id, value)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) WriteSelectedData(value int16) error {
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
|
||||
m.clearWriteResponses()
|
||||
raw := uint16(value)
|
||||
m.sendCommandLocked([]byte{
|
||||
winmonFrame,
|
||||
commandWriteData,
|
||||
byte(raw),
|
||||
byte(raw >> 8),
|
||||
})
|
||||
_, err := m.waitForAnyWriteResponseWithTimeout([]byte{
|
||||
commandWriteRAMResponse,
|
||||
commandWriteSettingResponse,
|
||||
commandWriteViaIDResponse,
|
||||
commandWriteRAMViaIDResponse,
|
||||
}, writeResponseTimeout)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *mk2Ser) GetDeviceState() (DeviceState, error) {
|
||||
m.beginCommand()
|
||||
defer m.endCommand()
|
||||
@@ -552,6 +894,10 @@ func encodePanelCurrentLimit(currentLimitA *float64) (uint16, error) {
|
||||
}
|
||||
|
||||
func (m *mk2Ser) writeByIDOnly(writeCommand, expectedResponse byte, id uint16, value int16) error {
|
||||
return m.writeByIDOnlyWithTimeout(writeCommand, expectedResponse, id, value, writeResponseTimeout)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) writeByIDOnlyWithTimeout(writeCommand, expectedResponse byte, id uint16, value int16, timeout time.Duration) error {
|
||||
m.clearWriteResponses()
|
||||
rawValue := uint16(value)
|
||||
m.sendCommandLocked([]byte{
|
||||
@@ -562,10 +908,14 @@ func (m *mk2Ser) writeByIDOnly(writeCommand, expectedResponse byte, id uint16, v
|
||||
byte(rawValue),
|
||||
byte(rawValue >> 8),
|
||||
})
|
||||
return m.waitForWriteResponse(expectedResponse)
|
||||
return m.waitForWriteResponseWithTimeout(expectedResponse, timeout)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) writeBySelection(selectCommand, expectedResponse byte, id uint16, value int16) error {
|
||||
return m.writeBySelectionWithTimeout(selectCommand, expectedResponse, id, value, writeResponseTimeout)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) writeBySelectionWithTimeout(selectCommand, expectedResponse byte, id uint16, value int16, timeout time.Duration) error {
|
||||
m.clearWriteResponses()
|
||||
rawValue := uint16(value)
|
||||
m.sendCommandLocked([]byte{
|
||||
@@ -581,10 +931,14 @@ func (m *mk2Ser) writeBySelection(selectCommand, expectedResponse byte, id uint1
|
||||
byte(rawValue >> 8),
|
||||
})
|
||||
|
||||
return m.waitForWriteResponse(expectedResponse)
|
||||
return m.waitForWriteResponseWithTimeout(expectedResponse, timeout)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) readValueByID(readCommand, expectedResponse byte, id uint16) (int16, error) {
|
||||
return m.readValueByIDWithTimeout(readCommand, expectedResponse, id, writeResponseTimeout)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) readValueByIDWithTimeout(readCommand, expectedResponse byte, id uint16, timeout time.Duration) (int16, error) {
|
||||
m.clearWinmonResponses()
|
||||
m.sendCommandLocked([]byte{
|
||||
winmonFrame,
|
||||
@@ -593,7 +947,7 @@ func (m *mk2Ser) readValueByID(readCommand, expectedResponse byte, id uint16) (i
|
||||
byte(id >> 8),
|
||||
})
|
||||
|
||||
resp, err := m.waitForWinmonResponse(expectedResponse)
|
||||
resp, err := m.waitForWinmonResponseWithTimeout(expectedResponse, timeout)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -670,6 +1024,10 @@ func (m *mk2Ser) clearWriteResponses() {
|
||||
}
|
||||
|
||||
func (m *mk2Ser) waitForWriteResponse(expectedResponse byte) error {
|
||||
return m.waitForWriteResponseWithTimeout(expectedResponse, writeResponseTimeout)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) waitForWriteResponseWithTimeout(expectedResponse byte, timeout time.Duration) error {
|
||||
if m.writeAck == nil {
|
||||
return errors.New("write response channel is not initialized")
|
||||
}
|
||||
@@ -684,15 +1042,56 @@ func (m *mk2Ser) waitForWriteResponse(expectedResponse byte) error {
|
||||
case expectedResponse:
|
||||
return nil
|
||||
case commandUnsupportedResponse:
|
||||
m.noteCommandFailure(fmt.Errorf("received unsupported response 0x%02x", response))
|
||||
return errCommandUnsupported
|
||||
case commandWriteNotAllowedResponse:
|
||||
m.noteCommandFailure(fmt.Errorf("received write rejected response 0x%02x", response))
|
||||
return errWriteRejected
|
||||
default:
|
||||
return fmt.Errorf("unexpected write response 0x%02x", response)
|
||||
err := fmt.Errorf("unexpected write response 0x%02x", response)
|
||||
m.noteCommandFailure(err)
|
||||
return err
|
||||
}
|
||||
case <-time.After(writeResponseTimeout):
|
||||
mk2log.WithField("expected_response", fmt.Sprintf("0x%02x", expectedResponse)).Error("Timed out waiting for write acknowledgement")
|
||||
return fmt.Errorf("timed out waiting for write response after %s", writeResponseTimeout)
|
||||
case <-time.After(timeout):
|
||||
err := fmt.Errorf("timed out waiting for write response after %s", timeout)
|
||||
mk2log.WithField("expected_response", fmt.Sprintf("0x%02x", expectedResponse)).WithError(err).Error("Timed out waiting for write acknowledgement")
|
||||
m.noteCommandTimeout(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mk2Ser) waitForAnyWriteResponseWithTimeout(expectedResponses []byte, timeout time.Duration) (byte, error) {
|
||||
if m.writeAck == nil {
|
||||
return 0, errors.New("write response channel is not initialized")
|
||||
}
|
||||
expected := make(map[byte]struct{}, len(expectedResponses))
|
||||
for _, response := range expectedResponses {
|
||||
expected[response] = struct{}{}
|
||||
}
|
||||
|
||||
select {
|
||||
case response := <-m.writeAck:
|
||||
if _, ok := expected[response]; ok {
|
||||
return response, nil
|
||||
}
|
||||
switch response {
|
||||
case commandUnsupportedResponse:
|
||||
err := fmt.Errorf("received unsupported write response 0x%02x", response)
|
||||
m.noteCommandFailure(err)
|
||||
return response, errCommandUnsupported
|
||||
case commandWriteNotAllowedResponse:
|
||||
err := fmt.Errorf("received write rejected response 0x%02x", response)
|
||||
m.noteCommandFailure(err)
|
||||
return response, errWriteRejected
|
||||
default:
|
||||
err := fmt.Errorf("unexpected write response 0x%02x", response)
|
||||
m.noteCommandFailure(err)
|
||||
return response, err
|
||||
}
|
||||
case <-time.After(timeout):
|
||||
err := fmt.Errorf("timed out waiting for write response after %s", timeout)
|
||||
m.noteCommandTimeout(err)
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -722,11 +1121,15 @@ func (m *mk2Ser) clearWinmonResponses() {
|
||||
}
|
||||
|
||||
func (m *mk2Ser) waitForWinmonResponse(expectedResponse byte) (winmonResponse, error) {
|
||||
return m.waitForWinmonResponseWithTimeout(expectedResponse, writeResponseTimeout)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) waitForWinmonResponseWithTimeout(expectedResponse byte, timeout time.Duration) (winmonResponse, error) {
|
||||
if m.winmonAck == nil {
|
||||
return winmonResponse{}, errors.New("winmon response channel is not initialized")
|
||||
}
|
||||
|
||||
timeout := time.After(writeResponseTimeout)
|
||||
timeoutChan := time.After(timeout)
|
||||
for {
|
||||
select {
|
||||
case response := <-m.winmonAck:
|
||||
@@ -740,8 +1143,10 @@ func (m *mk2Ser) waitForWinmonResponse(expectedResponse byte) (winmonResponse, e
|
||||
case expectedResponse:
|
||||
return response, nil
|
||||
case commandUnsupportedResponse:
|
||||
m.noteCommandFailure(fmt.Errorf("received unsupported winmon response 0x%02x", response.command))
|
||||
return winmonResponse{}, errCommandUnsupported
|
||||
case commandWriteNotAllowedResponse:
|
||||
m.noteCommandFailure(fmt.Errorf("received write rejected winmon response 0x%02x", response.command))
|
||||
return winmonResponse{}, errWriteRejected
|
||||
default:
|
||||
mk2log.WithFields(logrus.Fields{
|
||||
@@ -749,8 +1154,10 @@ func (m *mk2Ser) waitForWinmonResponse(expectedResponse byte) (winmonResponse, e
|
||||
"received_response": fmt.Sprintf("0x%02x", response.command),
|
||||
}).Debug("Ignoring unrelated winmon response while waiting")
|
||||
}
|
||||
case <-timeout:
|
||||
return winmonResponse{}, fmt.Errorf("timed out waiting for winmon response 0x%02x after %s", expectedResponse, writeResponseTimeout)
|
||||
case <-timeoutChan:
|
||||
err := fmt.Errorf("timed out waiting for winmon response 0x%02x after %s", expectedResponse, timeout)
|
||||
m.noteCommandTimeout(err)
|
||||
return winmonResponse{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -783,6 +1190,10 @@ func (m *mk2Ser) clearStateResponses() {
|
||||
}
|
||||
|
||||
func (m *mk2Ser) waitForStateResponse() error {
|
||||
return m.waitForStateResponseWithTimeout(writeResponseTimeout)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) waitForStateResponseWithTimeout(timeout time.Duration) error {
|
||||
if m.stateAck == nil {
|
||||
return errors.New("panel state response channel is not initialized")
|
||||
}
|
||||
@@ -791,9 +1202,11 @@ func (m *mk2Ser) waitForStateResponse() error {
|
||||
case <-m.stateAck:
|
||||
mk2log.Debug("Received panel state acknowledgement")
|
||||
return nil
|
||||
case <-time.After(writeResponseTimeout):
|
||||
mk2log.Error("Timed out waiting for panel state acknowledgement")
|
||||
return fmt.Errorf("timed out waiting for panel state response after %s", writeResponseTimeout)
|
||||
case <-time.After(timeout):
|
||||
err := fmt.Errorf("timed out waiting for panel state response after %s", timeout)
|
||||
mk2log.WithError(err).Error("Timed out waiting for panel state acknowledgement")
|
||||
m.noteCommandTimeout(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -823,6 +1236,10 @@ func (m *mk2Ser) clearInterfaceResponses() {
|
||||
}
|
||||
|
||||
func (m *mk2Ser) waitForInterfaceResponse(expectedStandby bool) error {
|
||||
return m.waitForInterfaceResponseWithTimeout(expectedStandby, writeResponseTimeout)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) waitForInterfaceResponseWithTimeout(expectedStandby bool, timeout time.Duration) error {
|
||||
if m.ifaceAck == nil {
|
||||
return errors.New("interface response channel is not initialized")
|
||||
}
|
||||
@@ -836,12 +1253,16 @@ func (m *mk2Ser) waitForInterfaceResponse(expectedStandby bool) error {
|
||||
"actual_standby": standbyEnabled,
|
||||
}).Debug("Received standby interface acknowledgement")
|
||||
if standbyEnabled != expectedStandby {
|
||||
return fmt.Errorf("unexpected standby line state 0x%02x", lineState)
|
||||
err := fmt.Errorf("unexpected standby line state 0x%02x", lineState)
|
||||
m.noteCommandFailure(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case <-time.After(writeResponseTimeout):
|
||||
mk2log.WithField("expected_standby", expectedStandby).Error("Timed out waiting for standby acknowledgement")
|
||||
return fmt.Errorf("timed out waiting for standby response after %s", writeResponseTimeout)
|
||||
case <-time.After(timeout):
|
||||
err := fmt.Errorf("timed out waiting for standby response after %s", timeout)
|
||||
mk2log.WithField("expected_standby", expectedStandby).WithError(err).Error("Timed out waiting for standby acknowledgement")
|
||||
m.noteCommandTimeout(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -874,6 +1295,18 @@ func (m *mk2Ser) addError(err error) {
|
||||
}
|
||||
m.info.Errors = append(m.info.Errors, err)
|
||||
m.info.Valid = false
|
||||
m.recordError(err)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) noteCommandTimeout(err error) {
|
||||
m.commandTimeouts.Add(1)
|
||||
m.commandFailures.Add(1)
|
||||
m.recordError(err)
|
||||
}
|
||||
|
||||
func (m *mk2Ser) noteCommandFailure(err error) {
|
||||
m.commandFailures.Add(1)
|
||||
m.recordError(err)
|
||||
}
|
||||
|
||||
// Updates report.
|
||||
@@ -905,6 +1338,8 @@ func (m *mk2Ser) handleFrame(l byte, frame []byte, appendedLED []byte) {
|
||||
return
|
||||
}
|
||||
if checkChecksum(l, frame[0], frame[1:]) {
|
||||
m.markFrameSeen()
|
||||
m.recordRXTrace(frame)
|
||||
switch frame[0] {
|
||||
case bootupFrameHeader:
|
||||
if m.pollPaused.Load() {
|
||||
@@ -988,8 +1423,10 @@ func (m *mk2Ser) handleFrame(l byte, frame []byte, appendedLED []byte) {
|
||||
mk2log.Warnf("[handleFrame] Invalid frame %v", frame[0])
|
||||
}
|
||||
} else {
|
||||
m.checksumErrors.Add(1)
|
||||
mk2log.Errorf("[handleFrame] Invalid incoming frame checksum: %x", frame)
|
||||
m.frameLock = false
|
||||
m.recordError(fmt.Errorf("invalid incoming frame checksum"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1220,13 +1657,127 @@ func (m *mk2Ser) sendCommandLocked(data []byte) {
|
||||
dataOut[l+2] = cr
|
||||
|
||||
mk2log.Debugf("sendCommand %#v", dataOut)
|
||||
m.recordTXTrace(dataOut, data)
|
||||
_, err := m.p.Write(dataOut)
|
||||
if err != nil {
|
||||
mk2log.WithError(err).Error("Failed to send MK2 command")
|
||||
m.addError(fmt.Errorf("Write error: %v", err))
|
||||
m.noteCommandFailure(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mk2Ser) markFrameSeen() {
|
||||
m.diagMu.Lock()
|
||||
m.lastFrameAt = time.Now().UTC()
|
||||
m.diagMu.Unlock()
|
||||
}
|
||||
|
||||
func (m *mk2Ser) recordError(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
m.diagMu.Lock()
|
||||
defer m.diagMu.Unlock()
|
||||
m.recentErrors = append(m.recentErrors, err.Error())
|
||||
if len(m.recentErrors) > 20 {
|
||||
m.recentErrors = m.recentErrors[len(m.recentErrors)-20:]
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mk2Ser) recordTXTrace(fullFrame []byte, payload []byte) {
|
||||
command := ""
|
||||
frame := ""
|
||||
if len(payload) > 0 {
|
||||
frame = fmt.Sprintf("0x%02x", payload[0])
|
||||
command = decodeTraceCommandName(payload)
|
||||
}
|
||||
m.appendTrace(ProtocolTrace{
|
||||
Timestamp: time.Now().UTC(),
|
||||
Direction: TraceDirectionTX,
|
||||
Frame: frame,
|
||||
Command: command,
|
||||
BytesHex: strings.ToUpper(fmt.Sprintf("%X", fullFrame)),
|
||||
})
|
||||
}
|
||||
|
||||
func (m *mk2Ser) recordRXTrace(frame []byte) {
|
||||
command := ""
|
||||
frameName := ""
|
||||
if len(frame) > 0 {
|
||||
frameName = fmt.Sprintf("0x%02x", frame[0])
|
||||
command = decodeTraceCommandName(frame)
|
||||
}
|
||||
m.appendTrace(ProtocolTrace{
|
||||
Timestamp: time.Now().UTC(),
|
||||
Direction: TraceDirectionRX,
|
||||
Frame: frameName,
|
||||
Command: command,
|
||||
BytesHex: strings.ToUpper(fmt.Sprintf("%X", frame)),
|
||||
})
|
||||
}
|
||||
|
||||
func decodeTraceCommandName(frame []byte) string {
|
||||
if len(frame) == 0 {
|
||||
return ""
|
||||
}
|
||||
switch frame[0] {
|
||||
case winmonFrame:
|
||||
if len(frame) < 2 {
|
||||
return "winmon"
|
||||
}
|
||||
return fmt.Sprintf("winmon:0x%02x", frame[1])
|
||||
case stateFrame:
|
||||
return "panel_state"
|
||||
case interfaceFrame:
|
||||
return "interface"
|
||||
case infoReqFrame:
|
||||
return "info_request"
|
||||
case ledFrame:
|
||||
return "led_status"
|
||||
case vFrame:
|
||||
return "version"
|
||||
case setTargetFrame:
|
||||
return "set_target"
|
||||
default:
|
||||
if frame[0] == frameHeader && len(frame) > 1 && frame[1] == winmonFrame && len(frame) > 2 {
|
||||
return fmt.Sprintf("winmon:0x%02x", frame[2])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mk2Ser) appendTrace(trace ProtocolTrace) {
|
||||
m.diagMu.Lock()
|
||||
defer m.diagMu.Unlock()
|
||||
m.traces = append(m.traces, trace)
|
||||
limit := m.traceLimit
|
||||
if limit <= 0 {
|
||||
limit = 200
|
||||
}
|
||||
if len(m.traces) > limit {
|
||||
m.traces = m.traces[len(m.traces)-limit:]
|
||||
}
|
||||
}
|
||||
|
||||
func calculateDriverHealthScore(diag DriverDiagnostics) int {
|
||||
score := 100
|
||||
score -= int(diag.CommandTimeouts) * 5
|
||||
score -= int(diag.CommandFailures) * 2
|
||||
score -= int(diag.ChecksumFailures) * 3
|
||||
if diag.LastFrameAt == nil {
|
||||
score -= 10
|
||||
} else if time.Since(diag.LastFrameAt.UTC()) > 30*time.Second {
|
||||
score -= 10
|
||||
}
|
||||
if score < 0 {
|
||||
return 0
|
||||
}
|
||||
if score > 100 {
|
||||
return 100
|
||||
}
|
||||
return score
|
||||
}
|
||||
|
||||
// Checks the frame crc.
|
||||
func checkChecksum(l, t byte, d []byte) bool {
|
||||
cr := (uint16(l) + uint16(t)) % 256
|
||||
|
||||
Reference in New Issue
Block a user