Skip to content
tsgb edited this page Jun 4, 2026 · 1 revision

Models

The OpenMppt.Model namespace contains the shared, serialization-friendly record types that every layer of the stack passes around. Codecs decode into them; drivers surface them via events; dashboards and loggers consume them. Because they are plain C# records with no internal I/O, they serialize cleanly to JSON with System.Text.Json or any compatible serializer — no custom converters required.


ITelemetrySample

public interface ITelemetrySample
{
    long TimestampMs { get; }
}

A marker interface that tags any record carrying a timestamp. It exists solely so loggers, time-series stores, and dashboards can accept a uniform stream of samples without needing to know the concrete type.

MpptLive is the only type in the library that currently implements ITelemetrySample. Future record types — once more vendor protocols are decoded — will implement the same interface, letting a single ingestion pipeline handle all of them without modification.


MpptLive

MpptLive is the main live-telemetry snapshot. One instance is produced per successful poll cycle and surfaced on IDeviceDriver.LiveChanged.

public record MpptLive(
    long   TimestampMs,
    double BatteryVoltage,
    double ChargeCurrent,
    double DischargeCurrent,
    double TemperatureC,
    int    SolarStatusRaw,
    int    WorkStatusRaw,
    int    PowerStatusRaw,
    double TotalAccumulatedAh,
    ChargerState ChargerState,
    double SocEstimate
) : ITelemetrySample

Stored fields

Field Type Description
TimestampMs long Unix epoch milliseconds (UTC)
BatteryVoltage double Terminal voltage in volts
ChargeCurrent double Amps flowing into the battery
DischargeCurrent double Amps flowing out of the battery (load side)
TemperatureC double Controller temperature in °C
SolarStatusRaw int Raw register value; input to ChargerStateLogic.FromRegisters
WorkStatusRaw int Raw register value; input to ChargerStateLogic.FromRegisters
PowerStatusRaw int Raw register value; input to ChargerStateLogic.FromRegisters
TotalAccumulatedAh double Lifetime Ah counter reported by the device
ChargerState ChargerState Decoded charger state (see below)
SocEstimate double State of charge, 0.0–1.0

Computed properties

Three watt figures are computed from the stored fields and are always available without any additional decode step:

Property Formula
BatteryWatts BatteryVoltage × ChargeCurrent
LoadWatts BatteryVoltage × DischargeCurrent
ApproxPvWatts BatteryWatts + LoadWatts

ApproxPvWatts is an estimate of PV harvest. It is accurate when the controller is actively charging and the battery current and load current are both sensed; it will be less accurate during idle or fault states.

Static EstimateSoc

double soc = MpptLive.EstimateSoc(batteryVoltage);

A static 12 V lead-acid voltage-to-SoC lookup table. Use this as the socFromVoltage delegate when no MpptSettings or BatteryProfile is available yet — for example on the very first poll before settings have been read.

For any pack whose cutoff and charge setpoints are known, prefer MpptSettings.ComputeSoc or BatteryProfile.SocFromVoltage instead. EstimateSoc is a reasonable last resort, not the primary path.


MpptSettings

MpptSettings holds the configurable setpoints read from the controller's holding registers. It is surfaced on IDeviceDriver.SettingsChanged after ReadSettingsAsync completes.

public record MpptSettings(
    int    BatteryType,
    int    TimerHours,
    int    TimerMinutes,
    double ChargeVoltageSetpoint,
    int    OutputMode,
    double CutoffVoltageSetpoint,
    bool   ManualLoadOn,
    int    VoltageMonitorMode,
    double RecoveryVoltageSetpoint
)

Fields

Field Type Description
BatteryType int Raw battery-type register value
TimerHours int Load-timer hour setting
TimerMinutes int Load-timer minute setting
ChargeVoltageSetpoint double Bulk/absorption charge target (V); treated as 100 % SoC by ComputeSoc
OutputMode int Raw output-mode register value
CutoffVoltageSetpoint double Low-voltage disconnect threshold (V); treated as 0 % SoC by ComputeSoc
ManualLoadOn bool Manual load override state
VoltageMonitorMode int Raw voltage-monitor-mode register value
RecoveryVoltageSetpoint double Low-voltage reconnect threshold (V)

Typed enum accessors

BatteryType  bt = settings.BatteryTypeEnum;   // BatteryType enum
OutputMode   om = settings.OutputModeEnum;    // OutputMode enum

BatteryTypeEnum and OutputModeEnum decode the raw integer fields into the corresponding enums so you do not need to cast or pattern-match integers directly.

ComputeSoc

double? soc = settings.ComputeSoc(batteryVoltage);

Linear interpolation between the two voltage setpoints the controller already stores:

  • CutoffVoltageSetpoint0 %
  • ChargeVoltageSetpoint100 %

Returns null when the two setpoints are equal or in the wrong order (degenerate configuration). The result is not clamped — voltages outside the setpoint range return values below 0.0 or above 1.0, which callers can clamp or flag as out-of-range as appropriate.

// Typical usage after settings are first received
driver.SettingsChanged += settings =>
{
    if (driver.Live is { } live)
    {
        double soc = settings.ComputeSoc(live.BatteryVoltage) ?? 0.0;
        UpdateSocDisplay(soc);
    }
};

BatteryType enum

Member Value Description
Unknown 0 Register value not recognised
SealedLead 1 Sealed lead-acid (AGM)
GelLead 2 Gel lead-acid
FloodedLead 3 Flooded/wet lead-acid
Lithium 4 Lithium (generic; exact chemistry unspecified)

OutputMode enum

Member Value Description
Manual 0 Load output controlled by ManualLoadOn
Auto 1 Controller manages load output automatically
Timer 2 Load output follows the timer schedule
Unknown 255 Register value not recognised

ChargerState

ChargerState describes the controller's current charging or load-management phase.

public enum ChargerState
{
    Bulk,
    Boost,
    Float,
    Idle,
    LoadOff,
    Fault,
    Unknown
}

ChargerStateLogic.FromRegisters

The enum value in MpptLive.ChargerState is decoded by the static heuristic:

ChargerState state = ChargerStateLogic.FromRegisters(
    solarStatus:     live.SolarStatusRaw,
    workStatus:      live.WorkStatusRaw,
    powerStatus:     live.PowerStatusRaw,
    chargeCurrent:   live.ChargeCurrent,
    dischargeCurrent: live.DischargeCurrent,
    batteryVoltage:  live.BatteryVoltage);

MpptRegisters.DecodeLive calls this internally, so the field is already decoded when MpptLive arrives on the event. FromRegisters is public for cases where you need to re-evaluate state from a stored raw snapshot.

Label()

string display = live.ChargerState.Label();
// → "Bulk", "Boost", "Float", "Idle", "Load off", "Fault", "Unknown"

Label() is an extension method returning a short, human-readable string suitable for display in a UI or log line.


BatteryProfile

BatteryProfile carries the physical characteristics of the battery pack connected to the controller. It is constructed by the host application from user-entered values; no controller protocol surfaces these directly.

public record BatteryProfile(
    double EmptyV,
    double FullV,
    double NominalV,
    double CapacityAh
)
Field Description
EmptyV Terminal voltage at which the pack is considered fully discharged
FullV Terminal voltage at which the pack is considered fully charged
NominalV Nominal pack voltage (used for display and Wh conversions)
CapacityAh Rated capacity in ampere-hours

CapacityWh

double wh = profile.CapacityWh;   // CapacityAh × NominalV

Nominal energy capacity in watt-hours, derived from CapacityAh and NominalV.

SocFromVoltage

double soc = profile.SocFromVoltage(batteryVoltage);

Converts a measured terminal voltage to a state-of-charge fraction (0.0–1.0) by linear interpolation between EmptyV (0 %) and FullV (100 %). This is the delegate you pass to MpptRegisters.DecodeLive:

var profile = new BatteryProfile(EmptyV: 20.0, FullV: 25.5, NominalV: 22.1, CapacityAh: 105);

MpptLive live = MpptRegisters.DecodeLive(
    regs,
    DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
    socFromVoltage: profile.SocFromVoltage);

DeltaWh

double delta = profile.DeltaWh(fromSoc, toSoc);

Returns the energy transferred (Wh) for a state-of-charge change from fromSoc to toSoc. The sign is positive when the SoC increases (charging) and negative when it decreases (discharging). Used by energy loggers to accumulate daily yield and consumption figures.

InferredNetCurrentA

double amps = profile.InferredNetCurrentA(fromSoc, toSoc, elapsedMs);

Back-calculates the average net current (A) implied by an observed SoC change over elapsedMs milliseconds. This is the energy-inference path for cases where current is not directly sensed — for example, inferring net battery current from DeltaWh and elapsed time when only voltage is available.

// How much energy moved in the last poll interval?
double delta = profile.DeltaWh(previousSoc, currentSoc);

// What average net current does that imply?
double inferredA = profile.InferredNetCurrentA(previousSoc, currentSoc, elapsedMs: 5000);

BatteryChemistry

BatteryChemistry describes the electrochemical type of a pack independently of any controller register value. It is used by the host application's battery configuration UI; it is not decoded from any protocol.

public enum BatteryChemistry
{
    Unknown,
    LeadAcid,
    Gel,
    Flooded,
    LiFePO4,
    NMC,
    NCA,
    LTO,
    Custom
}

DisplayName()

string name = BatteryChemistry.LiFePO4.DisplayName();
// → "LiFePO4"

An extension method returning a human-readable label for each chemistry. Useful for populating a chemistry picker in a settings screen.

TypicalNominalV()

double? v = BatteryChemistry.NMC.TypicalNominalV();

Returns the conventional nominal voltage per cell-stack for well-known chemistries, or null for Unknown and Custom. The host application can use this to pre-fill the NominalV field of a new BatteryProfile as a convenience default.


Serialization

All model types are C# records with only primitive and enum fields. They serialize and deserialize cleanly with System.Text.Json using default settings:

string json = JsonSerializer.Serialize(live);
MpptLive? restored = JsonSerializer.Deserialize<MpptLive>(json);

No custom converters, source generators, or attributes are required. This makes the types suitable for REST API payloads, SQLite JSON columns, message-queue envelopes, and flat file loggers without any additional ceremony.


See also

Clone this wiki locally