Skip to content

Getting Started

tsgb edited this page Jun 4, 2026 · 2 revisions

Getting Started

OpenMPPT is a .NET 8 library (net8.0) that provides a uniform interface for talking to solar charge controllers and battery devices. This page walks you through adding the package, reading live telemetry from a demo device, wiring up a real device over your own transport, and building the library from source.


Adding OpenMPPT to your project

NuGet package

The library is published to GitHub Packages under the NuGet id OpenMPPT. Add the feed to your NuGet.Config (or dotnet nuget add source) and then:

dotnet add package OpenMPPT

Project reference (when working from source)

If you have cloned the repository alongside your own solution, add a project reference instead:

<ProjectReference Include="..\OpenMPPT\src\OpenMppt\OpenMppt.csproj" />

Two-line hook: DemoDriver

DemoDriver produces synthetic telemetry with no hardware attached. It is the fastest way to verify your UI or data pipeline compiles and runs correctly.

using OpenMppt.Abstractions;
using OpenMppt.Drivers;

await using IDeviceDriver driver = new DemoDriver();

driver.LiveChanged += live => Console.WriteLine(
    $"{live.ChargerState.Label()} | " +
    $"Bat {live.BatteryVoltage:F2} V  {live.ChargeCurrent:F1} A  " +
    $"~{live.ApproxPvWatts:F0} W PV  SoC {live.SocEstimate:F0}%");

await driver.StartAsync("demo");

await Task.Delay(TimeSpan.FromSeconds(10));
await driver.StopAsync();

Fields available on MpptLive

Member Type Notes
TimestampMs long Unix epoch milliseconds
BatteryVoltage double Volts
ChargeCurrent double Amps into battery
DischargeCurrent double Amps out of battery (load)
TemperatureC double Controller temperature
ChargerState ChargerState Bulk, Boost, Float, Idle, LoadOff, Fault, Unknown
SocEstimate double Percent, 0–100; use MpptSettings.ComputeSoc for a calibrated value
BatteryWatts double Computed: BatteryVoltage × (ChargeCurrent − DischargeCurrent), signed
LoadWatts double Computed: BatteryVoltage × DischargeCurrent
ApproxPvWatts double Computed: BatteryVoltage × ChargeCurrent
TotalAccumulatedAh double Lifetime Ah counter from device
SolarStatusRaw / WorkStatusRaw / PowerStatusRaw int Raw register values; decoded into ChargerState via ChargerStateLogic.FromRegisters

ChargerState.Label() returns a short display string ("Bulk", "Float", etc.).

MpptLive.EstimateSoc(double v) is a static 12 V lead-acid fallback table. For lithium or any pack whose charge/cutoff setpoints are known, prefer MpptSettings.ComputeSoc(double v) once settings have been read.


Connecting a real device

OpenMPPT ships real drivers through a The Vendor Registry — the generic Chinese Modbus MPPT controller, Victron Instant Readout, and Renogy/SRNE + EPEver. You implement one small transport shim for your link layer; the registry builds the driver.

1. Implement IFrameTransport for your link layer

IFrameTransport is a simple connection-oriented byte pipe. The library never touches BLE GATT, serial RS-485, or TCP directly — that is always the host app's responsibility. See Transports for a full walkthrough; the contract is:

using OpenMppt.Abstractions;

public sealed class MyBleTransport : IFrameTransport
{
    public TransportState State { get; private set; } = TransportState.Disconnected;
    public event Action<TransportState>? StateChanged;
    public event Action<ReadOnlyMemory<byte>>? BytesReceived;

    public async Task<bool> ConnectAsync(string address, CancellationToken ct = default)
    {
        // open your BLE/serial/TCP connection to `address`
        // raise StateChanged(TransportState.Connected) on success
        // return false on failure (do not throw)
        State = TransportState.Connected;
        StateChanged?.Invoke(State);
        return true;
    }

    public Task<bool> WriteAsync(ReadOnlyMemory<byte> bytes, CancellationToken ct = default)
    {
        // forward bytes to the physical link; return false on failure
        return Task.FromResult(true);
    }

    public Task DisconnectAsync()
    {
        State = TransportState.Disconnected;
        StateChanged?.Invoke(State);
        return Task.CompletedTask;
    }

    public ValueTask DisposeAsync()
    {
        // clean up underlying connection
        return ValueTask.CompletedTask;
    }
}

Incoming raw bytes are surfaced by raising BytesReceived. The library-side ModbusReassembler handles frame re-assembly and CRC validation; you do not need to parse frames yourself.

2. Construct the driver, subscribe to events, call StartAsync

using OpenMppt.Devices;

var registry = DeviceRegistry.CreateDefault();
await using IDeviceDriver driver = registry.Create(
    "generic.modbus-mppt", new DriverContext { FrameTransport = new MyBleTransport() });

driver.StateChanged    += state    => Console.WriteLine($"State: {state}");
driver.LiveChanged     += live     => Console.WriteLine($"Bat {live.BatteryVoltage:F2} V  {live.ChargerState.Label()}");
driver.SettingsChanged += settings => Console.WriteLine($"Charge setpoint: {settings.ChargeVoltageSetpoint:F2} V");

await driver.StartAsync("AA:BB:CC:DD:EE:FF");   // address format is transport-specific

A passive Victron device is the same shape with an IAdvertisementSource and the per-device key:

await using IDeviceDriver victron = registry.Create("victron.instant-readout",
    new DriverContext { AdvertisementSource = new MyVictronWatcher(), Key = instantReadoutKey });
await victron.StartAsync("CA:FE:...");

See The Vendor Registry for every vendor id and how to register your own.

3. Read (and optionally write) settings

bool ok = await driver.ReadSettingsAsync();
if (ok && driver.Settings is { } s)
{
    Console.WriteLine($"Battery type: {s.BatteryTypeEnum}  Cutoff: {s.CutoffVoltageSetpoint:F1} V");
    double? soc = s.ComputeSoc(driver.Live?.BatteryVoltage ?? 0);
    Console.WriteLine($"Calibrated SoC: {soc:F0}%");
}

ReadSettingsAsync returns false when the driver does not support it. WriteRegisterAsync(address, value) follows the same pattern — check the return value before assuming the write succeeded.


Querying Capabilities to adapt your UI

Every driver exposes DeviceCapabilities as a [Flags] enum so the host application can show or hide controls at runtime:

var caps = driver.Capabilities;

bool canShowLive     = caps.HasFlag(DeviceCapabilities.LiveTelemetry);
bool canReadSettings = caps.HasFlag(DeviceCapabilities.ReadSettings);
bool canWrite        = caps.HasFlag(DeviceCapabilities.WriteSettings);

Console.WriteLine($"Live: {canShowLive}  Read: {canReadSettings}  Write: {canWrite}");
// DemoDriver → Live: True  Read: True  Write: False

DemoDriver advertises LiveTelemetry | ReadSettings; the generic Modbus MPPT driver additionally advertises WriteSettings.


Building and testing from source

The solution file is OpenMppt.slnx. No hardware is required; all 83 tests run headlessly.

# build
dotnet build OpenMppt.slnx

# run all tests
dotnet test OpenMppt.slnx

The test project targets net10.0; the library itself targets net8.0. Both should build cleanly on the .NET 10 SDK.

To pack a local NuGet artefact:

dotnet pack OpenMppt.slnx --configuration Release

See also

Clone this wiki locally