Implementing Solar Panel Management via Mobile Applications
A solar system is not just panels. It's a chain: panels → inverter → battery pack (optional) → grid connection point. Mobile applications manage not the panels directly, but the inverter and energy storage management system. Each inverter manufacturer — Huawei FusionSolar, SolarEdge, Fronius, SMA, GoodWe — has its own API or protocol. There is no unified standard.
Inverter Protocols
| Manufacturer | Local Protocol | Cloud API |
|---|---|---|
| Huawei FusionSolar | Modbus TCP + SUN2000 SDK | FusionSolar OpenAPI |
| SolarEdge | Modbus TCP | SolarEdge Monitoring API |
| Fronius | REST (Fronius Solar API v1) | Fronius Solar.web API |
| SMA | SMA Data Manager Modbus | Sunny Portal REST |
| GoodWe | Modbus TCP | GoodWe SEMS API |
| Enphase | N/A | Enlighten API v4 |
For most small systems, use the manufacturer's cloud API — simpler and doesn't require opening ports on the local network. For industrial installations and offline operation — use local Modbus TCP.
Huawei FusionSolar OpenAPI
One of the popular inverters in Europe and CIS countries. The API requires authentication via HTTPS Basic + systemCode:
// iOS
struct FusionSolarClient {
let baseURL = URL(string: "https://intl.fusionsolar.huawei.com/thirdData")!
var token: String?
mutating func login(userName: String, systemCode: String) async throws {
let body = ["userName": userName, "systemCode": systemCode]
let response: LoginResponse = try await post("/login", body: body)
token = response.data.token
}
func getStationList() async throws -> [Station] {
let response: StationListResponse = try await post(
"/getStationList",
body: ["pageNo": 1]
)
return response.data.list
}
func getRealTimeData(stationCode: String) async throws -> RealTimeData {
return try await post("/getStationRealKpi", body: ["stationCodes": stationCode])
}
}
Key metrics from getStationRealKpi: radiation_intensity (insolation), theory_power (theoretical power), inverter_power (actual generation), power_profit (generation in kWh per day), use_power (consumption).
Fronius Solar API: Local REST
Fronius inverters provide REST API directly from the inverter without cloud:
GET http://192.168.1.20/solar_api/v1/GetPowerFlowRealtimeData.fcgi
Response:
{
"Body": {
"Data": {
"Site": {
"Mode": "produce-load-grid",
"P_Grid": -1250.5,
"P_Load": -2800.0,
"P_PV": 4050.5,
"P_Akku": null,
"E_Day": 18.4,
"E_Year": 4230.0
}
}
}
}
P_Grid — negative value means feeding to the grid. P_Load — home consumption. P_PV — current generation. The difference is clear: 4050 W is generated, 2800 W is consumed, 1250 W is fed to the grid.
Fronius API is queried directly from the application only within the local network. For remote access — use a reverse proxy with authentication or Fronius Solar.web API.
Dashboard: Energy Flow Visualization
The key screen is an energy flow diagram: panels → home → grid → battery. Animated arrows show flow direction. In Flutter:
class EnergyFlowPainter extends CustomPainter {
final double pvPower; // generation
final double gridPower; // <0 to grid, >0 from grid
final double loadPower; // consumption
final double batteryPower; // <0 charging, >0 discharging
@override
void paint(Canvas canvas, Size size) {
_drawNode(canvas, pvIcon, pvPosition, '$pvPower W');
_drawNode(canvas, homeIcon, homePosition, '$loadPower W');
_drawNode(canvas, gridIcon, gridPosition, '${gridPower.abs()} W');
if (pvPower > 0) {
_drawAnimatedArrow(canvas, pvPosition, homePosition,
color: Colors.green, active: true);
}
if (gridPower < 0) {
_drawAnimatedArrow(canvas, homePosition, gridPosition,
color: Colors.orange, active: true);
}
}
}
Control: Inverter Operating Modes
SolarEdge and Huawei allow switching modes via API: Self-Consumption (maximize own usage), Time-of-Use (charge battery during off-peak hours), Export Limitation (restrict grid feed to a set value).
Command via SolarEdge API:
suspend fun setStorageCommand(siteId: String, command: StorageCommand): Result<Unit> {
return withContext(Dispatchers.IO) {
runCatching {
val response = api.setStorageCommand(
siteId = siteId,
body = StorageCommandBody(
mode = command.mode.apiValue,
chargeLimit = command.chargeLimit,
dischargeLimit = command.dischargeLimit,
)
)
if (!response.isSuccessful) {
throw ApiException(response.code(), response.message())
}
}
}
}
Changing inverter settings is an operation with consequences. UX must require explicit confirmation and show the current mode separately from the pending command.
Developing a mobile application for monitoring a single solar system via cloud API: 2-3 weeks. Supporting multiple inverters, local Modbus TCP, mode management and automation: 5-8 weeks. Cost is calculated after clarifying equipment.







