A generic, vendor-extensible .NET library for solar charge controllers and battery devices. It owns everything between "raw bytes on a wire" and "decoded telemetry your app can render" — Modbus and Victron codecs, frame reassembly, register decode, and a clean driver interface — with no UI and no platform BLE dependency.
Build the device-talking once; consume it everywhere: desktop and mobile apps, web dashboards, chart renderers, loggers, database ingesters, headless services.
┌── your consumer (app / website / logger / service) ──┐
│ subscribes to IDeviceDriver events, renders models │
└───────────────────────┬──────────────────────────────┘
│ OpenMPPT (this library, net8.0)
┌───────────────┴───────────────┐
│ Drivers orchestrate │ Modbus · Victron · Renogy · EPEver
│ Codecs pure bytes↔model │ · Demo. Pure, fully unit-tested.
│ Model MpptLive, etc. │
│ Abstractions IDeviceDriver, │
│ IFrameTransport │
└───────────────┬───────────────┘
│ IFrameTransport / IAdvertisementSource (you implement)
┌───────────────────────┴──────────────────────────────┐
│ the radio: BLE GATT, serial / RS-485, or Modbus-TCP │
└───────────────────────────────────────────────────────┘
The library never touches a radio. A driver only sees a byte pipe, so the
same driver works over BLE, a USB serial dongle, or a TCP socket. The host
implements one tiny interface — IFrameTransport ("connect, send these bytes,
here are the bytes I got") — and OpenMPPT does all the framing, CRC, reassembly,
decode and poll/transaction logic. That keeps each host's transport shim small
and the protocol logic in one tested place.
No-hardware demo source (works today):
await using IDeviceDriver driver = new DemoDriver();
driver.LiveChanged += live =>
Console.WriteLine($"{live.BatteryVoltage:0.0} V {live.ChargeCurrent:0.0} A {live.SocEstimate:0}%");
await driver.StartAsync("demo");A real device is identical from the consumer's side — pick a vendor from the
registry, give it your IFrameTransport (or IAdvertisementSource) shim, and
the same events flow:
var registry = DeviceRegistry.CreateDefault();
await using IDeviceDriver driver = registry.Create(
"generic.modbus-mppt", new DriverContext { FrameTransport = myBleTransport });
driver.LiveChanged += vm.OnLive;
driver.SettingsChanged += vm.OnSettings;
await driver.StartAsync(mac);Generic consumers read driver.Capabilities to decide what to show — a
read-only advertisement device simply omits WriteSettings and the UI hides its
editors. No vendor-specific code.
| Folder | What |
|---|---|
Abstractions/ |
IDeviceDriver, IFrameTransport, IAdvertisementSource, ConnectionState, DeviceCapabilities, DeviceType, RawFrame |
Model/ |
MpptLive, MpptSettings, ChargerState, BatteryProfile, BatteryChemistry — serialization-friendly records |
Codecs/Modbus/ |
ModbusCrc, ModbusFrame, ModbusReassembler, MpptProtocol, MpptRegisters |
Codecs/Victron/ |
VictronDecoder (Instant Readout AES-CTR) |
Drivers/ |
ModbusMpptDriver, ModbusProfileDriver (Renogy/EPEver), VictronDriver, DemoDriver |
Devices/ |
DeviceRegistry, VendorDescriptor, DriverContext — the vendor catalogue |
dotnet build OpenMppt.slnx -c Debug
dotnet test OpenMppt.slnx -c Debug # headless, no hardware
dotnet pack src/OpenMppt -c Release -o artifacts # → OpenMPPT.<ver>.nupkgTargets net8.0 so both net8 and net10 consumers can reference it; the test
project runs on net10.
- Vendor registry —
DeviceRegistry+VendorDescriptor+ transport-aware identification; adding a vendor is one descriptor. - Drivers — generic Chinese Modbus MPPT (live + settings + write), Victron
Instant Readout (passive), Renogy/SRNE + EPEver (profile-driven), plus the
hardware-free
DemoDriver. - 83 headless tests, CI, GitHub Packages publish, aggressive Dependabot.
- More host transport examples (BLE GATT, serial/RS-485, Modbus-TCP) — transports ship with the hosts, not here (this stays platform-neutral).
- More device categories over time (battery monitors, inverters).
OpenMPPT is licensed under the GNU General Public License v3.0 or later — see LICENSE.
OpenMPPT · GPL-3.0-or-later · contributions welcome — see CONTRIBUTING