-
Notifications
You must be signed in to change notification settings - Fork 0
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.
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 OpenMPPTIf you have cloned the repository alongside your own solution, add a project reference instead:
<ProjectReference Include="..\OpenMPPT\src\OpenMppt\OpenMppt.csproj" />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();| 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.
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.
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.
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-specificA 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.
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.
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: FalseDemoDriver advertises LiveTelemetry | ReadSettings; the generic Modbus MPPT driver additionally advertises WriteSettings.
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.slnxThe 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⚡ OpenMPPT Wiki · Repository · GPL-3.0-or-later · contributions welcome