3a4oT/solar-inverter-swift
Swift library for solar inverter monitoring via Modbus/Solarman V5 protocol. Supports Deye, Solis, Sofar, and 15+ manufacturers. Uses ha-solarman YAML profiles for multi-vendor compatibility. Cross-platform (macOS, iOS, Linux).
Overview
SolarCore provides a unified API for reading solar inverter data across multiple manufacturers. It uses upstream ha-solarman YAML profiles for register mappings and returns typed Swift models.
Supported Manufacturers: Deye, Sofar, Solis, Afore, Kstar, and 13+ more (30 profiles across 18 manufacturers)
Note: Only inverters with Solarman V5 WiFi data loggers are supported. Victron and other manufacturers using proprietary protocols (VE.Direct, VE.Bus) are not compatible.
Features
- Multi-Vendor — 30 profiles across 18 manufacturers
- ha-solarman Compatible — Uses upstream YAML profiles directly
- Type-Safe — Fully typed Swift models (Codable, Sendable)
- Swift 6.2 — Typed throws, modern concurrency
- Full Parsing — All 10 parsing rules including datetime, bitmasks, sign-magnitude
Installation
Swift Package Manager
dependencies: [
.package(url: "https://github.com/3a4oT/solar-inverter-swift.git", from: "1.0.0")
]Then add to your target:
.target(
name: "YourApp",
dependencies: [
.product(name: "SolarCore", package: "solar-inverter-swift"),
]
)Quick Start
Scoped Driver (CLI / Scripts / Tests)
Auto-closes connection when scope exits. Best for one-off operations:
import SolarCore
let status = try await withSolarmanDriver(
host: "192.168.1.100",
serial: 2712345678,
profile: .deyeP3,
groups: [.battery, .pv, .grid]
)
print("SOC: \(status.battery?.soc ?? 0)%")
print("PV Power: \(status.pv?.power ?? 0)W")Example Output
Real data from a Deye three-phase hybrid inverter (SG0*LP3):
{
"battery": {
"current": 0.17, // A
"power": 9, // W (negative = discharging)
"soc": 95, // %
"temperature": 12, // °C
"voltage": 53.28 // V
},
"grid": {
"frequency": 50, // Hz
"phases": [
{ "phase": "l1", "power": 148, "voltage": 231.0 }, // W, V
{ "phase": "l2", "power": 262, "voltage": 226.8 },
{ "phase": "l3", "power": 167, "voltage": 224.8 }
],
"power": 577 // W (negative = exporting)
},
"inverter": {
"serial_number": "24XXXXXXXX",
"status": "standby"
},
"load": {
"phases": [
{ "phase": "l1", "power": 53 }, // W
{ "phase": "l2", "power": 274 },
{ "phase": "l3", "power": 188 }
],
"power": 515 // W
},
"pv": {
"power": 0, // W
"strings": [
{ "id": 1, "voltage": 11.6, "current": 0, "power": 0 }, // V, A, W
{ "id": 2, "voltage": 5.0, "current": 0, "power": 0 }
]
},
"timestamp": "2025-12-14T17:37:28Z" // UTC
}Long-Lived Client (Hummingbird / Web Frameworks)
For web services with shared client and graceful shutdown:
import Hummingbird
import SolarCore
import SolarmanV5
let client = SolarmanV5Client(
host: "192.168.1.100",
serial: 2712345678,
timeout: .seconds(60)
)
let profile = try ProfileLoader().load(.deyeP3)
let driver = SolarmanDriver(client: client)
let router = Router()
router.get("solar/status") { _, _ in
let status = try await driver.read(profile: profile, groups: [.battery, .pv, .grid])
return status // Codable
}
var app = Application(router: router)
app.addServices(client) // ServiceLifecycle integration
try await app.runService()Thread Safety: SolarmanV5Client is thread-safe and can be shared across concurrent requests. Internally it uses Mutex to serialize Modbus requests — this matches pysolarmanv5 behavior and WiFi logger hardware limitations (most loggers don't support concurrent requests). Concurrent API calls will queue and execute sequentially.
Custom Groups & JSON Serialization
Read specific sensor groups and serialize to JSON:
import SolarCore
// Read custom set of groups
let status = try await withSolarmanDriver(
host: "192.168.1.100",
serial: 2712345678,
profile: .deyeP3,
groups: [.battery, .pv, .load, .inverter] // Only what you need
)
// Access typed Swift models
if let battery = status.battery {
print("SOC: \(battery.soc)%")
print("Power: \(battery.power)W")
}
if let pv = status.pv {
print("Solar: \(pv.power)W")
print("Daily: \(pv.dailyProduction ?? 0)kWh")
}
// Serialize to JSON (snake_case, ISO8601 dates, pretty printed)
let jsonData = try SolarStatus.jsonEncoder.encode(status)
let jsonString = String(data: jsonData, encoding: .utf8)!
print(jsonString)Available groups: battery, grid, pv, load, inverter, generator, ups, bms, timeOfUse, alerts
Performance tip: Request only needed groups. Reading
.batteryalone takes ~100-200ms (1 Modbus request), while all groups take ~1.5-2s (8-10 requests). See docs/architecture.md for details.
Profiles
Profiles use upstream ha-solarman YAML format. For Deye inverters:
| Profile | Models | Type | |---------|--------|------| | deye_hybrid | SG0LP1 | Single-phase hybrid | | deye_p3 | SG0LP3, SG0HP3 | Three-phase hybrid | | deye_micro | Microinverter | Micro | | deye_string | G0 | String (grid-tie) |
See docs/development.md for profile sync instructions.
Architecture
SolarCore
├── Models/ Typed data models (Codable, Sendable)
├── Profiles/ ha-solarman YAML profiles
│ ├── Models/ InverterDefinition, SensorItem, ParsingRule
│ ├── Loader/ YAML parsing with security limits
│ └── Resources/ 30+ profiles by manufacturer
├── Sensors/ Register → value conversion
│ ├── RegisterBatcher Optimized Modbus reads (max 125 per request)
│ └── RegisterConverter Scale, offset, parsing rules
└── Drivers/ Protocol implementations
└── SolarmanDriver Solarman V5 (WiFi stick)Dependencies
| Package | Version | Purpose | |---------|---------|---------| | solarman-swift | 1.0.0+ | Solarman V5 protocol | | Yams | 6.2.0+ | YAML profile parsing |
Error Handling
All methods use typed throws for precise error handling:
do {
let status = try await withSolarmanDriver(...)
} catch let error as DriverError {
switch error {
case .connectionFailed(let message):
// Network error
case .profileError(let message):
// Profile loading failed
case .timeout:
// Read timeout
}
} catch let error as SensorError {
switch error {
case .insufficientRegisters(let expected, let got):
// Not enough data
case .rawValueOutOfRange(let value, let min, let max):
// Value filtered by range
}
}Development
# Install SwiftFormat and pre-commit hook
brew install swiftformat
./Scripts/install-hooks.sh
# Run tests
swift test --parallelDocumentation
- docs/architecture.md — Package structure, data flow, conversion pipeline
- docs/energy-calculations.md — Energy balance, cost calculations, efficiency ratios
- docs/development.md — Setup, testing, profile sync, CI
References
- ha-solarman — Upstream profiles
- pysolarmanv5 — Solarman V5 reference
- solarman-swift — Swift V5 client
- Deye Modbus Protocol V118
License
Apache License 2.0. See LICENSE for details.
Package Metadata
Repository: 3a4oT/solar-inverter-swift
Stars: 0
Forks: 0
Open issues: 0
Default branch: main
Primary language: swift
License: Apache-2.0
Topics: deye, modbus, monitoring, solar-energy, solar-monitor, solarmanv5, swift
README: README.md