All checks were successful
continuous-integration/drone/push Build is passing
333 lines
9.0 KiB
Go
333 lines
9.0 KiB
Go
package mk2driver
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"sort"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
defaultTransactionRetries = 2
|
|
defaultTransactionRetryDelay = 200 * time.Millisecond
|
|
defaultTransactionBackoff = 1.5
|
|
fastCommandTimeout = 1500 * time.Millisecond
|
|
standardCommandTimeout = 3 * time.Second
|
|
slowCommandTimeout = 6 * time.Second
|
|
)
|
|
|
|
type registerKey struct {
|
|
kind RegisterKind
|
|
id uint16
|
|
}
|
|
|
|
func int16Ptr(v int16) *int16 {
|
|
return &v
|
|
}
|
|
|
|
var knownRegisterMetadata = map[registerKey]RegisterMetadata{
|
|
{kind: RegisterKindRAMVar, id: ramVarVMains}: {
|
|
Kind: RegisterKindRAMVar,
|
|
ID: ramVarVMains,
|
|
Name: "mains_voltage",
|
|
Description: "AC input mains voltage (scaled)",
|
|
Unit: "V",
|
|
Scale: 1,
|
|
Writable: false,
|
|
Signed: true,
|
|
SafetyClass: RegisterSafetyReadOnly,
|
|
},
|
|
{kind: RegisterKindRAMVar, id: ramVarIMains}: {
|
|
Kind: RegisterKindRAMVar,
|
|
ID: ramVarIMains,
|
|
Name: "mains_current",
|
|
Description: "AC input mains current (scaled)",
|
|
Unit: "A",
|
|
Scale: 1,
|
|
Writable: false,
|
|
Signed: true,
|
|
SafetyClass: RegisterSafetyReadOnly,
|
|
},
|
|
{kind: RegisterKindRAMVar, id: ramVarVInverter}: {
|
|
Kind: RegisterKindRAMVar,
|
|
ID: ramVarVInverter,
|
|
Name: "inverter_voltage",
|
|
Description: "AC output inverter voltage (scaled)",
|
|
Unit: "V",
|
|
Scale: 1,
|
|
Writable: false,
|
|
Signed: true,
|
|
SafetyClass: RegisterSafetyReadOnly,
|
|
},
|
|
{kind: RegisterKindRAMVar, id: ramVarIInverter}: {
|
|
Kind: RegisterKindRAMVar,
|
|
ID: ramVarIInverter,
|
|
Name: "inverter_current",
|
|
Description: "AC output inverter current (scaled)",
|
|
Unit: "A",
|
|
Scale: 1,
|
|
Writable: false,
|
|
Signed: true,
|
|
SafetyClass: RegisterSafetyReadOnly,
|
|
},
|
|
{kind: RegisterKindRAMVar, id: ramVarVBat}: {
|
|
Kind: RegisterKindRAMVar,
|
|
ID: ramVarVBat,
|
|
Name: "battery_voltage",
|
|
Description: "Battery voltage (scaled)",
|
|
Unit: "V",
|
|
Scale: 1,
|
|
Writable: false,
|
|
Signed: true,
|
|
SafetyClass: RegisterSafetyReadOnly,
|
|
},
|
|
{kind: RegisterKindRAMVar, id: ramVarIBat}: {
|
|
Kind: RegisterKindRAMVar,
|
|
ID: ramVarIBat,
|
|
Name: "battery_current",
|
|
Description: "Battery current (scaled)",
|
|
Unit: "A",
|
|
Scale: 1,
|
|
Writable: false,
|
|
Signed: true,
|
|
SafetyClass: RegisterSafetyReadOnly,
|
|
},
|
|
{kind: RegisterKindRAMVar, id: ramVarVBatRipple}: {
|
|
Kind: RegisterKindRAMVar,
|
|
ID: ramVarVBatRipple,
|
|
Name: "battery_voltage_ripple",
|
|
Description: "Battery ripple voltage (scaled)",
|
|
Unit: "V",
|
|
Scale: 1,
|
|
Writable: false,
|
|
Signed: true,
|
|
SafetyClass: RegisterSafetyReadOnly,
|
|
},
|
|
{kind: RegisterKindRAMVar, id: ramVarInverterPeriod}: {
|
|
Kind: RegisterKindRAMVar,
|
|
ID: ramVarInverterPeriod,
|
|
Name: "inverter_period",
|
|
Description: "Inverter period source value",
|
|
Unit: "ticks",
|
|
Scale: 1,
|
|
Writable: false,
|
|
Signed: true,
|
|
SafetyClass: RegisterSafetyReadOnly,
|
|
},
|
|
{kind: RegisterKindRAMVar, id: ramVarMainPeriod}: {
|
|
Kind: RegisterKindRAMVar,
|
|
ID: ramVarMainPeriod,
|
|
Name: "mains_period",
|
|
Description: "Mains period source value",
|
|
Unit: "ticks",
|
|
Scale: 1,
|
|
Writable: false,
|
|
Signed: true,
|
|
SafetyClass: RegisterSafetyReadOnly,
|
|
},
|
|
{kind: RegisterKindRAMVar, id: ramVarIACLoad}: {
|
|
Kind: RegisterKindRAMVar,
|
|
ID: ramVarIACLoad,
|
|
Name: "ac_load_current",
|
|
Description: "AC load current (scaled)",
|
|
Unit: "A",
|
|
Scale: 1,
|
|
Writable: false,
|
|
Signed: true,
|
|
SafetyClass: RegisterSafetyReadOnly,
|
|
},
|
|
{kind: RegisterKindRAMVar, id: ramVarVirSwitchPos}: {
|
|
Kind: RegisterKindRAMVar,
|
|
ID: ramVarVirSwitchPos,
|
|
Name: "virtual_switch_position",
|
|
Description: "Virtual switch position",
|
|
Unit: "state",
|
|
Scale: 1,
|
|
Writable: true,
|
|
Signed: false,
|
|
MinValue: int16Ptr(0),
|
|
MaxValue: int16Ptr(255),
|
|
SafetyClass: RegisterSafetyGuarded,
|
|
},
|
|
{kind: RegisterKindRAMVar, id: ramVarIgnACInState}: {
|
|
Kind: RegisterKindRAMVar,
|
|
ID: ramVarIgnACInState,
|
|
Name: "ignored_ac_input_state",
|
|
Description: "AC input state as seen by firmware",
|
|
Unit: "state",
|
|
Scale: 1,
|
|
Writable: false,
|
|
Signed: false,
|
|
SafetyClass: RegisterSafetyReadOnly,
|
|
},
|
|
{kind: RegisterKindRAMVar, id: ramVarMultiFuncRelay}: {
|
|
Kind: RegisterKindRAMVar,
|
|
ID: ramVarMultiFuncRelay,
|
|
Name: "multifunction_relay",
|
|
Description: "Multifunction relay state",
|
|
Unit: "state",
|
|
Scale: 1,
|
|
Writable: true,
|
|
Signed: false,
|
|
MinValue: int16Ptr(0),
|
|
MaxValue: int16Ptr(1),
|
|
SafetyClass: RegisterSafetyOperational,
|
|
},
|
|
{kind: RegisterKindRAMVar, id: ramVarChargeState}: {
|
|
Kind: RegisterKindRAMVar,
|
|
ID: ramVarChargeState,
|
|
Name: "battery_charge_state",
|
|
Description: "Battery charge state fraction",
|
|
Unit: "fraction",
|
|
Scale: 1,
|
|
Writable: false,
|
|
Signed: true,
|
|
SafetyClass: RegisterSafetyReadOnly,
|
|
},
|
|
{kind: RegisterKindRAMVar, id: ramVarInverterPower1}: {
|
|
Kind: RegisterKindRAMVar,
|
|
ID: ramVarInverterPower1,
|
|
Name: "inverter_power_1",
|
|
Description: "Inverter power source register 1",
|
|
Unit: "W",
|
|
Scale: 1,
|
|
Writable: false,
|
|
Signed: true,
|
|
SafetyClass: RegisterSafetyReadOnly,
|
|
},
|
|
{kind: RegisterKindRAMVar, id: ramVarInverterPower2}: {
|
|
Kind: RegisterKindRAMVar,
|
|
ID: ramVarInverterPower2,
|
|
Name: "inverter_power_2",
|
|
Description: "Inverter power source register 2",
|
|
Unit: "W",
|
|
Scale: 1,
|
|
Writable: false,
|
|
Signed: true,
|
|
SafetyClass: RegisterSafetyReadOnly,
|
|
},
|
|
{kind: RegisterKindRAMVar, id: ramVarOutPower}: {
|
|
Kind: RegisterKindRAMVar,
|
|
ID: ramVarOutPower,
|
|
Name: "output_power",
|
|
Description: "Output power source register",
|
|
Unit: "W",
|
|
Scale: 1,
|
|
Writable: false,
|
|
Signed: true,
|
|
SafetyClass: RegisterSafetyReadOnly,
|
|
},
|
|
}
|
|
|
|
func normalizeTransactionOptions(opts TransactionOptions) TransactionOptions {
|
|
if opts.Retries < 0 {
|
|
opts.Retries = 0
|
|
}
|
|
if opts.RetryDelay < 0 {
|
|
opts.RetryDelay = 0
|
|
}
|
|
if opts.BackoffFactor <= 0 {
|
|
opts.BackoffFactor = defaultTransactionBackoff
|
|
}
|
|
if opts.Retries == 0 && opts.RetryDelay == 0 && !opts.ReadBeforeWrite && !opts.VerifyAfterWrite {
|
|
opts.Retries = defaultTransactionRetries
|
|
opts.RetryDelay = defaultTransactionRetryDelay
|
|
opts.BackoffFactor = defaultTransactionBackoff
|
|
opts.ReadBeforeWrite = true
|
|
opts.VerifyAfterWrite = true
|
|
opts.TimeoutClass = TimeoutClassStandard
|
|
return opts
|
|
}
|
|
if opts.RetryDelay == 0 {
|
|
opts.RetryDelay = defaultTransactionRetryDelay
|
|
}
|
|
if opts.TimeoutClass == "" {
|
|
opts.TimeoutClass = TimeoutClassStandard
|
|
}
|
|
return opts
|
|
}
|
|
|
|
func lookupRegisterMetadata(kind RegisterKind, id uint16) (RegisterMetadata, bool) {
|
|
meta, ok := knownRegisterMetadata[registerKey{kind: kind, id: id}]
|
|
if ok {
|
|
meta = withMetadataDefaults(meta)
|
|
}
|
|
return meta, ok
|
|
}
|
|
|
|
func listRegisterMetadata() []RegisterMetadata {
|
|
out := make([]RegisterMetadata, 0, len(knownRegisterMetadata))
|
|
for _, meta := range knownRegisterMetadata {
|
|
out = append(out, withMetadataDefaults(meta))
|
|
}
|
|
sort.Slice(out, func(i, j int) bool {
|
|
if out[i].Kind != out[j].Kind {
|
|
return out[i].Kind < out[j].Kind
|
|
}
|
|
return out[i].ID < out[j].ID
|
|
})
|
|
return out
|
|
}
|
|
|
|
func validateValueAgainstMetadata(meta RegisterMetadata, value int16) error {
|
|
if meta.MinValue != nil && value < *meta.MinValue {
|
|
return fmt.Errorf("value %d is below minimum %d for %s:%d", value, *meta.MinValue, meta.Kind, meta.ID)
|
|
}
|
|
if meta.MaxValue != nil && value > *meta.MaxValue {
|
|
return fmt.Errorf("value %d is above maximum %d for %s:%d", value, *meta.MaxValue, meta.Kind, meta.ID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func withMetadataDefaults(meta RegisterMetadata) RegisterMetadata {
|
|
if meta.Scale == 0 {
|
|
meta.Scale = 1
|
|
}
|
|
if meta.SafetyClass == "" {
|
|
if meta.Writable {
|
|
meta.SafetyClass = RegisterSafetyGuarded
|
|
} else {
|
|
meta.SafetyClass = RegisterSafetyReadOnly
|
|
}
|
|
}
|
|
return meta
|
|
}
|
|
|
|
func resolveCommandTimeout(opts TransactionOptions) time.Duration {
|
|
if opts.CommandTimeout > 0 {
|
|
return opts.CommandTimeout
|
|
}
|
|
switch opts.TimeoutClass {
|
|
case TimeoutClassFast:
|
|
return fastCommandTimeout
|
|
case TimeoutClassSlow:
|
|
return slowCommandTimeout
|
|
default:
|
|
return standardCommandTimeout
|
|
}
|
|
}
|
|
|
|
func retryDelayForAttempt(opts TransactionOptions, attempt int) time.Duration {
|
|
if opts.RetryDelay <= 0 || attempt <= 1 {
|
|
return opts.RetryDelay
|
|
}
|
|
factor := math.Pow(opts.BackoffFactor, float64(attempt-1))
|
|
delay := float64(opts.RetryDelay) * factor
|
|
return time.Duration(delay)
|
|
}
|
|
|
|
func defaultWritableRegisterAddresses() []RegisterAddress {
|
|
metas := listRegisterMetadata()
|
|
out := make([]RegisterAddress, 0, len(metas))
|
|
for _, meta := range metas {
|
|
if !meta.Writable {
|
|
continue
|
|
}
|
|
out = append(out, RegisterAddress{
|
|
Kind: meta.Kind,
|
|
ID: meta.ID,
|
|
})
|
|
}
|
|
return out
|
|
}
|