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 }