[fronius] Add support for Fronius Smart Meter (#9209)

* [fronius] Add support for Fronius Smart Meter
* [fronius] Use DTO, implement UoM
* [fronius] fix README.md
* [fronius] Add mandatory label for channel-type and fix property update
* [fronius] Store meterRealtimeBodyData instead of meterRealtimeResponse

Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
This commit is contained in:
jimtng 2020-12-29 03:24:10 +10:00 committed by GitHub
parent c556f49691
commit fe5e9b85e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 974 additions and 90 deletions

View File

@ -1,12 +1,16 @@
# Fronius Binding
This binding uses the [Fronius Solar API V1](https://www.fronius.com/en/photovoltaics/products/all-products/system-monitoring/open-interfaces/fronius-solar-api-json-) to obtain data from a Fronius devices.
This binding uses the [Fronius Solar API V1](https://www.fronius.com/en/photovoltaics/products/all-products/system-monitoring/open-interfaces/fronius-solar-api-json-) to obtain data from Fronius devices.
It supports Fronius inverters and Fronius Smart Meter. Tested with a Fronius Symo 8.2-3-M and Fronius Smart Meter 63A.
## Supported Things
Support Fronius Galvo, Fronius Symo inverters and other Fronius inverters in combination with the Fronius Datamanager 1.0 / 2.0 or Fronius Datalogger.
You can add multiple inverters that depend on the same datalogger with different device ids. (Default 1)
| Thing Type | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bridge` | The Bridge |
| `powerinverter` | Fronius Galvo, Symo and other Fronius inverters in combination with the Fronius Datamanager 1.0 / 2.0 or Fronius Datalogger. You can add multiple inverters that depend on the same datalogger with different device ids. (Default 1) |
| `meter` | Fronius Smart Meter. You can add multiple smart meters with different device ids. (The default id = 0) |
## Discovery
@ -14,41 +18,83 @@ There is no discovery implemented. You have to create your things manually and s
## Binding Configuration
The binding has no configuration options, all configuration is done at `bridge` or `powerinverter` level.
The binding has no configuration options, all configuration is done at `bridge`, `powerinverter` or `meter` level.
## Thing Configuration
### Bridge Thing Configuration
| Parameter | Description |
|-----------------|------------------------------------------------------ |
| hostname | The hostname or IP address of your Fronius Datalogger |
| refreshInterval | Refresh interval in seconds |
| Parameter | Description |
| ----------------- | ----------------------------------------------------- |
| `hostname` | The hostname or IP address of your Fronius Datalogger |
| `refreshInterval` | Refresh interval in seconds |
### Powerinverter Thing Configuration
| Parameter | Description |
|-----------------|------------------------------------------------------ |
| deviceId | The identifier of your device (Default: 1) |
| Parameter | Description |
| ---------- | ------------------------------------------ |
| `deviceId` | The identifier of your device (Default: 1) |
### Meter Thing Configuration
| Parameter | Description |
| ---------- | ----------------------------------------------- |
| `deviceId` | The identifier of your smart meter (Default: 0) |
## Channels
| Channel ID | Item Type | Description |
|------------|--------------|------------------------- |
| inverterdatachanneldayenergy | Number | Energy generated on current day |
| inverterdatachannelpac | Number | AC powery |
| inverterdatachanneltotal | Number | Energy generated overall |
| inverterdatachannelyear | Number | Energy generated in current year |
| inverterdatachannelfac | Number | AC frequency |
| inverterdatachanneliac | Number | AC current |
| inverterdatachannelidc | Number | DC current |
| inverterdatachanneluac | Number | AC voltage |
| inverterdatachanneludc | Number | DC voltage |
| inverterdatadevicestatuserrorcode | Number | Device error code |
| inverterdatadevicestatusstatuscode | Number | Device status code<br />`0` - `6` Startup<br />`7` Running <br />`8` Standby<br />`9` Bootloading<br />`10` Error |
| powerflowchannelpgrid | Number | Power + from grid, - to grid |
| powerflowchannelpload | Number | Power + generator, - consumer |
| powerflowchannelpakku | Number | Power + charge, - discharge |
### Channels for `powerinverter` Thing
| Channel ID | Item Type | Description |
| ------------------------------------ | --------- | ----------------------------------------------------------------------------------------------------------------- |
| `inverterdatachanneldayenergy` | Number | Energy generated on current day |
| `inverterdatachannelpac` | Number | AC power |
| `inverterdatachanneltotal` | Number | Energy generated overall |
| `inverterdatachannelyear` | Number | Energy generated in current year |
| `inverterdatachannelfac` | Number | AC frequency |
| `inverterdatachanneliac` | Number | AC current |
| `inverterdatachannelidc` | Number | DC current |
| `inverterdatachanneluac` | Number | AC voltage |
| `inverterdatachanneludc` | Number | DC voltage |
| `inverterdatadevicestatuserrorcode` | Number | Device error code |
| `inverterdatadevicestatusstatuscode` | Number | Device status code<br />`0` - `6` Startup<br />`7` Running <br />`8` Standby<br />`9` Bootloading<br />`10` Error |
| `powerflowchannelpgrid` | Number | Power + from grid, - to grid |
| `powerflowchannelpload` | Number | Power + generator, - consumer |
| `powerflowchannelpakku` | Number | Power + charge, - discharge |
### Channels for `meter` Thing
| Channel ID | Item Type | Description |
| ----------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `enable` | Number | 1 = enabled, 0 = disabled |
| `location` | Number | 0 = grid interconnection point (primary meter)<br/> 1 = load (primary meter) <br />3 = external generator (secondary meters)(multiple)<br />256-511 = subloads (secondary meters)(unique). Refer to Fronius Solar API. |
| `currentacphase1` | Number:ElectricCurrent | AC Current on Phase 1 |
| `currentacphase2` | Number:ElectricCurrent | AC Current on Phase 2 |
| `currentacphase3` | Number:ElectricCurrent | AC Current on Phase 3 |
| `voltageacphase1` | Number:ElectricPotential | AC Voltage on Phase 1 |
| `voltageacphase2` | Number:ElectricPotential | AC Voltage on Phase 2 |
| `voltageacphase3` | Number:ElectricPotential | AC Voltage on Phase 3 |
| `powerrealphase1` | Number:Power | Real Power on Phase 1 |
| `powerrealphase2` | Number:Power | Real Power on Phase 2 |
| `powerrealphase3` | Number:Power | Real Power on Phase 3 |
| `powerfactorphase1` | Number | Power Factor on Phase 1 |
| `powerfactorphase2` | Number | Power Factor on Phase 2 |
| `powerfactorphase3` | Number | Power Factor on Phase 3 |
| `energyrealsumconsumed` | Number:Energy | Real Energy consumed |
| `energyrealsumproduced` | Number:Energy | Real Energy produced |
| |
## Properties
The `meter` thing has the following properties:
| Property | Description |
| -------- | ------------------------------ |
| `model` | The model name of the meter |
| `serial` | The serial number of the meter |
## Full Example
@ -57,13 +103,14 @@ demo.things:
```
Bridge fronius:bridge:mybridge [hostname="192.168.66.148", refreshInterval=5] {
Thing powerinverter myinverter [deviceId=1]
Thing meter mymeter [deviceId=0]
}
```
demo.items:
```
Number AC_Powery { channel="fronius:powerinverter:mybridge:myinverter:inverterdatachannelpac" }
Number AC_Power { channel="fronius:powerinverter:mybridge:myinverter:inverterdatachannelpac" }
Number Day_Energy { channel="fronius:powerinverter:mybridge:myinverter:inverterdatachanneldayenergy" }
Number Total_Energy { channel="fronius:powerinverter:mybridge:myinverter:inverterdatachanneltotal" }
Number Year_Energy { channel="fronius:powerinverter:mybridge:myinverter:inverterdatachannelyear" }
@ -77,6 +124,21 @@ Number StatusCode { channel="fronius:powerinverter:mybridge:myinverter:inverterd
Number Grid_Power { channel="fronius:powerinverter:mybridge:myinverter:powerflowchannelpgrid" }
Number Load_Power { channel="fronius:powerinverter:mybridge:myinverter:powerflowchannelpload" }
Number Battery_Power { channel="fronius:powerinverter:mybridge:myinverter:powerflowchannelpakku" }
```
Tested with a Fronius Symo 8.2-3-M
Number Meter_Enable { channel="fronius:meter:mybridge:mymeter:enable" }
Number Meter_Location { channel="fronius:meter:mybridge:mymeter:location" }
Number:ElectricCurrent Meter_CurrentPhase1 { channel="fronius:meter:mybridge:mymeter:currentacphase1" }
Number:ElectricCurrent Meter_CurrentPhase2 { channel="fronius:meter:mybridge:mymeter:currentacphase2" }
Number:ElectricCurrent Meter_CurrentPhase3 { channel="fronius:meter:mybridge:mymeter:currentacphase3" }
Number:Voltage Meter_VoltagePhase1 { channel="fronius:meter:mybridge:mymeter:voltageacphase1" }
Number:Voltage Meter_VoltagePhase2 { channel="fronius:meter:mybridge:mymeter:voltageacphase2" }
Number:Voltage Meter_VoltagePhase3 { channel="fronius:meter:mybridge:mymeter:voltageacphase3" }
Number:Power Meter_PowerPhase1 { channel="fronius:meter:mybridge:mymeter:powerrealphase1" }
Number:Power Meter_PowerPhase2 { channel="fronius:meter:mybridge:mymeter:powerrealphase2" }
Number:Power Meter_PowerPhase3 { channel="fronius:meter:mybridge:mymeter:powerrealphase3" }
Number Meter_PowerFactorPhase1 { channel="fronius:meter:mybridge:mymeter:powerfactorphase1" }
Number Meter_PowerFactorPhase2 { channel="fronius:meter:mybridge:mymeter:powerfactorphase2" }
Number Meter_PowerFactorPhase3 { channel="fronius:meter:mybridge:mymeter:powerfactorphase3" }
Number:Energy Meter_EnergyConsumed { channel="fronius:meter:mybridge:mymeter:energyrealsumconsumed" }
Number:Energy Meter_EnergyProduced { channel="fronius:meter:mybridge:mymeter:energyrealsumproduced" }
```

View File

@ -30,6 +30,7 @@ public class FroniusBindingConstants {
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_INVERTER = new ThingTypeUID(BINDING_ID, "powerinverter");
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
public static final ThingTypeUID THING_TYPE_METER = new ThingTypeUID(BINDING_ID, "meter");
// List of all Channel ids
public static final String InverterDataChannelDayEnergy = "inverterdatachanneldayenergy";
@ -46,8 +47,27 @@ public class FroniusBindingConstants {
public static final String PowerFlowpGrid = "powerflowchannelpgrid";
public static final String PowerFlowpLoad = "powerflowchannelpload";
public static final String PowerFlowpAkku = "powerflowchannelpakku";
public static final String MeterModel = "model";
public static final String MeterSerial = "serial";
public static final String MeterEnable = "enable";
public static final String MeterLocation = "location";
public static final String MeterCurrentAcPhase1 = "currentacphase1";
public static final String MeterCurrentAcPhase2 = "currentacphase2";
public static final String MeterCurrentAcPhase3 = "currentacphase3";
public static final String MeterVoltageAcPhase1 = "voltageacphase1";
public static final String MeterVoltageAcPhase2 = "voltageacphase2";
public static final String MeterVoltageAcPhase3 = "voltageacphase3";
public static final String MeterPowerPhase1 = "powerrealphase1";
public static final String MeterPowerPhase2 = "powerrealphase2";
public static final String MeterPowerPhase3 = "powerrealphase3";
public static final String MeterPowerFactorPhase1 = "powerfactorphase1";
public static final String MeterPowerFactorPhase2 = "powerfactorphase2";
public static final String MeterPowerFactorPhase3 = "powerfactorphase3";
public static final String MeterEnergyRealSumConsumed = "energyrealsumconsumed";
public static final String MeterEnergyRealSumProduced = "energyrealsumproduced";
// List of all Urls
public static final String INVERTER_REALTIME_DATA_URL = "http://%IP%/solar_api/v1/GetInverterRealtimeData.cgi?Scope=Device&DeviceId=%DEVICEID%&DataCollection=CommonInverterData";
public static final String POWERFLOW_REALTIME_DATA = "http://%IP%/solar_api/v1/GetPowerFlowRealtimeData.fcgi";
public static final String METER_REALTIME_DATA_URL = "http://%IP%/solar_api/v1/GetMeterRealtimeData.cgi?Scope=Device&DeviceId=%DEVICEID%&DataCollection=MeterRealtimeData";
}

View File

@ -18,6 +18,7 @@ import java.util.HashSet;
import java.util.Set;
import org.openhab.binding.fronius.internal.handler.FroniusBridgeHandler;
import org.openhab.binding.fronius.internal.handler.FroniusMeterHandler;
import org.openhab.binding.fronius.internal.handler.FroniusSymoInverterHandler;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
@ -42,6 +43,7 @@ public class FroniusHandlerFactory extends BaseThingHandlerFactory {
{
add(THING_TYPE_INVERTER);
add(THING_TYPE_BRIDGE);
add(THING_TYPE_METER);
}
};
@ -58,6 +60,8 @@ public class FroniusHandlerFactory extends BaseThingHandlerFactory {
return new FroniusSymoInverterHandler(thing);
} else if (thingTypeUID.equals(THING_TYPE_BRIDGE)) {
return new FroniusBridgeHandler((Bridge) thing);
} else if (thingTypeUID.equals(THING_TYPE_METER)) {
return new FroniusMeterHandler(thing);
}
return null;
}

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2020 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;
import com.google.gson.annotations.SerializedName;
/**
* The {@link MeterRealtimeBody} is responsible for storing
* the "body" node of the JSON response
*
* @author Jimmy Tanagra - Initial contribution
*/
public class MeterRealtimeBodyDTO {
@SerializedName("Data")
private MeterRealtimeBodyDataDTO data;
public MeterRealtimeBodyDataDTO getData() {
if (data == null) {
data = new MeterRealtimeBodyDataDTO();
}
return data;
}
public void setData(MeterRealtimeBodyDataDTO data) {
this.data = data;
}
}

View File

@ -0,0 +1,397 @@
/**
* Copyright (c) 2010-2020 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;
import com.google.gson.annotations.SerializedName;
/**
* The {@link MeterRealtimeBodyData} is responsible for storing
* the "data" node of the JSON response
*
* @author Jimmy Tanagra - Initial contribution
*/
public class MeterRealtimeBodyDataDTO {
@SerializedName("Current_AC_Phase_1")
private double currentACPhase1;
@SerializedName("Current_AC_Phase_2")
private double currentACPhase2;
@SerializedName("Current_AC_Phase_3")
private double currentACPhase3;
@SerializedName("Details")
private MeterRealtimeDetailsDTO details;
@SerializedName("Enable")
private int enable;
@SerializedName("EnergyReactive_VArAC_Sum_Consumed")
private int energyReactiveVArACSumConsumed;
@SerializedName("EnergyReactive_VArAC_Sum_Produced")
private int energyReactiveVArACSumProduced;
@SerializedName("EnergyReal_WAC_Minus_Absolute")
private int energyRealWACMinusAbsolute;
@SerializedName("EnergyReal_WAC_Plus_Absolute")
private int energyRealWACPlusAbsolute;
@SerializedName("EnergyReal_WAC_Sum_Consumed")
private int energyRealWACSumConsumed;
@SerializedName("EnergyReal_WAC_Sum_Produced")
private int energyRealWACSumProduced;
@SerializedName("Frequency_Phase_Average")
private double frequencyPhaseAverage;
@SerializedName("Meter_Location_Current")
private int meterLocationCurrent;
@SerializedName("PowerApparent_S_Phase_1")
private double powerApparentSPhase1;
@SerializedName("PowerApparent_S_Phase_2")
private double powerApparentSPhase2;
@SerializedName("PowerApparent_S_Phase_3")
private double powerApparentSPhase3;
@SerializedName("PowerApparent_S_Sum")
private int powerApparentSSum;
@SerializedName("PowerFactor_Phase_1")
private double powerFactorPhase1;
@SerializedName("PowerFactor_Phase_2")
private double powerFactorPhase2;
@SerializedName("PowerFactor_Phase_3")
private double powerFactorPhase3;
@SerializedName("PowerFactor_Sum")
private double powerFactorSum;
@SerializedName("PowerReactive_Q_Phase_1")
private double powerReactiveQPhase1;
@SerializedName("PowerReactive_Q_Phase_2")
private double powerReactiveQPhase2;
@SerializedName("PowerReactive_Q_Phase_3")
private double powerReactiveQPhase3;
@SerializedName("PowerReactive_Q_Sum")
private double powerReactiveQSum;
@SerializedName("PowerReal_P_Phase_1")
private double powerRealPPhase1;
@SerializedName("PowerReal_P_Phase_2")
private double powerRealPPhase2;
@SerializedName("PowerReal_P_Phase_3")
private double powerRealPPhase3;
@SerializedName("PowerReal_P_Sum")
private double powerRealPSum;
@SerializedName("TimeStamp")
private int timeStamp;
@SerializedName("Visible")
private int visible;
@SerializedName("Voltage_AC_PhaseToPhase_12")
private double voltageACPhaseToPhase12;
@SerializedName("Voltage_AC_PhaseToPhase_23")
private double voltageACPhaseToPhase23;
@SerializedName("Voltage_AC_PhaseToPhase_31")
private double voltageACPhaseToPhase31;
@SerializedName("Voltage_AC_Phase_1")
private double voltageACPhase1;
@SerializedName("Voltage_AC_Phase_2")
private double voltageACPhase2;
@SerializedName("Voltage_AC_Phase_3")
private double voltageACPhase3;
public double getCurrentACPhase1() {
return currentACPhase1;
}
public void setCurrentACPhase1(double currentACPhase1) {
this.currentACPhase1 = currentACPhase1;
}
public double getCurrentACPhase2() {
return currentACPhase2;
}
public void setCurrentACPhase2(double currentACPhase2) {
this.currentACPhase2 = currentACPhase2;
}
public double getCurrentACPhase3() {
return currentACPhase3;
}
public void setCurrentACPhase3(double currentACPhase3) {
this.currentACPhase3 = currentACPhase3;
}
public MeterRealtimeDetailsDTO getDetails() {
if (details == null) {
details = new MeterRealtimeDetailsDTO();
}
return details;
}
public void setDetails(MeterRealtimeDetailsDTO details) {
this.details = details;
}
public int getEnable() {
return enable;
}
public void setEnable(int enable) {
this.enable = enable;
}
public int getEnergyReactiveVArACSumConsumed() {
return energyReactiveVArACSumConsumed;
}
public void setEnergyReactiveVArACSumConsumed(int energyReactiveVArACSumConsumed) {
this.energyReactiveVArACSumConsumed = energyReactiveVArACSumConsumed;
}
public int getEnergyReactiveVArACSumProduced() {
return energyReactiveVArACSumProduced;
}
public void setEnergyReactiveVArACSumProduced(int energyReactiveVArACSumProduced) {
this.energyReactiveVArACSumProduced = energyReactiveVArACSumProduced;
}
public int getEnergyRealWACMinusAbsolute() {
return energyRealWACMinusAbsolute;
}
public void setEnergyRealWACMinusAbsolute(int energyRealWACMinusAbsolute) {
this.energyRealWACMinusAbsolute = energyRealWACMinusAbsolute;
}
public int getEnergyRealWACPlusAbsolute() {
return energyRealWACPlusAbsolute;
}
public void setEnergyRealWACPlusAbsolute(int energyRealWACPlusAbsolute) {
this.energyRealWACPlusAbsolute = energyRealWACPlusAbsolute;
}
public int getEnergyRealWACSumConsumed() {
return energyRealWACSumConsumed;
}
public void setEnergyRealWACSumConsumed(int energyRealWACSumConsumed) {
this.energyRealWACSumConsumed = energyRealWACSumConsumed;
}
public int getEnergyRealWACSumProduced() {
return energyRealWACSumProduced;
}
public void setEnergyRealWACSumProduced(int energyRealWACSumProduced) {
this.energyRealWACSumProduced = energyRealWACSumProduced;
}
public double getFrequencyPhaseAverage() {
return frequencyPhaseAverage;
}
public void setFrequencyPhaseAverage(double frequencyPhaseAverage) {
this.frequencyPhaseAverage = frequencyPhaseAverage;
}
public int getMeterLocationCurrent() {
return meterLocationCurrent;
}
public void setMeterLocationCurrent(int meterLocationCurrent) {
this.meterLocationCurrent = meterLocationCurrent;
}
public double getPowerApparentSPhase1() {
return powerApparentSPhase1;
}
public void setPowerApparentSPhase1(double powerApparentSPhase1) {
this.powerApparentSPhase1 = powerApparentSPhase1;
}
public double getPowerApparentSPhase2() {
return powerApparentSPhase2;
}
public void setPowerApparentSPhase2(double powerApparentSPhase2) {
this.powerApparentSPhase2 = powerApparentSPhase2;
}
public double getPowerApparentSPhase3() {
return powerApparentSPhase3;
}
public void setPowerApparentSPhase3(double powerApparentSPhase3) {
this.powerApparentSPhase3 = powerApparentSPhase3;
}
public int getPowerApparentSSum() {
return powerApparentSSum;
}
public void setPowerApparentSSum(int powerApparentSSum) {
this.powerApparentSSum = powerApparentSSum;
}
public double getPowerFactorPhase1() {
return powerFactorPhase1;
}
public void setPowerFactorPhase1(double powerFactorPhase1) {
this.powerFactorPhase1 = powerFactorPhase1;
}
public double getPowerFactorPhase2() {
return powerFactorPhase2;
}
public void setPowerFactorPhase2(double powerFactorPhase2) {
this.powerFactorPhase2 = powerFactorPhase2;
}
public double getPowerFactorPhase3() {
return powerFactorPhase3;
}
public void setPowerFactorPhase3(double powerFactorPhase3) {
this.powerFactorPhase3 = powerFactorPhase3;
}
public double getPowerFactorSum() {
return powerFactorSum;
}
public void setPowerFactorSum(double powerFactorSum) {
this.powerFactorSum = powerFactorSum;
}
public double getPowerReactiveQPhase1() {
return powerReactiveQPhase1;
}
public void setPowerReactiveQPhase1(double powerReactiveQPhase1) {
this.powerReactiveQPhase1 = powerReactiveQPhase1;
}
public double getPowerReactiveQPhase2() {
return powerReactiveQPhase2;
}
public void setPowerReactiveQPhase2(double powerReactiveQPhase2) {
this.powerReactiveQPhase2 = powerReactiveQPhase2;
}
public double getPowerReactiveQPhase3() {
return powerReactiveQPhase3;
}
public void setPowerReactiveQPhase3(double powerReactiveQPhase3) {
this.powerReactiveQPhase3 = powerReactiveQPhase3;
}
public double getPowerReactiveQSum() {
return powerReactiveQSum;
}
public void setPowerReactiveQSum(double powerReactiveQSum) {
this.powerReactiveQSum = powerReactiveQSum;
}
public double getPowerRealPPhase1() {
return powerRealPPhase1;
}
public void setPowerRealPPhase1(double powerRealPPhase1) {
this.powerRealPPhase1 = powerRealPPhase1;
}
public double getPowerRealPPhase2() {
return powerRealPPhase2;
}
public void setPowerRealPPhase2(double powerRealPPhase2) {
this.powerRealPPhase2 = powerRealPPhase2;
}
public double getPowerRealPPhase3() {
return powerRealPPhase3;
}
public void setPowerRealPPhase3(double powerRealPPhase3) {
this.powerRealPPhase3 = powerRealPPhase3;
}
public double getPowerRealPSum() {
return powerRealPSum;
}
public void setPowerRealPSum(double powerRealPSum) {
this.powerRealPSum = powerRealPSum;
}
public int getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(int timeStamp) {
this.timeStamp = timeStamp;
}
public int getVisible() {
return visible;
}
public void setVisible(int visible) {
this.visible = visible;
}
public double getVoltageACPhaseToPhase12() {
return voltageACPhaseToPhase12;
}
public void setVoltageACPhaseToPhase12(double voltageACPhaseToPhase12) {
this.voltageACPhaseToPhase12 = voltageACPhaseToPhase12;
}
public double getVoltageACPhaseToPhase23() {
return voltageACPhaseToPhase23;
}
public void setVoltageACPhaseToPhase23(double voltageACPhaseToPhase23) {
this.voltageACPhaseToPhase23 = voltageACPhaseToPhase23;
}
public double getVoltageACPhaseToPhase31() {
return voltageACPhaseToPhase31;
}
public void setVoltageACPhaseToPhase31(double voltageACPhaseToPhase31) {
this.voltageACPhaseToPhase31 = voltageACPhaseToPhase31;
}
public double getVoltageACPhase1() {
return voltageACPhase1;
}
public void setVoltageACPhase1(double voltageACPhase1) {
this.voltageACPhase1 = voltageACPhase1;
}
public double getVoltageACPhase2() {
return voltageACPhase2;
}
public void setVoltageACPhase2(double voltageACPhase2) {
this.voltageACPhase2 = voltageACPhase2;
}
public double getVoltageACPhase3() {
return voltageACPhase3;
}
public void setVoltageACPhase3(double voltageACPhase3) {
this.voltageACPhase3 = voltageACPhase3;
}
}

View File

@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2020 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;
import com.google.gson.annotations.SerializedName;
/**
* The {@link MeterRealtimeDetails} is responsible for storing
* the "body" node of the JSON response
*
* @author Jimmy Tanagra - Initial contribution
*/
public class MeterRealtimeDetailsDTO {
@SerializedName("Manufacturer")
private String manufacturer;
@SerializedName("Model")
private String model;
@SerializedName("Serial")
private String serial;
public String getManufacturer() {
return manufacturer;
}
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getSerial() {
return serial;
}
public void setSerial(String serial) {
this.serial = serial;
}
}

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2020 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;
import com.google.gson.annotations.SerializedName;
/**
* The {@link MeterRealtimeResponse} is responsible for storing
* the response from the powerflowrealtime api
*
* @author Jimmy Tanagra - Initial contribution
*/
public class MeterRealtimeResponseDTO extends BaseFroniusResponse {
@SerializedName("Body")
private MeterRealtimeBodyDTO body;
public MeterRealtimeBodyDTO getBody() {
if (body == null) {
body = new MeterRealtimeBodyDTO();
}
return body;
}
public void setBody(MeterRealtimeBodyDTO body) {
this.body = body;
}
}

View File

@ -12,16 +12,22 @@
*/
package org.openhab.binding.fronius.internal.handler;
import java.io.IOException;
import java.math.BigDecimal;
import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
import org.openhab.binding.fronius.internal.api.BaseFroniusResponse;
import org.openhab.binding.fronius.internal.api.ValueUnit;
import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
@ -30,6 +36,9 @@ import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
/**
* Basic Handler class for all Fronius services.
*
@ -38,12 +47,15 @@ import org.slf4j.LoggerFactory;
*/
public abstract class FroniusBaseThingHandler extends BaseThingHandler {
private static final int API_TIMEOUT = 5000;
private final Logger logger = LoggerFactory.getLogger(FroniusBaseThingHandler.class);
private final String serviceDescription;
private FroniusBridgeHandler bridgeHandler;
private final Gson gson;
public FroniusBaseThingHandler(Thing thing) {
super(thing);
gson = new Gson();
serviceDescription = getDescription();
}
@ -115,6 +127,10 @@ public abstract class FroniusBaseThingHandler extends BaseThingHandler {
state = new DecimalType((double) value);
} else if (value instanceof ValueUnit) {
state = new DecimalType(((ValueUnit) value).getValue());
} else if (value instanceof String) {
state = new StringType((String) value);
} else if (value instanceof QuantityType) {
state = (QuantityType) value;
} else {
logger.warn("Update channel {}: Unsupported value type {}", channelId, value.getClass().getSimpleName());
}
@ -149,4 +165,53 @@ public abstract class FroniusBaseThingHandler extends BaseThingHandler {
* @param bridgeConfiguration the connected bridge configuration
*/
public abstract void refresh(FroniusBridgeConfiguration bridgeConfiguration);
/**
*
* @param type response class type
* @param url to request
* @return the object representation of the json response
*/
protected <T extends BaseFroniusResponse> T collectDataFormUrl(Class<T> type, String url) {
T result = null;
boolean resultOk = false;
String errorMsg = null;
try {
logger.debug("URL = {}", url);
String response = HttpUtil.executeUrl("GET", url, API_TIMEOUT);
if (response != null) {
logger.debug("aqiResponse = {}", response);
result = gson.fromJson(response, type);
}
if (result == null) {
errorMsg = "no data returned";
} else {
if (result.getHead().getStatus().getCode() == 0) {
resultOk = true;
} else {
errorMsg = result.getHead().getStatus().getReason();
}
}
if (!resultOk) {
logger.debug("Error in fronius response: {}", errorMsg);
}
} catch (JsonSyntaxException e) {
errorMsg = "Invalid JSON data received";
logger.debug("Error running fronius request: {}", errorMsg);
} catch (IOException | IllegalStateException e) {
errorMsg = e.getMessage();
logger.debug("Error running fronius request: {}", errorMsg);
}
// Update the thing status
if (resultOk) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, errorMsg);
}
return resultOk ? result : null;
}
}

View File

@ -0,0 +1,154 @@
/**
* Copyright (c) 2010-2020 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.handler;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.fronius.internal.FroniusBaseDeviceConfiguration;
import org.openhab.binding.fronius.internal.FroniusBindingConstants;
import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
import org.openhab.binding.fronius.internal.api.MeterRealtimeBodyDataDTO;
import org.openhab.binding.fronius.internal.api.MeterRealtimeResponseDTO;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Thing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link FroniusMeterHandler} is responsible for updating the data, which are
* sent to one of the channels.
*
* @author Jimmy Tanagra - Initial contribution
*/
public class FroniusMeterHandler extends FroniusBaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(FroniusMeterHandler.class);
private MeterRealtimeBodyDataDTO meterRealtimeBodyData;
private FroniusBaseDeviceConfiguration config;
public FroniusMeterHandler(Thing thing) {
super(thing);
}
@Override
protected String getDescription() {
return "Fronius Smart Meter";
}
@Override
public void refresh(FroniusBridgeConfiguration bridgeConfiguration) {
updateData(bridgeConfiguration, config);
updateChannels();
updateProperties();
}
@Override
public void initialize() {
config = getConfigAs(FroniusBaseDeviceConfiguration.class);
super.initialize();
}
/**
* Update the channel from the last data retrieved
*
* @param channelId the id identifying the channel to be updated
* @return the last retrieved data
*/
@Override
protected Object getValue(String channelId) {
if (meterRealtimeBodyData == null) {
return null;
}
String[] fields = StringUtils.split(channelId, "#");
String fieldName = fields[0];
switch (fieldName) {
case FroniusBindingConstants.MeterEnable:
return meterRealtimeBodyData.getEnable();
case FroniusBindingConstants.MeterLocation:
return meterRealtimeBodyData.getMeterLocationCurrent();
case FroniusBindingConstants.MeterCurrentAcPhase1:
return new QuantityType(meterRealtimeBodyData.getCurrentACPhase1(), Units.AMPERE);
case FroniusBindingConstants.MeterCurrentAcPhase2:
return new QuantityType(meterRealtimeBodyData.getCurrentACPhase2(), Units.AMPERE);
case FroniusBindingConstants.MeterCurrentAcPhase3:
return new QuantityType(meterRealtimeBodyData.getCurrentACPhase3(), Units.AMPERE);
case FroniusBindingConstants.MeterVoltageAcPhase1:
return new QuantityType(meterRealtimeBodyData.getVoltageACPhase1(), Units.VOLT);
case FroniusBindingConstants.MeterVoltageAcPhase2:
return new QuantityType(meterRealtimeBodyData.getVoltageACPhase2(), Units.VOLT);
case FroniusBindingConstants.MeterVoltageAcPhase3:
return new QuantityType(meterRealtimeBodyData.getVoltageACPhase3(), Units.VOLT);
case FroniusBindingConstants.MeterPowerPhase1:
return new QuantityType(meterRealtimeBodyData.getPowerRealPPhase1(), Units.WATT);
case FroniusBindingConstants.MeterPowerPhase2:
return new QuantityType(meterRealtimeBodyData.getPowerRealPPhase2(), Units.WATT);
case FroniusBindingConstants.MeterPowerPhase3:
return new QuantityType(meterRealtimeBodyData.getPowerRealPPhase3(), Units.WATT);
case FroniusBindingConstants.MeterPowerFactorPhase1:
return meterRealtimeBodyData.getPowerFactorPhase1();
case FroniusBindingConstants.MeterPowerFactorPhase2:
return meterRealtimeBodyData.getPowerFactorPhase2();
case FroniusBindingConstants.MeterPowerFactorPhase3:
return meterRealtimeBodyData.getPowerFactorPhase3();
case FroniusBindingConstants.MeterEnergyRealSumConsumed:
return new QuantityType(meterRealtimeBodyData.getEnergyRealWACSumConsumed(), Units.WATT_HOUR);
case FroniusBindingConstants.MeterEnergyRealSumProduced:
return new QuantityType(meterRealtimeBodyData.getEnergyRealWACSumProduced(), Units.WATT_HOUR);
}
return null;
}
private void updateProperties() {
if (meterRealtimeBodyData == null) {
return;
}
Map<String, String> properties = editProperties();
properties.put(FroniusBindingConstants.MeterModel, meterRealtimeBodyData.getDetails().getModel());
properties.put(FroniusBindingConstants.MeterSerial, meterRealtimeBodyData.getDetails().getSerial());
updateProperties(properties);
}
/**
* Get new data
*/
private void updateData(FroniusBridgeConfiguration bridgeConfiguration, FroniusBaseDeviceConfiguration config) {
MeterRealtimeResponseDTO meterRealtimeResponse = getMeterRealtimeData(bridgeConfiguration.hostname,
config.deviceId);
if (meterRealtimeResponse == null) {
meterRealtimeBodyData = null;
} else {
meterRealtimeBodyData = meterRealtimeResponse.getBody().getData();
}
}
/**
* Make the MeterRealtimeData request
*
* @param ip address of the device
* @param deviceId of the device
* @return {MeterRealtimeResponse} the object representation of the json response
*/
private MeterRealtimeResponseDTO getMeterRealtimeData(String ip, int deviceId) {
String location = FroniusBindingConstants.METER_REALTIME_DATA_URL.replace("%IP%", StringUtils.trimToEmpty(ip));
location = location.replace("%DEVICEID%", Integer.toString(deviceId));
return collectDataFormUrl(MeterRealtimeResponseDTO.class, location);
}
}

View File

@ -12,26 +12,17 @@
*/
package org.openhab.binding.fronius.internal.handler;
import java.io.IOException;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.fronius.internal.FroniusBaseDeviceConfiguration;
import org.openhab.binding.fronius.internal.FroniusBindingConstants;
import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
import org.openhab.binding.fronius.internal.api.BaseFroniusResponse;
import org.openhab.binding.fronius.internal.api.InverterRealtimeResponse;
import org.openhab.binding.fronius.internal.api.PowerFlowRealtimeResponse;
import org.openhab.binding.fronius.internal.api.ValueUnit;
import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
/**
* The {@link FroniusSymoInverterHandler} is responsible for updating the data, which are
* sent to one of the channels.
@ -41,16 +32,13 @@ import com.google.gson.JsonSyntaxException;
*/
public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
private static final int API_TIMEOUT = 5000;
private final Logger logger = LoggerFactory.getLogger(FroniusSymoInverterHandler.class);
private InverterRealtimeResponse inverterRealtimeResponse;
private PowerFlowRealtimeResponse powerFlowResponse;
private FroniusBaseDeviceConfiguration config;
private final Gson gson;
public FroniusSymoInverterHandler(Thing thing) {
super(thing);
gson = new Gson();
}
@Override
@ -149,54 +137,6 @@ public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
powerFlowResponse = getPowerFlowRealtime(bridgeConfiguration.hostname);
}
/**
*
* @param type response class type
* @param url to request
* @return the object representation of the json response
*/
private <T extends BaseFroniusResponse> T collectDataFormUrl(Class<T> type, String url) {
T result = null;
boolean resultOk = false;
String errorMsg = null;
try {
logger.debug("URL = {}", url);
String response = HttpUtil.executeUrl("GET", url, API_TIMEOUT);
if (response != null) {
logger.debug("aqiResponse = {}", response);
result = gson.fromJson(response, type);
}
if (result == null) {
errorMsg = "no data returned";
} else {
if (result.getHead().getStatus().getCode() == 0) {
resultOk = true;
} else {
errorMsg = result.getHead().getStatus().getReason();
}
}
if (!resultOk) {
logger.debug("Error in fronius response: {}", errorMsg);
}
} catch (JsonSyntaxException e) {
errorMsg = "Configuration is incorrect";
logger.debug("Error running fronius request: {}", errorMsg);
} catch (IOException | IllegalStateException e) {
logger.debug("Error running fronius request: {}", e.getMessage());
}
// Update the thing status
if (resultOk) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, errorMsg);
}
return resultOk ? result : null;
}
/**
* Make the PowerFlowRealtimeDataRequest
*

View File

@ -38,6 +38,75 @@
</config-description>
</thing-type>
<!-- Meter Thing Type -->
<thing-type id="meter">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Fronius Smart Meter</label>
<description>Fronius Smart Meter</description>
<channels>
<channel id="enable" typeId="meter_enable"/>
<channel id="location" typeId="meter_location"/>
<channel id="currentacphase1" typeId="meter_ac_current">
<label>AC Current Phase 1</label>
</channel>
<channel id="currentacphase2" typeId="meter_ac_current">
<label>AC Current Phase 2</label>
</channel>
<channel id="currentacphase3" typeId="meter_ac_current">
<label>AC Current Phase 3</label>
</channel>
<channel id="voltageacphase1" typeId="meter_ac_voltage">
<label>AC Voltage Phase 1</label>
</channel>
<channel id="voltageacphase2" typeId="meter_ac_voltage">
<label>AC Voltage Phase 2</label>
</channel>
<channel id="voltageacphase3" typeId="meter_ac_voltage">
<label>AC Voltage Phase 3</label>
</channel>
<channel id="powerrealphase1" typeId="meter_powerreal">
<label>Real Power Phase 1</label>
</channel>
<channel id="powerrealphase2" typeId="meter_powerreal">
<label>Real Power Phase 2</label>
</channel>
<channel id="powerrealphase3" typeId="meter_powerreal">
<label>Real Power Phase 3</label>
</channel>
<channel id="powerfactorphase1" typeId="meter_powerfactor">
<label>Power Factor Phase 1</label>
</channel>
<channel id="powerfactorphase2" typeId="meter_powerfactor">
<label>Power Factor Phase 2</label>
</channel>
<channel id="powerfactorphase3" typeId="meter_powerfactor">
<label>Power Factor Phase 3</label>
</channel>
<channel id="energyrealsumconsumed" typeId="meter_energy">
<label>Real Energy Consumed</label>
</channel>
<channel id="energyrealsumproduced" typeId="meter_energy">
<label>Real Energy Produced</label>
</channel>
</channels>
<properties>
<property name="model"/>
<property name="serial"/>
</properties>
<config-description>
<parameter name="deviceId" type="integer">
<label>Device ID</label>
<description>Specific device identifier</description>
<default>0</default>
</parameter>
</config-description>
</thing-type>
<channel-type id="day_energy">
<item-type>Number</item-type>
<label>Day Energy</label>
@ -127,4 +196,49 @@
<description>Battery Power ( + charge, - discharge )</description>
<state pattern="%.2f W" readOnly="true"></state>
</channel-type>
<channel-type id="meter_enable" advanced="true">
<item-type>Number</item-type>
<label>Enabled</label>
<description>Enabled</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="meter_location" advanced="true">
<item-type>Number</item-type>
<label>Location</label>
<description>Meter location code</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="meter_ac_current">
<item-type>Number:ElectricCurrent</item-type>
<label>AC Current</label>
<description></description>
<state pattern="%.2f %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="meter_ac_voltage">
<item-type>Number:ElectricPotential</item-type>
<label>AC Voltage</label>
<description></description>
<state pattern="%.2f %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="meter_powerreal">
<item-type>Number:Power</item-type>
<label>Power</label>
<description></description>
<state pattern="%.2f %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="meter_powerfactor">
<item-type>Number</item-type>
<label>Power Factor</label>
<description></description>
<state pattern="%.2f" readOnly="true"></state>
</channel-type>
<channel-type id="meter_energy">
<item-type>Number:Energy</item-type>
<label>Energy</label>
<description></description>
<state pattern="%.2f %unit%" readOnly="true"></state>
</channel-type>
</thing:thing-descriptions>