[fronius] Use Solar API for getting serial number (#19262)

* [fronius] Use Solar API for getting serial
Signed-off-by: Florian Hotze <dev@florianhotze.com>
This commit is contained in:
Florian Hotze
2025-08-29 17:42:22 +02:00
committed by GitHub
parent b4d83ab930
commit 654be3d89c
6 changed files with 215 additions and 25 deletions
@@ -99,6 +99,7 @@ public class FroniusBindingConstants {
// List of all Urls
public static final String INVERTER_REALTIME_DATA_URL = "%SCHEME%://%IP%/solar_api/v1/GetInverterRealtimeData.cgi?Scope=Device&DeviceId=%DEVICEID%&DataCollection=CommonInverterData";
public static final String INVERTER_INFO_URL = "%SCHEME%://%IP%/solar_api/v1/GetInverterInfo.cgi";
public static final String POWERFLOW_REALTIME_DATA_URL = "%SCHEME%://%IP%/solar_api/v1/GetPowerFlowRealtimeData.fcgi";
public static final String METER_REALTIME_DATA_URL = "%SCHEME%://%IP%/solar_api/v1/GetMeterRealtimeData.cgi?Scope=Device&DeviceId=%DEVICEID%&DataCollection=MeterRealtimeData";
public static final String OHMPILOT_REALTIME_DATA_URL = "%SCHEME%://%IP%/solar_api/v1/GetOhmPilotRealtimeData.cgi?Scope=Device&DeviceId=%DEVICEID%";
@@ -110,6 +111,10 @@ public class FroniusBindingConstants {
return parseUrl(INVERTER_REALTIME_DATA_URL, scheme, ip, deviceId);
}
public static String getInverterInfoUrl(String scheme, String ip) {
return parseUrl(INVERTER_INFO_URL, scheme, ip);
}
public static String getPowerFlowDataUrl(String scheme, String ip) {
return parseUrl(POWERFLOW_REALTIME_DATA_URL, scheme, ip);
}
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.fronius.internal.api.dto.inverter;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
/**
* The {@link InverterInfoBody} is responsible for storing the "Body" node of the {@link InverterInfoResponse}.
*
* @author Florian Hotze - Initial contribution
*/
@NonNullByDefault
public class InverterInfoBody {
@SerializedName("Data")
private @Nullable Map<Integer, InverterInfoBodyData> data;
public @Nullable Map<Integer, InverterInfoBodyData> getData() {
return data;
}
}
@@ -0,0 +1,78 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.fronius.internal.api.dto.inverter;
import com.google.gson.annotations.SerializedName;
/**
* The {@link InverterInfoBodyData} is responsible for storing the "Data" node of the {@link InverterInfoBody}.
*
* @author Florian Hotze - Initial contribution
*/
public class InverterInfoBodyData {
@SerializedName("DT")
private Integer deviceType;
@SerializedName("PVPower")
private Integer pvPower;
@SerializedName("CustomName")
private String customName;
@SerializedName("Show")
private Integer show;
@SerializedName("UniqueID")
private Integer uniqueID;
@SerializedName("ErrorCode")
private Integer errorCode;
@SerializedName("StatusCode")
private Integer statusCode;
@SerializedName("InverterState")
private String inverterState;
public Integer getDeviceType() {
return deviceType;
}
public Integer getPvPower() {
return pvPower;
}
public String getCustomName() {
return customName;
}
public Integer getShow() {
return show;
}
public Integer getUniqueID() {
return uniqueID;
}
public Integer getErrorCode() {
return errorCode;
}
public Integer getStatusCode() {
return statusCode;
}
public String getInverterState() {
return inverterState;
}
}
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.fronius.internal.api.dto.inverter;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.fronius.internal.api.dto.BaseFroniusResponse;
import com.google.gson.annotations.SerializedName;
/**
* The {@link InverterInfoResponse} is responsible for storing the response from the GetInverterInfo request.
*
* @author Florian Hotze - Initial contribution
*/
@NonNullByDefault
public class InverterInfoResponse extends BaseFroniusResponse {
@SerializedName("Body")
private @Nullable InverterInfoBody body;
public @Nullable InverterInfoBody getBody() {
return body;
}
}
@@ -20,7 +20,7 @@ import com.google.gson.annotations.SerializedName;
/**
* The {@link InverterRealtimeResponse} is responsible for storing
* the response from the GetInverterRealtimeData response.
* the response from the GetInverterRealtimeData request.
*
* @author Thomas Rokohl - Initial contribution
*/
@@ -33,10 +33,7 @@ import org.openhab.binding.fronius.internal.api.FroniusBatteryControl;
import org.openhab.binding.fronius.internal.api.FroniusCommunicationException;
import org.openhab.binding.fronius.internal.api.FroniusHttpUtil;
import org.openhab.binding.fronius.internal.api.dto.ValueUnit;
import org.openhab.binding.fronius.internal.api.dto.inverter.InverterDeviceStatus;
import org.openhab.binding.fronius.internal.api.dto.inverter.InverterRealtimeBody;
import org.openhab.binding.fronius.internal.api.dto.inverter.InverterRealtimeBodyData;
import org.openhab.binding.fronius.internal.api.dto.inverter.InverterRealtimeResponse;
import org.openhab.binding.fronius.internal.api.dto.inverter.*;
import org.openhab.binding.fronius.internal.api.dto.powerflow.PowerFlowRealtimeBody;
import org.openhab.binding.fronius.internal.api.dto.powerflow.PowerFlowRealtimeBodyData;
import org.openhab.binding.fronius.internal.api.dto.powerflow.PowerFlowRealtimeInverter;
@@ -132,7 +129,10 @@ public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
Map<String, String> properties = editProperties();
properties.put(Thing.PROPERTY_SERIAL_NUMBER, localInverterInfo.serial());
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, localInverterInfo.firmware());
String firmware = localInverterInfo.firmware();
if (firmware != null) {
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, firmware);
}
updateProperties(properties);
}
@@ -142,7 +142,7 @@ public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
Bridge bridge = getBridge();
if (bridge != null) {
FroniusBridgeConfiguration bridgeConfig = bridge.getConfiguration().as(FroniusBridgeConfiguration.class);
inverterInfo = getInverterInfo(bridgeConfig.hostname);
inverterInfo = getInverterInfo(bridgeConfig.scheme, bridgeConfig.hostname, config.deviceId);
updateProperties();
initializeBatteryControl(bridgeConfig.hostname, bridgeConfig.username, bridgeConfig.password);
}
@@ -158,9 +158,10 @@ public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
public void handleBridgeConfigurationUpdate(Map<String, Object> configurationParameters) {
super.handleBridgeConfigurationUpdate(configurationParameters);
Bridge bridge = getBridge();
if (bridge != null) {
FroniusBaseDeviceConfiguration config = this.config;
if (bridge != null && config != null) {
FroniusBridgeConfiguration bridgeConfig = bridge.getConfiguration().as(FroniusBridgeConfiguration.class);
inverterInfo = getInverterInfo(bridgeConfig.hostname);
inverterInfo = getInverterInfo(bridgeConfig.scheme, bridgeConfig.hostname, config.deviceId);
updateProperties();
initializeBatteryControl(bridgeConfig.hostname, bridgeConfig.username, bridgeConfig.password);
}
@@ -380,6 +381,7 @@ public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
/**
* Make the InverterRealtimeDataRequest
*
* @param scheme http or https
* @param ip address of the device
* @param deviceId of the device
* @return {InverterRealtimeResponse} the object representation of the json response
@@ -390,6 +392,18 @@ public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
return collectDataFromUrl(InverterRealtimeResponse.class, location);
}
/**
* Make the InverterInfoRequest
*
* @param scheme http or https
* @param ip address of the device
* @return {InverterInfoResponse} the object representation of the json response
*/
private InverterInfoResponse getInverterInfoData(String scheme, String ip) throws FroniusCommunicationException {
String location = FroniusBindingConstants.getInverterInfoUrl(scheme, ip);
return collectDataFromUrl(InverterInfoResponse.class, location);
}
/**
* Calculate the power value from the given voltage and current channels
*
@@ -406,11 +420,38 @@ public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
/**
* Get the version information of the inverter
*
* @param hostname the IP address of the inverter
* @return InverterInfo containing serial number and firmware version, or null if not available
* @param scheme http or https
* @param ip address of the device
* @param deviceId of the device
* @return InverterInfoBody containing serial number and firmware version, or null if not available
*/
private @Nullable InverterInfo getInverterInfo(String hostname) {
final String host = "http://" + hostname;
private @Nullable InverterInfo getInverterInfo(String schema, String ip, int deviceId) {
InverterInfoResponse inverterInfoResponse = null;
try {
inverterInfoResponse = getInverterInfoData(schema, ip);
} catch (FroniusCommunicationException e) {
logger.warn("Failed to get InverterInfo from Fronius inverter at {}: {}", ip, e.getMessage());
return null;
}
if (inverterInfoResponse.getBody() == null) {
return null;
}
InverterInfoBody inverterInfoBody = inverterInfoResponse.getBody();
if (inverterInfoBody == null) {
return null;
}
Map<Integer, InverterInfoBodyData> inverterInfoBodyData = inverterInfoBody.getData();
if (inverterInfoBodyData == null) {
return null;
}
InverterInfoBodyData data = inverterInfoBodyData.get(deviceId);
if (data == null) {
return null;
}
final String serial = String.valueOf(data.getUniqueID());
final String host = "http://" + ip;
final String versionPath = "/status/version";
String url = host + "/api" + versionPath; // try the new API path first
@@ -422,34 +463,31 @@ public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
try {
response = FroniusHttpUtil.executeUrl(HttpMethod.GET, url, API_TIMEOUT);
} catch (FroniusCommunicationException ex) {
logger.warn("Failed to get version info from Fronius inverter at {}: {}", hostname, ex.getMessage());
return null;
logger.debug("Failed to get version info from Fronius inverter at {}: {}", ip, ex.getMessage());
return new InverterInfo(serial, null);
}
}
try {
JsonElement jsonElement = JsonParser.parseString(response);
if (!jsonElement.isJsonObject()) {
logger.warn("Invalid JSON response for version info from Fronius inverter at {}: {}", hostname,
response);
return null;
logger.debug("Invalid JSON response for version info from Fronius inverter at {}: {}", ip, response);
return new InverterInfo(serial, null);
}
try {
String serial = jsonElement.getAsJsonObject().get("serialNumber").getAsString();
String firmware = jsonElement.getAsJsonObject().get("swrevisions").getAsJsonObject().get("GEN24")
.getAsString();
return new InverterInfo(serial, firmware);
} catch (IllegalStateException | UnsupportedOperationException e) {
logger.warn("Failed to parse version info from Fronius inverter at {}: {}", hostname, e.getMessage());
return null;
logger.debug("Failed to parse version info from Fronius inverter at {}: {}", ip, e.getMessage());
return new InverterInfo(serial, null);
}
} catch (JsonSyntaxException e) {
// 404 errors go here, as the response is not valid JSON
logger.debug("Invalid JSON response for version info from Fronius inverter at {}: {}", hostname, response,
e);
logger.debug("Invalid JSON response for version info from Fronius inverter at {}: {}", ip, response, e);
}
return null;
return new InverterInfo(serial, null);
}
private record InverterInfo(String serial, String firmware) {
private record InverterInfo(String serial, @Nullable String firmware) {
}
}