[fenecon] Provide additional data such as the temperature on the inverter (#18613)
* Update / expand the initial user examples (#18600) Signed-off-by: Philipp Schneider <philipp.schneider@nixo-soft.de>pull/18688/head
parent
9ef01e5f47
commit
4dacea107b
|
@ -31,32 +31,48 @@ The FENECON Thing only needs to be configured with the `hostname`, all other par
|
|||
|
||||
The FENECON binding currently only provides access to read out the values from the energy storage system.
|
||||
|
||||
| Channel | Type | Read/Write | Description |
|
||||
|-------------------------------|----------------------|------------|-----------------------------------------------------------------------------|
|
||||
| state | String | R | FENECON system state: Ok, Info, Warning or Fault |
|
||||
| last-update | DateTime | R | Last successful update via REST-API from the FENECON system |
|
||||
| ess-soc | Number:Dimensionless | R | Battery state of charge in percent |
|
||||
| charger-power | Number:Power | R | Current charger power of energy storage system in watt. |
|
||||
| discharger-power | Number:Power | R | Current discharger power of energy storage system in watt. |
|
||||
| emergency-power-mode | Switch | R | Indicates if there is grid power is off and the emergency power mode is on. |
|
||||
| production-active-power | Number:Power | R | Current active power producer load in watt. |
|
||||
| production-max-active-power | Number:Power | R | Maximum active production power in watt that was measured. |
|
||||
| export-to-grid-power | Number:Power | R | Current export power to grid in watt. |
|
||||
| exported-to-grid-energy | Number:Energy | R | Total energy exported to the grid in watt per hour. |
|
||||
| consumption-active-power | Number:Power | R | Current active power consumer load in watt. |
|
||||
| consumption-max-active-power | Number:Power | R | Maximum active consumption power in watt that was measured. |
|
||||
| consumption-active-power-l1 | Number:Power | R | Current active power consumer load in watt on phase 1. |
|
||||
| consumption-active-power-l2 | Number:Power | R | Current active power consumer load in watt on phase 2. |
|
||||
| consumption-active-power-l3 | Number:Power | R | Current active power consumer load in watt on phase 3. |
|
||||
| import-from-grid-power | Number:Power | R | Current import power from grid in watt. |
|
||||
| imported-from-grid-energy | Number:Energy | R | Total energy imported from the grid in watt per hour. |
|
||||
| Channel | Type | Read/Write | Description |
|
||||
|-------------------------------|----------------------------|------------|--------------------------------------------------------------------------------|
|
||||
| state | String | R | FENECON system state: Ok, Info, Warning or Fault |
|
||||
| fems-version | String | R | FENECON energy management system (FEMS) version - e.g 2025.2.3 |
|
||||
| last-update | DateTime | R | Last successful update via REST-API from the FENECON system |
|
||||
| ess-soc | Number:Dimensionless | R | Battery state of charge. |
|
||||
| batt-tower-soh | Number:Dimensionless | R | Battery state of health. |
|
||||
| charger-power | Number:Power | R | Current charger power of energy storage system. |
|
||||
| discharger-power | Number:Power | R | Current discharger power of energy storage system. |
|
||||
| emergency-power-mode | Switch | R | Indicates if there is grid power is off and the emergency power mode is on. |
|
||||
| production-active-power | Number:Power | R | Current active power producer load. |
|
||||
| production-max-active-power | Number:Power | R | Maximum active production power that was measured. |
|
||||
| export-to-grid-power | Number:Power | R | Current export power to grid. |
|
||||
| exported-to-grid-energy | Number:Energy | R | Total energy exported to the grid. |
|
||||
| consumption-active-power | Number:Power | R | Current active power consumer load. |
|
||||
| consumption-max-active-power | Number:Power | R | Maximum active consumption power that was measured. |
|
||||
| consumption-active-power-l1 | Number:Power | R | Current active power consumer load on phase 1. |
|
||||
| consumption-active-power-l2 | Number:Power | R | Current active power consumer load on phase 2. |
|
||||
| consumption-active-power-l3 | Number:Power | R | Current active power consumer load on phase 3. |
|
||||
| import-from-grid-power | Number:Power | R | Current import power from grid. |
|
||||
| imported-from-grid-energy | Number:Energy | R | Total energy imported from the grid. |
|
||||
| inverter-air-temperature | Number:Temperature | R | Air temperature at the inverter. |
|
||||
| inverter-radiator-temperature | Number:Temperature | R | Radiator temperature of the inverter. |
|
||||
| bms-pack-temperature | Number:Temperature | R | Temperature in the battery management system (BMS) box. |
|
||||
| batt-tower-voltage | Number:ElectricPotential | R | Battery voltage of the FENECON energy management system (FEMS). |
|
||||
| batt-tower-current | Number:ElectricCurrent | R | Battery current of the FENECON energy management system (FEMS). |
|
||||
| charger0-actual-power | Number:Power | R | Charger actual power on the charger 0 - e.g west roof, if available. |
|
||||
| charger0-voltage | Number:ElectricPotential | R | Charger voltage on the charger 0 - e.g west roof, if available. |
|
||||
| charger0-current | Number:ElectricCurrent | R | Charger current on the charger 0 - e.g west roof, if available. |
|
||||
| charger1-actual-power | Number:Power | R | Charger actual power on the charger 1 - e.g east roof, if available. |
|
||||
| charger1-voltage | Number:ElectricPotential | R | Charger voltage on the charger 1 - e.g east roof, if available. |
|
||||
| charger1-current | Number:ElectricCurrent | R | Charger current on the charger 1 - e.g east roof, if available. |
|
||||
| charger2-actual-power | Number:Power | R | Charger actual power on the charger 2 - e.g south roof, if available. |
|
||||
| charger2-voltage | Number:ElectricPotential | R | Charger voltage on the charger 2 - e.g south roof, if available. |
|
||||
| charger2-current | Number:ElectricCurrent | R | Charger current on the charger 2 - e.g south roof, if available. |
|
||||
|
||||
## Full Example
|
||||
|
||||
### fenecon.things
|
||||
|
||||
```java
|
||||
Thing fenecon:home-device:local "FENECON Home" [hostname="192.168.1.11", refreshInterval=5]
|
||||
Thing fenecon:home-device:local "FENECON Home" [hostname="192.168.1.11", refreshInterval=30]
|
||||
```
|
||||
|
||||
### demo.items
|
||||
|
@ -66,33 +82,52 @@ Thing fenecon:home-device:local "FENECON Home" [hostname="192.168.1.11", refresh
|
|||
Group Home "MyHome" <house> ["Indoor"]
|
||||
Group GF "GroundFloor" <groundfloor> (Home) ["GroundFloor"]
|
||||
// Utility room
|
||||
Group GF_UtilityRoom "Utility room" <energy> (Home, GF) ["Room"]
|
||||
Group GF_UtilityRoom "Utility room" <energy> (GF) ["Room"]
|
||||
Group GF_UtilityRoomSolar "Utility room solar" <solarplant> (GF_UtilityRoom) ["Inverter"]
|
||||
|
||||
// FENECON items
|
||||
String EssState <text> (GF_UtilityRoomSolar) ["Status"] {channel="fenecon:home-device:local:state"}
|
||||
DateTime LastFeneconUpdate <time> (GF_UtilityRoomSolar) ["Status"] {channel="fenecon:home-device:local:last-update"}
|
||||
Number:Dimensionless EssSoc <batterylevel> (GF_UtilityRoomSolar) ["Measurement"] {unit="%", channel="fenecon:home-device:local:ess-soc"}
|
||||
Number:Power ChargerPower <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:charger-power"}
|
||||
Number:Power DischargerPower <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:discharger-power"}
|
||||
Switch EmergencyPowerMode <switch> (GF_UtilityRoomSolar) ["Switch"] {channel="fenecon:home-device:local:emergency-power-mode"}
|
||||
String EssState <text> (GF_UtilityRoomSolar) ["Status"] {channel="fenecon:home-device:local:state"}
|
||||
String FemsVersion <text> (GF_UtilityRoomSolar) ["Status"] {channel="fenecon:home-device:local:fems-version"}
|
||||
DateTime LastFeneconUpdate <time> (GF_UtilityRoomSolar) ["Status"] {channel="fenecon:home-device:local:last-update"}
|
||||
|
||||
Number:Power ProductionActivePower <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:production-active-power"}
|
||||
Number:Power ProductionMaxActivePower <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:production-max-active-power"}
|
||||
Number:Power SellToGridPower <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:export-to-grid-power"}
|
||||
Number:Energy TotalSellEnergy <energy> (GF_UtilityRoomSolar) ["Measurement", "Energy"] {channel="fenecon:home-device:local:exported-to-grid-energy"}
|
||||
Number:Dimensionless EssSoc <batterylevel> (GF_UtilityRoomSolar) ["Measurement"] {unit="%", channel="fenecon:home-device:local:ess-soc"}
|
||||
Number:Dimensionless BattSoh <batterylevel> (GF_UtilityRoomSolar) ["Measurement"] {unit="%", channel="fenecon:home-device:local:batt-tower-soh"}
|
||||
Number:Power ChargerPower <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:charger-power"}
|
||||
Number:Power DischargerPower <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:discharger-power"}
|
||||
Switch EmergencyPowerMode <switch> (GF_UtilityRoomSolar) ["Switch"] {channel="fenecon:home-device:local:emergency-power-mode"}
|
||||
|
||||
Number:Power ConsumptionActivePower <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:consumption-active-power"}
|
||||
Number:Power ConsumptionMaxActivePower <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:consumption-max-active-power"}
|
||||
Number:Power ConsumptionActivePowerPhase1 <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:consumption-active-power-l1"}
|
||||
Number:Power ConsumptionActivePowerPhase2 <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:consumption-active-power-l2"}
|
||||
Number:Power ConsumptionActivePowerPhase3 <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:consumption-active-power-l3"}
|
||||
Number:Power BuyFromGridPower <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:import-from-grid-power"}
|
||||
Number:Energy TotalBuyEnergy <energy> (GF_UtilityRoomSolar) ["Measurement", "Energy"] {channel="fenecon:home-device:local:imported-from-grid-energy"}
|
||||
Number:Power ProductionActivePower <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:production-active-power"}
|
||||
Number:Power ProductionMaxActivePower <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:production-max-active-power"}
|
||||
Number:Power SellToGridPower <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:export-to-grid-power"}
|
||||
Number:Energy TotalSellEnergy <energy> (GF_UtilityRoomSolar) ["Measurement", "Energy"] {channel="fenecon:home-device:local:exported-to-grid-energy"}
|
||||
|
||||
Number:Power ConsumptionActivePower <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:consumption-active-power"}
|
||||
Number:Power ConsumptionMaxActivePower <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:consumption-max-active-power"}
|
||||
Number:Power ConsumptionActivePowerPhase1 <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:consumption-active-power-l1"}
|
||||
Number:Power ConsumptionActivePowerPhase2 <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:consumption-active-power-l2"}
|
||||
Number:Power ConsumptionActivePowerPhase3 <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:consumption-active-power-l3"}
|
||||
|
||||
Number:Power BuyFromGridPower <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:import-from-grid-power"}
|
||||
Number:Energy TotalBuyEnergy <energy> (GF_UtilityRoomSolar) ["Measurement", "Energy"] {channel="fenecon:home-device:local:imported-from-grid-energy"}
|
||||
|
||||
Number:Temperature InverterAirTemp <temperature> (GF_UtilityRoomSolar) ["Measurement", "Temperature"] {channel="fenecon:home-device:local:inverter-air-temperature"}
|
||||
Number:Temperature InverterRadiatorTemp <temperature> (GF_UtilityRoomSolar) ["Measurement", "Temperature"] {channel="fenecon:home-device:local:inverter-radiator-temperature"}
|
||||
Number:Temperature BmsBoxTemp <temperature> (GF_UtilityRoomSolar) ["Measurement", "Temperature"] {channel="fenecon:home-device:local:bms-pack-temperature"}
|
||||
|
||||
Number:ElectricPotential BattTowerVoltage <energy> (GF_UtilityRoomSolar) ["Measurement", "Voltage"] {channel="fenecon:home-device:local:batt-tower-voltage"}
|
||||
Number:ElectricCurrent BattTowerCurrent <energy> (GF_UtilityRoomSolar) ["Measurement", "Current"] {channel="fenecon:home-device:local:batt-tower-current"}
|
||||
|
||||
// Charger corresponds to the solar power plant on the roof.
|
||||
Number:Power ChargerWestActualPower <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:charger0-actual-power"}
|
||||
Number:ElectricPotential ChargerWestVoltage <energy> (GF_UtilityRoomSolar) ["Measurement", "Voltage"] {channel="fenecon:home-device:local:charger0-voltage"}
|
||||
Number:ElectricCurrent ChargerWestCurrent <energy> (GF_UtilityRoomSolar) ["Measurement", "Current"] {channel="fenecon:home-device:local:charger0-current"}
|
||||
Number:Power ChargerEastActualPower <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:charger1-actual-power"}
|
||||
Number:ElectricPotential ChargerEastVoltage <energy> (GF_UtilityRoomSolar) ["Measurement", "Voltage"] {channel="fenecon:home-device:local:charger1-voltage"}
|
||||
Number:ElectricCurrent ChargerEastCurrent <energy> (GF_UtilityRoomSolar) ["Measurement", "Current"] {channel="fenecon:home-device:local:charger1-current"}
|
||||
Number:Power ChargerSouthActualPower <energy> (GF_UtilityRoomSolar) ["Measurement", "Power"] {channel="fenecon:home-device:local:charger2-actual-power"}
|
||||
Number:ElectricPotential ChargerSouthVoltage <energy> (GF_UtilityRoomSolar) ["Measurement", "Voltage"] {channel="fenecon:home-device:local:charger2-voltage"}
|
||||
Number:ElectricCurrent ChargerSouthCurrent <energy> (GF_UtilityRoomSolar) ["Measurement", "Current"] {channel="fenecon:home-device:local:charger2-current"}
|
||||
|
||||
// Examples of items for calculating the energy purchased and sold. Look at the demo.rules section.
|
||||
Number:Currency SoldEnergy "Total sold energy [%.2f €]" <price> (GF_UtilityRoomSolar)
|
||||
Number:Currency PurchasedEnergy "Total purchased energy [%.2f €]" <price> (GF_UtilityRoomSolar)
|
||||
```
|
||||
|
||||
### demo.sitemap
|
||||
|
@ -105,6 +140,21 @@ sitemap demo label="FENECON Example Sitemap" {
|
|||
}
|
||||
```
|
||||
|
||||
### rrd4j.persist
|
||||
|
||||
```perl
|
||||
Strategies {
|
||||
everyMinute : "0 * * * * ?"
|
||||
default = everyChange
|
||||
}
|
||||
|
||||
Items {
|
||||
ProductionActivePower: strategy = everyUpdate, everyMinute, restoreOnStartup
|
||||
ConsumptionActivePower: strategy = everyUpdate, everyMinute, restoreOnStartup
|
||||
BuyFromGridPower: strategy = everyUpdate, everyMinute, restoreOnStartup
|
||||
}
|
||||
```
|
||||
|
||||
### demo.rules
|
||||
|
||||
:::: tabs
|
||||
|
@ -152,6 +202,25 @@ then
|
|||
var result = current * purchasedPricePerKiloWattHour;
|
||||
PurchasedEnergy.postUpdate(result)
|
||||
end
|
||||
|
||||
// !!! This is only designed as a demonstration, the calculation should only be executed every 30 or 60 minutes if necessary. And for the calculation, be sure to consider the persistence example: rrd4j.persist!
|
||||
rule "Calculation daily power values"
|
||||
when
|
||||
Item LastFeneconUpdate changed
|
||||
then
|
||||
var dailyMax = (ProductionActivePower.maximumSince(now.with(LocalTime.of(0,0,0,0))).state as Number).floatValue()
|
||||
MaxProductionActivePowerOfTheDay.postUpdate(dailyMax)
|
||||
|
||||
var dailyProduction = (ProductionActivePower.sumSince(now.with(LocalTime.of(0,0,0,0))) as Number).floatValue() / 60 / 1000
|
||||
ProductionActivePowerOfTheDay.postUpdate(dailyProduction)
|
||||
|
||||
var dailyConsumption = (ConsumptionActivePower.sumSince(now.with(LocalTime.of(0,0,0,0))) as Number).floatValue() / 60 / 1000
|
||||
ConsumptionActivePowerOfTheDay.postUpdate(dailyConsumption)
|
||||
|
||||
var dailyBuyFromGrid = (BuyFromGridPower.sumSince(now.with(LocalTime.of(0,0,0,0))) as Number).floatValue() / 60 / 1000
|
||||
BuyFromGridPowerOfTheDay.postUpdate(dailyBuyFromGrid)
|
||||
|
||||
end
|
||||
```
|
||||
|
||||
:::
|
||||
|
|
|
@ -15,6 +15,7 @@ package org.openhab.binding.fenecon.internal;
|
|||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.fenecon.internal.api.Address;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
|
@ -32,6 +33,7 @@ public class FeneconBindingConstants {
|
|||
public static final ThingTypeUID THING_TYPE_HOME_DEVICE = new ThingTypeUID(BINDING_ID, "home-device");
|
||||
|
||||
// List of all FENECON Addresses
|
||||
// Group: _sum/...
|
||||
public static final String STATE_ADDRESS = "_sum/State";
|
||||
public static final String ESS_SOC_ADDRESS = "_sum/EssSoc";
|
||||
public static final String CONSUMPTION_ACTIVE_POWER_ADDRESS = "_sum/ConsumptionActivePower";
|
||||
|
@ -46,13 +48,45 @@ public class FeneconBindingConstants {
|
|||
public static final String GRID_MODE_ADDRESS = "_sum/GridMode";
|
||||
public static final String GRID_SELL_ACTIVE_ENERGY_ADDRESS = "_sum/GridSellActiveEnergy";
|
||||
public static final String GRID_BUY_ACTIVE_ENERGY_ADDRESS = "_sum/GridBuyActiveEnergy";
|
||||
// Group: _meta/...
|
||||
public static final String FEMS_VERSION_ADDRESS = "_meta/Version";
|
||||
// Group: batteryInverter0/...
|
||||
public static final String BATT_INVERTER_AIR_TEMP_ADDRESS = "batteryInverter0/AirTemperature";
|
||||
public static final String BATT_INVERTER_RADIATOR_TEMP_ADDRESS = "batteryInverter0/RadiatorTemperature";
|
||||
public static final String BATT_INVERTER_BMS_PACK_TEMP_ADDRESS = "batteryInverter0/BmsPackTemperature";
|
||||
// Group: battery0/...
|
||||
public static final String BATT_TOWER_PACK_VOLTAGE_ADDRESS = "battery0/Tower0PackVoltage";
|
||||
public static final String BATT_TOWER_CURRENT_ADDRESS = "battery0/Current";
|
||||
public static final String BATT_SOH_ADDRESS = "battery0/Soh";
|
||||
// Group: charger0/...
|
||||
public static final String CHARGER0_ACTUAL_POWER_ADDRESS = "charger0/ActualPower";
|
||||
public static final String CHARGER0_VOLTAGE_ADDRESS = "charger0/Voltage";
|
||||
public static final String CHARGER0_CURRENT_ADDRESS = "charger0/Current";
|
||||
// Group: charger1/...
|
||||
public static final String CHARGER1_ACTUAL_POWER_ADDRESS = "charger1/ActualPower";
|
||||
public static final String CHARGER1_VOLTAGE_ADDRESS = "charger1/Voltage";
|
||||
public static final String CHARGER1_CURRENT_ADDRESS = "charger1/Current";
|
||||
// Group: charger2/...
|
||||
public static final String CHARGER2_ACTUAL_POWER_ADDRESS = "charger2/ActualPower";
|
||||
public static final String CHARGER2_VOLTAGE_ADDRESS = "charger2/Voltage";
|
||||
public static final String CHARGER2_CURRENT_ADDRESS = "charger2/Current";
|
||||
|
||||
// Group of all FENECON Addresses
|
||||
public static final List<String> ADDRESSES = List.of(STATE_ADDRESS, GRID_MODE_ADDRESS,
|
||||
CONSUMPTION_ACTIVE_POWER_ADDRESS, CONSUMPTION_ACTIVE_POWER_PHASE1_ADDRESS,
|
||||
CONSUMPTION_ACTIVE_POWER_PHASE2_ADDRESS, CONSUMPTION_ACTIVE_POWER_PHASE3_ADDRESS,
|
||||
CONSUMPTION_MAX_ACTIVE_POWER_ADDRESS, PRODUCTION_MAX_ACTIVE_POWER_ADDRESS, PRODUCTION_ACTIVE_POWER_ADDRESS,
|
||||
GRID_ACTIVE_POWER_ADDRESS, GRID_BUY_ACTIVE_ENERGY_ADDRESS, GRID_SELL_ACTIVE_ENERGY_ADDRESS, ESS_SOC_ADDRESS,
|
||||
ESS_DISCHARGE_POWER_ADDRESS);
|
||||
public static final List<Address> ADDRESSES = List.of(new Address(STATE_ADDRESS), new Address(GRID_MODE_ADDRESS),
|
||||
new Address(CONSUMPTION_ACTIVE_POWER_ADDRESS), new Address(CONSUMPTION_ACTIVE_POWER_PHASE1_ADDRESS),
|
||||
new Address(CONSUMPTION_ACTIVE_POWER_PHASE2_ADDRESS), new Address(CONSUMPTION_ACTIVE_POWER_PHASE3_ADDRESS),
|
||||
new Address(CONSUMPTION_MAX_ACTIVE_POWER_ADDRESS), new Address(PRODUCTION_MAX_ACTIVE_POWER_ADDRESS),
|
||||
new Address(PRODUCTION_ACTIVE_POWER_ADDRESS), new Address(GRID_ACTIVE_POWER_ADDRESS),
|
||||
new Address(GRID_BUY_ACTIVE_ENERGY_ADDRESS), new Address(GRID_SELL_ACTIVE_ENERGY_ADDRESS),
|
||||
new Address(ESS_SOC_ADDRESS), new Address(ESS_DISCHARGE_POWER_ADDRESS), new Address(FEMS_VERSION_ADDRESS),
|
||||
new Address(BATT_INVERTER_AIR_TEMP_ADDRESS), new Address(BATT_INVERTER_RADIATOR_TEMP_ADDRESS),
|
||||
new Address(BATT_INVERTER_BMS_PACK_TEMP_ADDRESS), new Address(BATT_TOWER_PACK_VOLTAGE_ADDRESS),
|
||||
new Address(BATT_TOWER_CURRENT_ADDRESS), new Address(BATT_SOH_ADDRESS),
|
||||
new Address(CHARGER0_ACTUAL_POWER_ADDRESS), new Address(CHARGER1_ACTUAL_POWER_ADDRESS),
|
||||
new Address(CHARGER2_ACTUAL_POWER_ADDRESS), new Address(CHARGER0_VOLTAGE_ADDRESS),
|
||||
new Address(CHARGER1_VOLTAGE_ADDRESS), new Address(CHARGER2_VOLTAGE_ADDRESS),
|
||||
new Address(CHARGER0_CURRENT_ADDRESS), new Address(CHARGER1_CURRENT_ADDRESS),
|
||||
new Address(CHARGER2_CURRENT_ADDRESS));
|
||||
|
||||
// List of all Channel IDs
|
||||
public static final String STATE_CHANNEL = "state";
|
||||
|
@ -72,4 +106,20 @@ public class FeneconBindingConstants {
|
|||
public static final String EXPORTED_TO_GRID_ENERGY_CHANNEL = "exported-to-grid-energy";
|
||||
public static final String IMPORTED_FROM_GRID_ENERGY_CHANNEL = "imported-from-grid-energy";
|
||||
public static final String LAST_UPDATE_CHANNEL = "last-update";
|
||||
public static final String FEMS_VERSION_CHANNEL = "fems-version";
|
||||
public static final String BATT_INVERTER_AIR_TEMP_CHANNEL = "inverter-air-temperature";
|
||||
public static final String BATT_INVERTER_RADIATOR_TEMP_CHANNEL = "inverter-radiator-temperature";
|
||||
public static final String BATT_INVERTER_BMS_PACK_TEMP_CHANNEL = "bms-pack-temperature";
|
||||
public static final String BATT_TOWER_PACK_VOLTAGE_CHANNEL = "batt-tower-voltage";
|
||||
public static final String BATT_TOWER_CURRENT_CHANNEL = "batt-tower-current";
|
||||
public static final String BATT_SOH_CHANNEL = "batt-tower-soh";
|
||||
public static final String CHARGER0_ACTUAL_POWER_CHANNEL = "charger0-actual-power";
|
||||
public static final String CHARGER1_ACTUAL_POWER_CHANNEL = "charger1-actual-power";
|
||||
public static final String CHARGER2_ACTUAL_POWER_CHANNEL = "charger2-actual-power";
|
||||
public static final String CHARGER0_VOLTAGE_CHANNEL = "charger0-voltage";
|
||||
public static final String CHARGER1_VOLTAGE_CHANNEL = "charger1-voltage";
|
||||
public static final String CHARGER2_VOLTAGE_CHANNEL = "charger2-voltage";
|
||||
public static final String CHARGER0_CURRENT_CHANNEL = "charger0-current";
|
||||
public static final String CHARGER1_CURRENT_CHANNEL = "charger1-current";
|
||||
public static final String CHARGER2_CURRENT_CHANNEL = "charger2-current";
|
||||
}
|
||||
|
|
|
@ -12,13 +12,14 @@
|
|||
*/
|
||||
package org.openhab.binding.fenecon.internal;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.fenecon.internal.api.AddressComponentChannelUtil;
|
||||
import org.openhab.binding.fenecon.internal.api.BatteryPower;
|
||||
import org.openhab.binding.fenecon.internal.api.FeneconController;
|
||||
import org.openhab.binding.fenecon.internal.api.FeneconResponse;
|
||||
|
@ -29,6 +30,7 @@ import org.openhab.core.library.types.DateTimeType;
|
|||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
@ -71,18 +73,21 @@ public class FeneconHandler extends BaseThingHandler {
|
|||
}
|
||||
|
||||
private void pollingCode() {
|
||||
for (String eachChannel : FeneconBindingConstants.ADDRESSES) {
|
||||
List<String> componentRequests = AddressComponentChannelUtil
|
||||
.createComponentRequests(FeneconBindingConstants.ADDRESSES);
|
||||
|
||||
for (String eachComponentRequest : componentRequests) {
|
||||
try {
|
||||
@SuppressWarnings("null")
|
||||
Optional<FeneconResponse> response = feneconController.requestChannel(eachChannel);
|
||||
List<FeneconResponse> responses = feneconController.requestChannel(eachComponentRequest);
|
||||
|
||||
if (response.isPresent()) {
|
||||
processDataPoint(response.get());
|
||||
for (FeneconResponse eachResponse : responses) {
|
||||
processDataPoint(eachResponse);
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} catch (FeneconException err) {
|
||||
logger.trace("FENECON - connection problem on FENECON channel {}", eachChannel, err);
|
||||
logger.trace("FENECON - connection problem on FENECON channel {}", eachComponentRequest, err);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, err.getMessage());
|
||||
return;
|
||||
}
|
||||
|
@ -167,6 +172,102 @@ public class FeneconHandler extends BaseThingHandler {
|
|||
updateState(FeneconBindingConstants.IMPORTED_FROM_GRID_ENERGY_CHANNEL,
|
||||
new QuantityType<>(Integer.valueOf(response.value()), Units.WATT_HOUR));
|
||||
break;
|
||||
case FeneconBindingConstants.FEMS_VERSION_ADDRESS:
|
||||
// { "address": "_meta/Version","type": "STRING", "accessMode": "RO", "text": "", "unit": "", "value":
|
||||
// "2025.2.3"}
|
||||
updateState(FeneconBindingConstants.FEMS_VERSION_CHANNEL, new StringType(response.value()));
|
||||
break;
|
||||
case FeneconBindingConstants.BATT_INVERTER_AIR_TEMP_ADDRESS:
|
||||
// {"address": "batteryInverter0/AirTemperature","type": "INTEGER","accessMode": "RO", "text": "",
|
||||
// "unit": "C", "value": 41 }
|
||||
updateState(FeneconBindingConstants.BATT_INVERTER_AIR_TEMP_CHANNEL,
|
||||
new QuantityType<>(Integer.valueOf(response.value()), SIUnits.CELSIUS));
|
||||
break;
|
||||
case FeneconBindingConstants.BATT_INVERTER_RADIATOR_TEMP_ADDRESS:
|
||||
// {"address": "batteryInverter0/RadiatorTemperature","type": "INTEGER", "accessMode": "RO", "text": "",
|
||||
// "unit": "C", "value": 37 }
|
||||
updateState(FeneconBindingConstants.BATT_INVERTER_RADIATOR_TEMP_CHANNEL,
|
||||
new QuantityType<>(Integer.valueOf(response.value()), SIUnits.CELSIUS));
|
||||
break;
|
||||
case FeneconBindingConstants.BATT_INVERTER_BMS_PACK_TEMP_ADDRESS:
|
||||
// {"address": "batteryInverter0/BmsPackTemperature", "type": "INTEGER", "accessMode": "RO", "text": "",
|
||||
// "unit": "C", "value": 26 }
|
||||
updateState(FeneconBindingConstants.BATT_INVERTER_BMS_PACK_TEMP_CHANNEL,
|
||||
new QuantityType<>(Integer.valueOf(response.value()), SIUnits.CELSIUS));
|
||||
break;
|
||||
case FeneconBindingConstants.BATT_TOWER_PACK_VOLTAGE_ADDRESS:
|
||||
// {"address": "battery0/Tower0PackVoltage", "type": "INTEGER", "accessMode": "RO", "text": "", "unit":
|
||||
// "", "value": 2749 }
|
||||
// Tower pack voltage in mV
|
||||
updateState(FeneconBindingConstants.BATT_TOWER_PACK_VOLTAGE_CHANNEL,
|
||||
new QuantityType<>(Integer.valueOf(response.value()) / 1000.0, Units.VOLT));
|
||||
break;
|
||||
case FeneconBindingConstants.BATT_TOWER_CURRENT_ADDRESS:
|
||||
// {"address": "battery0/Current", "type": "INTEGER", "accessMode": "RO", "text": "", "unit": "A",
|
||||
// "value": 9 }
|
||||
updateState(FeneconBindingConstants.BATT_TOWER_CURRENT_CHANNEL,
|
||||
new QuantityType<>(Integer.valueOf(response.value()), Units.AMPERE));
|
||||
break;
|
||||
case FeneconBindingConstants.BATT_SOH_ADDRESS:
|
||||
// { "address": "battery0/Soh", "type": "INTEGER", "accessMode": "RO", "text": "", "unit": "%", "value":
|
||||
// 100 }
|
||||
updateState(FeneconBindingConstants.BATT_SOH_CHANNEL,
|
||||
new QuantityType<>(Integer.valueOf(response.value()), Units.PERCENT));
|
||||
break;
|
||||
case FeneconBindingConstants.CHARGER0_ACTUAL_POWER_ADDRESS:
|
||||
// { "address": "charger0/ActualPower", "type": "INTEGER", "accessMode": "RO", "text": "", "unit": "W",
|
||||
// "value": 312 }
|
||||
updateState(FeneconBindingConstants.CHARGER0_ACTUAL_POWER_CHANNEL,
|
||||
new QuantityType<>(Integer.valueOf(response.value()), Units.WATT));
|
||||
break;
|
||||
case FeneconBindingConstants.CHARGER1_ACTUAL_POWER_ADDRESS:
|
||||
// { "address": "charger1/ActualPower", "type": "INTEGER", "accessMode": "RO", "text": "", "unit": "W",
|
||||
// "value": 33 }
|
||||
updateState(FeneconBindingConstants.CHARGER1_ACTUAL_POWER_CHANNEL,
|
||||
new QuantityType<>(Integer.valueOf(response.value()), Units.WATT));
|
||||
break;
|
||||
case FeneconBindingConstants.CHARGER2_ACTUAL_POWER_ADDRESS:
|
||||
// { "address": "charger2/ActualPower", "type": "INTEGER", "accessMode": "RO", "text": "", "unit": "W",
|
||||
// "value": 412 }
|
||||
updateState(FeneconBindingConstants.CHARGER2_ACTUAL_POWER_CHANNEL,
|
||||
new QuantityType<>(Integer.valueOf(response.value()), Units.WATT));
|
||||
break;
|
||||
case FeneconBindingConstants.CHARGER0_VOLTAGE_ADDRESS:
|
||||
// { "address": "charger0/Voltage", "type": "INTEGER", "accessMode": "RO", "text": "", "unit": "mV",
|
||||
// "value": 193000 }
|
||||
updateState(FeneconBindingConstants.CHARGER0_VOLTAGE_CHANNEL,
|
||||
new QuantityType<>(Integer.valueOf(response.value()) / 1000.0, Units.VOLT));
|
||||
break;
|
||||
case FeneconBindingConstants.CHARGER1_VOLTAGE_ADDRESS:
|
||||
// { "address": "charger1/Voltage", "type": "INTEGER", "accessMode": "RO", "text": "", "unit": "mV",
|
||||
// "value": 193000 }
|
||||
updateState(FeneconBindingConstants.CHARGER1_VOLTAGE_CHANNEL,
|
||||
new QuantityType<>(Integer.valueOf(response.value()) / 1000.0, Units.VOLT));
|
||||
break;
|
||||
case FeneconBindingConstants.CHARGER2_VOLTAGE_ADDRESS:
|
||||
// { "address": "charger2/Voltage", "type": "INTEGER", "accessMode": "RO", "text": "", "unit": "mV",
|
||||
// "value": 193000 }
|
||||
updateState(FeneconBindingConstants.CHARGER2_VOLTAGE_CHANNEL,
|
||||
new QuantityType<>(Integer.valueOf(response.value()) / 1000.0, Units.VOLT));
|
||||
break;
|
||||
case FeneconBindingConstants.CHARGER0_CURRENT_ADDRESS:
|
||||
// {"address": "charger0/Current", "type": "INTEGER", "accessMode": "RO", "text": "", "unit": "mA",
|
||||
// "value": 1200 },
|
||||
updateState(FeneconBindingConstants.CHARGER0_CURRENT_CHANNEL,
|
||||
new QuantityType<>(Integer.valueOf(response.value()) / 1000.0, Units.AMPERE));
|
||||
break;
|
||||
case FeneconBindingConstants.CHARGER1_CURRENT_ADDRESS:
|
||||
// {"address": "charger1/Current", "type": "INTEGER", "accessMode": "RO", "text": "", "unit": "mA",
|
||||
// "value": 1000 },
|
||||
updateState(FeneconBindingConstants.CHARGER1_CURRENT_CHANNEL,
|
||||
new QuantityType<>(Integer.valueOf(response.value()) / 1000.0, Units.AMPERE));
|
||||
break;
|
||||
case FeneconBindingConstants.CHARGER2_CURRENT_ADDRESS:
|
||||
// {"address": "charger2/Current", "type": "INTEGER", "accessMode": "RO", "text": "", "unit": "mA",
|
||||
// "value": 1100 },
|
||||
updateState(FeneconBindingConstants.CHARGER2_CURRENT_CHANNEL,
|
||||
new QuantityType<>(Integer.valueOf(response.value()) / 1000.0, Units.AMPERE));
|
||||
break;
|
||||
default:
|
||||
logger.trace("FENECON - No channel ID to address {} found.", response.address());
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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.fenecon.internal.api;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link Address} is a small helper class to split a REST-API Address in component and channel.
|
||||
*
|
||||
* @author Philipp Schneider - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class Address {
|
||||
|
||||
private final String address;
|
||||
private final AddressComponent component;
|
||||
private final AddressChannel channel;
|
||||
|
||||
public Address(@NotNull String address) {
|
||||
this.address = address;
|
||||
|
||||
String[] parts = address.split("/");
|
||||
|
||||
if (parts.length != 2) {
|
||||
throw new IllegalArgumentException("Invalid address format 'component/channel' for: " + address);
|
||||
}
|
||||
|
||||
component = new AddressComponent(parts[0]);
|
||||
channel = new AddressChannel(parts[1]);
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public AddressComponent getComponent() {
|
||||
return component;
|
||||
}
|
||||
|
||||
public AddressChannel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(other instanceof Address)) {
|
||||
return false;
|
||||
}
|
||||
Address address = (Address) other;
|
||||
if (address.address.equals(this.address)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return address;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.fenecon.internal.api;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link AddressChannel} is a container class to identify a channel of a {@link Address}.
|
||||
*
|
||||
* @author Philipp Schneider - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public record AddressChannel(String channel) implements Comparable<AddressChannel> {
|
||||
@Override
|
||||
public int compareTo(AddressChannel that) {
|
||||
return Objects.compare(this, that,
|
||||
Comparator.comparing(AddressChannel::channel).thenComparing(AddressChannel::channel));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.fenecon.internal.api;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link AddressComponent} is a container class to identify a component of a {@link Address}.
|
||||
*
|
||||
* @author Philipp Schneider - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public record AddressComponent(String component) {
|
||||
public AddressComponent(String component) {
|
||||
this.component = convertComponentWithRegEx(component);
|
||||
}
|
||||
|
||||
// Bundle same components with regex if possible, to reduce the number of requests
|
||||
private static String convertComponentWithRegEx(String component) {
|
||||
return component.replaceFirst("\\d$", ".+");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.fenecon.internal.api;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link AddressComponentChannelUtil} is a small helper class for e.g. to split a list of {@link Address} in
|
||||
* {@link AddressComponent} and a list of {@link AddressChannel} for a group REST-API request.
|
||||
*
|
||||
* @author Philipp Schneider - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AddressComponentChannelUtil {
|
||||
|
||||
public static List<String> createComponentRequests(List<Address> addresses) {
|
||||
return split(addresses).entrySet().stream()
|
||||
.map(entry -> createComponentRequest(entry.getKey(), entry.getValue())).toList();
|
||||
}
|
||||
|
||||
protected static Map<AddressComponent, Set<AddressChannel>> split(List<Address> addresses) {
|
||||
return addresses.stream().collect(Collectors.toMap(Address::getComponent,
|
||||
value -> new TreeSet<AddressChannel>(List.of(value.getChannel())), (existing, newest) -> {
|
||||
existing.addAll(newest);
|
||||
return existing;
|
||||
}));
|
||||
}
|
||||
|
||||
protected static String createComponentRequest(AddressComponent component, Set<AddressChannel> channels) {
|
||||
// Grouping REST-API requests - e.g. http://...:8084/rest/channel/_sum/(State|EssSoc)
|
||||
|
||||
// For valid URIs the pipe delimiter must be encoded as %7C
|
||||
return component.component() + "/("
|
||||
+ String.join("%7C", channels.stream().map(AddressChannel::channel).toList()) + ")";
|
||||
}
|
||||
}
|
|
@ -14,6 +14,8 @@ package org.openhab.binding.fenecon.internal.api;
|
|||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -33,6 +35,8 @@ import org.openhab.binding.fenecon.internal.exception.FeneconException;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
@ -68,16 +72,17 @@ public class FeneconController {
|
|||
}
|
||||
|
||||
/**
|
||||
* Queries the data for a specified channel.
|
||||
* Queries the data for a specified channel group.
|
||||
*
|
||||
* @param channel Channel to be queried, e.g. _sum/State .
|
||||
* @param channel Channel group to be queried, e.g. _sum/(State|EssSoc) .
|
||||
* @return {@link FeneconResponse} can be optional if values are not available.
|
||||
* @throws FeneconException is thrown if there are problems with the connection or processing of data to the FENECON
|
||||
* system.
|
||||
*/
|
||||
public Optional<FeneconResponse> requestChannel(String channel) throws FeneconException {
|
||||
public List<FeneconResponse> requestChannel(String channel) throws FeneconException {
|
||||
try {
|
||||
URI uri = new URI(getBaseUrl(config) + "rest/channel/" + channel);
|
||||
logger.trace("FENECON - uri: {}", uri);
|
||||
|
||||
Request request = httpClient.newRequest(uri).timeout(10, TimeUnit.SECONDS).method(HttpMethod.GET);
|
||||
logger.trace("FENECON - request: {}", request);
|
||||
|
@ -88,37 +93,68 @@ public class FeneconController {
|
|||
|
||||
int statusCode = response.getStatus();
|
||||
if (statusCode > 300) {
|
||||
// Authentication error
|
||||
if (statusCode == 401) {
|
||||
if (statusCode == 401) { // Authentication error
|
||||
throw new FeneconAuthenticationException(
|
||||
"Authentication on the FENECON system was not possible. Check password.");
|
||||
} else if (statusCode == 404) { // Channel-URL not supported
|
||||
logger.debug("Channel request '{}' not possible, is not supported by the FENECON system.", channel);
|
||||
return List.of();
|
||||
} else {
|
||||
throw new FeneconCommunicationException("Unexpected http status code: " + statusCode);
|
||||
}
|
||||
} else {
|
||||
return createResponseFromJson(JsonParser.parseString(response.getContentAsString()).getAsJsonObject());
|
||||
return createResponseFromJson(JsonParser.parseString(response.getContentAsString()));
|
||||
}
|
||||
} catch (TimeoutException | ExecutionException | UnsupportedOperationException | InterruptedException err) {
|
||||
throw new FeneconCommunicationException("Communication error with FENECON system on channel: " + channel,
|
||||
throw new FeneconCommunicationException(
|
||||
"Communication error: " + err.getMessage() + " with FENECON system on channel: " + channel, err);
|
||||
} catch (URISyntaxException | IllegalStateException | JsonSyntaxException err) {
|
||||
throw new FeneconCommunicationException("Syntax error: " + err.getMessage() + " on channel: " + channel,
|
||||
err);
|
||||
} catch (URISyntaxException | JsonSyntaxException err) {
|
||||
throw new FeneconCommunicationException("Syntax error on channel: " + channel, err);
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<FeneconResponse> createResponseFromJson(JsonObject response) {
|
||||
private List<FeneconResponse> createResponseFromJson(JsonElement jsonElement) {
|
||||
if (jsonElement.isJsonArray()) {
|
||||
return createResponseFromJsonArray(jsonElement.getAsJsonArray());
|
||||
} else if (jsonElement.isJsonObject()) {
|
||||
return createResponseFromJsonObject(jsonElement.getAsJsonObject());
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected response format: " + jsonElement);
|
||||
}
|
||||
}
|
||||
|
||||
private List<FeneconResponse> createResponseFromJsonArray(JsonArray jsonArray) {
|
||||
// Example response: [{"address":"_sum/EssSoc","type":"INTEGER","accessMode":"RO","text":"Range
|
||||
// 0..100","unit":"%","value":99}]
|
||||
List<FeneconResponse> result = new ArrayList<>();
|
||||
for (JsonElement each : jsonArray) {
|
||||
if (each.isJsonObject()) {
|
||||
result.addAll(createResponseFromJsonObject(each.getAsJsonObject()));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<FeneconResponse> createResponseFromJsonObject(JsonObject jsonObject) {
|
||||
// Example response: {"address":"_sum/EssSoc","type":"INTEGER","accessMode":"RO","text":"Range
|
||||
// 0..100","unit":"%","value":99}
|
||||
List<FeneconResponse> result = new ArrayList<>();
|
||||
convertJsonObjectToResponse(jsonObject).ifPresent(result::add);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (response.get("value").isJsonNull()) {
|
||||
// Example problem response: {"address":"_sum/EssSoc","type":"INTEGER","accessMode":"RO","text":"Range
|
||||
private Optional<FeneconResponse> convertJsonObjectToResponse(JsonObject jsonObject) {
|
||||
if (jsonObject.get("value").isJsonNull()) {
|
||||
// Example problem response:
|
||||
// {"address":"_sum/EssSoc","type":"INTEGER","accessMode":"RO","text":"Range
|
||||
// 0..100","unit":"%","value":null}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
String address = response.get("address").getAsString();
|
||||
String text = response.get("text").getAsString();
|
||||
String value = response.get("value").getAsString();
|
||||
String address = jsonObject.get("address").getAsString();
|
||||
String text = jsonObject.get("text").getAsString();
|
||||
String value = jsonObject.get("value").getAsString();
|
||||
|
||||
return Optional.of(new FeneconResponse(address, text, value));
|
||||
}
|
||||
|
|
|
@ -21,34 +21,54 @@ thing-type.config.fenecon.home-device.refreshInterval.description = Interval the
|
|||
|
||||
# channel types
|
||||
|
||||
channel-type.fenecon.batt-tower-current.label = FEMS Battery Current
|
||||
channel-type.fenecon.batt-tower-current.description = Battery current of the FENECON energy management system (FEMS).
|
||||
channel-type.fenecon.batt-tower-soh.label = Battery Health State
|
||||
channel-type.fenecon.batt-tower-soh.description = Battery state of health.
|
||||
channel-type.fenecon.batt-tower-voltage.label = FEMS Battery Voltage
|
||||
channel-type.fenecon.batt-tower-voltage.description = Battery voltage of the FENECON energy management system (FEMS).
|
||||
channel-type.fenecon.bms-pack-temperature.label = BMS Pack Temperature
|
||||
channel-type.fenecon.bms-pack-temperature.description = Temperature in the battery management system (BMS) box.
|
||||
channel-type.fenecon.charger-actual-power.label = Charger Actual Power
|
||||
channel-type.fenecon.charger-actual-power.description = Charger actual power on the corresponding charger.
|
||||
channel-type.fenecon.charger-current.label = Charger Current
|
||||
channel-type.fenecon.charger-current.description = Charger current on the corresponding charger.
|
||||
channel-type.fenecon.charger-power.label = Charger Power
|
||||
channel-type.fenecon.charger-power.description = Current charger power of energy storage system in watt.
|
||||
channel-type.fenecon.charger-power.description = Current charger power of energy storage system.
|
||||
channel-type.fenecon.charger-voltage.label = Charger Voltage
|
||||
channel-type.fenecon.charger-voltage.description = Charger voltage on the corresponding charger.
|
||||
channel-type.fenecon.consumption-active-power-phase.label = Consumer Power Phase
|
||||
channel-type.fenecon.consumption-active-power-phase.description = Current active power consumer load in watt on the corresponding phase.
|
||||
channel-type.fenecon.consumption-active-power-phase.description = Current active power consumer load on the corresponding phase.
|
||||
channel-type.fenecon.consumption-active-power.label = Consumer Power
|
||||
channel-type.fenecon.consumption-active-power.description = Current active power consumer load in watt.
|
||||
channel-type.fenecon.consumption-active-power.description = Current active power consumer load.
|
||||
channel-type.fenecon.consumption-max-active-power.label = Consumer Max Power
|
||||
channel-type.fenecon.consumption-max-active-power.description = Maximum active consumption power in watt that was measured.
|
||||
channel-type.fenecon.consumption-max-active-power.description = Maximum active consumption power that was measured.
|
||||
channel-type.fenecon.discharger-power.label = Discharger Power
|
||||
channel-type.fenecon.discharger-power.description = Current discharger power of energy storage system in watt.
|
||||
channel-type.fenecon.discharger-power.description = Current discharger power of energy storage system.
|
||||
channel-type.fenecon.emergency-power-mode.label = Emergency Power Mode
|
||||
channel-type.fenecon.emergency-power-mode.description = Indicates if there is no power from the grid and the emergency power mode is on.
|
||||
channel-type.fenecon.ess-soc.label = Battery State
|
||||
channel-type.fenecon.ess-soc.description = Battery state of charge in percent
|
||||
channel-type.fenecon.ess-soc.description = Battery state of charge.
|
||||
channel-type.fenecon.export-to-grid-power.label = Export Grid Power
|
||||
channel-type.fenecon.export-to-grid-power.description = Current export power to grid in watt.
|
||||
channel-type.fenecon.export-to-grid-power.description = Current export power to grid.
|
||||
channel-type.fenecon.exported-to-grid-energy.label = Exported Grid Energy
|
||||
channel-type.fenecon.exported-to-grid-energy.description = Total energy exported to the grid in watt per hour.
|
||||
channel-type.fenecon.exported-to-grid-energy.description = Total energy exported to the grid.
|
||||
channel-type.fenecon.fems-version.label = FEMS Version
|
||||
channel-type.fenecon.fems-version.description = FENECON energy management system (FEMS) version.
|
||||
channel-type.fenecon.import-from-grid-power.label = Import Grid Power
|
||||
channel-type.fenecon.import-from-grid-power.description = Current import power from grid in watt.
|
||||
channel-type.fenecon.import-from-grid-power.description = Current import power from grid.
|
||||
channel-type.fenecon.imported-from-grid-energy.label = Imported Grid Energy
|
||||
channel-type.fenecon.imported-from-grid-energy.description = Total energy imported from the grid in watt per hour.
|
||||
channel-type.fenecon.imported-from-grid-energy.description = Total energy imported from the grid.
|
||||
channel-type.fenecon.inverter-air-temperature.label = Inverter Air Temperature
|
||||
channel-type.fenecon.inverter-air-temperature.description = Air temperature at the inverter.
|
||||
channel-type.fenecon.inverter-radiator-temperature.label = Inverter Radiator Temperature
|
||||
channel-type.fenecon.inverter-radiator-temperature.description = Radiator temperature at the inverter.
|
||||
channel-type.fenecon.last-update.label = Last Update
|
||||
channel-type.fenecon.last-update.description = Last successful update via REST-API from the FENECON system
|
||||
channel-type.fenecon.last-update.description = Last successful update via REST-API from the FENECON system.
|
||||
channel-type.fenecon.production-active-power.label = Producer Power
|
||||
channel-type.fenecon.production-active-power.description = Current active power producer load in watt.
|
||||
channel-type.fenecon.production-active-power.description = Current active power producer load.
|
||||
channel-type.fenecon.production-max-active-power.label = Producer Max Power
|
||||
channel-type.fenecon.production-max-active-power.description = Maximum active production power in watt that was measured.
|
||||
channel-type.fenecon.production-max-active-power.description = Maximum active production power that was measured.
|
||||
channel-type.fenecon.state.label = System State
|
||||
channel-type.fenecon.state.description = FENECON system state
|
||||
channel-type.fenecon.state.state.option.OK = Ok
|
||||
|
|
|
@ -14,8 +14,10 @@
|
|||
|
||||
<channels>
|
||||
<channel id="state" typeId="state"/>
|
||||
<channel id="fems-version" typeId="fems-version"/>
|
||||
<channel id="last-update" typeId="last-update"/>
|
||||
<channel id="ess-soc" typeId="ess-soc"/>
|
||||
<channel id="batt-tower-soh" typeId="batt-tower-soh"/>
|
||||
<channel id="charger-power" typeId="charger-power"/>
|
||||
<channel id="discharger-power" typeId="discharger-power"/>
|
||||
<channel id="emergency-power-mode" typeId="emergency-power-mode"/>
|
||||
|
@ -30,8 +32,26 @@
|
|||
<channel id="exported-to-grid-energy" typeId="exported-to-grid-energy"/>
|
||||
<channel id="import-from-grid-power" typeId="import-from-grid-power"/>
|
||||
<channel id="imported-from-grid-energy" typeId="imported-from-grid-energy"/>
|
||||
<channel id="inverter-air-temperature" typeId="inverter-air-temperature"/>
|
||||
<channel id="inverter-radiator-temperature" typeId="inverter-radiator-temperature"/>
|
||||
<channel id="bms-pack-temperature" typeId="bms-pack-temperature"/>
|
||||
<channel id="batt-tower-voltage" typeId="batt-tower-voltage"/>
|
||||
<channel id="batt-tower-current" typeId="batt-tower-current"/>
|
||||
<channel id="charger0-actual-power" typeId="charger-actual-power"/>
|
||||
<channel id="charger0-voltage" typeId="charger-voltage"/>
|
||||
<channel id="charger0-current" typeId="charger-current"/>
|
||||
<channel id="charger1-actual-power" typeId="charger-actual-power"/>
|
||||
<channel id="charger1-voltage" typeId="charger-voltage"/>
|
||||
<channel id="charger1-current" typeId="charger-current"/>
|
||||
<channel id="charger2-actual-power" typeId="charger-actual-power"/>
|
||||
<channel id="charger2-voltage" typeId="charger-voltage"/>
|
||||
<channel id="charger2-current" typeId="charger-current"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="thingTypeVersion">1</property>
|
||||
</properties>
|
||||
|
||||
<config-description>
|
||||
<parameter name="hostname" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
|
@ -76,31 +96,45 @@
|
|||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="fems-version">
|
||||
<item-type>String</item-type>
|
||||
<label>FEMS Version</label>
|
||||
<description>FENECON energy management system (FEMS) version.</description>
|
||||
<category>Text</category>
|
||||
<state readOnly="true" pattern="%s"></state>
|
||||
</channel-type>
|
||||
<channel-type id="last-update">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Update</label>
|
||||
<description>Last successful update via REST-API from the FENECON system</description>
|
||||
<description>Last successful update via REST-API from the FENECON system.</description>
|
||||
<category>Time</category>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="ess-soc">
|
||||
<item-type unitHint="%">Number:Dimensionless</item-type>
|
||||
<label>Battery State</label>
|
||||
<description>Battery state of charge in percent</description>
|
||||
<description>Battery state of charge.</description>
|
||||
<category>BatteryLevel</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="batt-tower-soh">
|
||||
<item-type unitHint="%">Number:Dimensionless</item-type>
|
||||
<label>Battery Health State</label>
|
||||
<description>Battery state of health.</description>
|
||||
<category>BatteryLevel</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="charger-power">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Charger Power</label>
|
||||
<description>Current charger power of energy storage system in watt.</description>
|
||||
<description>Current charger power of energy storage system.</description>
|
||||
<category>Energy</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="discharger-power">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Discharger Power</label>
|
||||
<description>Current discharger power of energy storage system in watt.</description>
|
||||
<description>Current discharger power of energy storage system.</description>
|
||||
<category>Energy</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
|
@ -114,65 +148,121 @@
|
|||
<channel-type id="production-active-power">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Producer Power</label>
|
||||
<description>Current active power producer load in watt.</description>
|
||||
<description>Current active power producer load.</description>
|
||||
<category>Energy</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="export-to-grid-power">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Export Grid Power</label>
|
||||
<description>Current export power to grid in watt.</description>
|
||||
<description>Current export power to grid.</description>
|
||||
<category>Energy</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="exported-to-grid-energy">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Exported Grid Energy</label>
|
||||
<description>Total energy exported to the grid in watt per hour.</description>
|
||||
<description>Total energy exported to the grid.</description>
|
||||
<category>Energy</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="consumption-active-power">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Consumer Power</label>
|
||||
<description>Current active power consumer load in watt.</description>
|
||||
<description>Current active power consumer load.</description>
|
||||
<category>Energy</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="consumption-active-power-phase">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Consumer Power Phase</label>
|
||||
<description>Current active power consumer load in watt on the corresponding phase.</description>
|
||||
<description>Current active power consumer load on the corresponding phase.</description>
|
||||
<category>Energy</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="consumption-max-active-power">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Consumer Max Power</label>
|
||||
<description>Maximum active consumption power in watt that was measured.</description>
|
||||
<description>Maximum active consumption power that was measured.</description>
|
||||
<category>Energy</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="production-max-active-power">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Producer Max Power</label>
|
||||
<description>Maximum active production power in watt that was measured.</description>
|
||||
<description>Maximum active production power that was measured.</description>
|
||||
<category>Energy</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="import-from-grid-power">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Import Grid Power</label>
|
||||
<description>Current import power from grid in watt.</description>
|
||||
<description>Current import power from grid.</description>
|
||||
<category>Energy</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="imported-from-grid-energy">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Imported Grid Energy</label>
|
||||
<description>Total energy imported from the grid in watt per hour.</description>
|
||||
<description>Total energy imported from the grid.</description>
|
||||
<category>Energy</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="inverter-air-temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Inverter Air Temperature</label>
|
||||
<description>Air temperature at the inverter.</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="inverter-radiator-temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Inverter Radiator Temperature</label>
|
||||
<description>Radiator temperature at the inverter.</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="bms-pack-temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>BMS Pack Temperature</label>
|
||||
<description>Temperature in the battery management system (BMS) box.</description>
|
||||
<category>Temperature</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="batt-tower-voltage">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>FEMS Battery Voltage</label>
|
||||
<description>Battery voltage of the FENECON energy management system (FEMS).</description>
|
||||
<category>Voltage</category>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="batt-tower-current">
|
||||
<item-type>Number:ElectricCurrent</item-type>
|
||||
<label>FEMS Battery Current</label>
|
||||
<description>Battery current of the FENECON energy management system (FEMS).</description>
|
||||
<category>Current</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="charger-actual-power">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Charger Actual Power</label>
|
||||
<description>Charger actual power on the corresponding charger.</description>
|
||||
<category>Energy</category>
|
||||
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="charger-voltage">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>Charger Voltage</label>
|
||||
<description>Charger voltage on the corresponding charger.</description>
|
||||
<category>Voltage</category>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="charger-current">
|
||||
<item-type>Number:ElectricCurrent</item-type>
|
||||
<label>Charger Current</label>
|
||||
<description>Charger current on the corresponding charger.</description>
|
||||
<category>Current</category>
|
||||
<state readOnly="true" pattern="%.2f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
|
||||
|
||||
<thing-type uid="fenecon:home-device">
|
||||
<instruction-set targetVersion="1">
|
||||
<add-channel id="fems-version">
|
||||
<type>fenecon:fems-version</type>
|
||||
</add-channel>
|
||||
<add-channel id="batt-tower-soh">
|
||||
<type>fenecon:batt-tower-soh</type>
|
||||
</add-channel>
|
||||
<add-channel id="inverter-air-temperature">
|
||||
<type>fenecon:inverter-air-temperature</type>
|
||||
</add-channel>
|
||||
<add-channel id="inverter-radiator-temperature">
|
||||
<type>fenecon:inverter-radiator-temperature</type>
|
||||
</add-channel>
|
||||
<add-channel id="bms-pack-temperature">
|
||||
<type>fenecon:bms-pack-temperature</type>
|
||||
</add-channel>
|
||||
<add-channel id="batt-tower-voltage">
|
||||
<type>fenecon:batt-tower-voltage</type>
|
||||
</add-channel>
|
||||
<add-channel id="batt-tower-current">
|
||||
<type>fenecon:batt-tower-current</type>
|
||||
</add-channel>
|
||||
<add-channel id="charger0-actual-power">
|
||||
<type>fenecon:charger-actual-power</type>
|
||||
</add-channel>
|
||||
<add-channel id="charger0-voltage">
|
||||
<type>fenecon:charger-voltage</type>
|
||||
</add-channel>
|
||||
<add-channel id="charger0-current">
|
||||
<type>fenecon:charger-current</type>
|
||||
</add-channel>
|
||||
<add-channel id="charger1-actual-power">
|
||||
<type>fenecon:charger-actual-power</type>
|
||||
</add-channel>
|
||||
<add-channel id="charger1-voltage">
|
||||
<type>fenecon:charger-voltage</type>
|
||||
</add-channel>
|
||||
<add-channel id="charger1-current">
|
||||
<type>fenecon:charger-current</type>
|
||||
</add-channel>
|
||||
<add-channel id="charger2-actual-power">
|
||||
<type>fenecon:charger-actual-power</type>
|
||||
</add-channel>
|
||||
<add-channel id="charger2-voltage">
|
||||
<type>fenecon:charger-voltage</type>
|
||||
</add-channel>
|
||||
<add-channel id="charger2-current">
|
||||
<type>fenecon:charger-current</type>
|
||||
</add-channel>
|
||||
</instruction-set>
|
||||
</thing-type>
|
||||
|
||||
</update:update-descriptions>
|
|
@ -20,6 +20,7 @@ import java.util.List;
|
|||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.fenecon.internal.api.Address;
|
||||
|
||||
/**
|
||||
* Test for {@link FeneconBindingConstants}.
|
||||
|
@ -31,13 +32,13 @@ public class FeneconBindingConstantsTest {
|
|||
|
||||
@Test
|
||||
void checkAllAddressesAreListed() throws IllegalArgumentException, IllegalAccessException {
|
||||
List<String> findAddresses = new ArrayList<>();
|
||||
List<Address> findAddresses = new ArrayList<>();
|
||||
|
||||
for (Field eachDeclaredField : FeneconBindingConstants.class.getDeclaredFields()) {
|
||||
if (eachDeclaredField.getName().endsWith("_ADDRESS")) {
|
||||
String address = (String) eachDeclaredField.get(FeneconBindingConstants.class);
|
||||
if (address != null) {
|
||||
findAddresses.add(address);
|
||||
findAddresses.add(new Address(address));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,4 +46,33 @@ public class FeneconBindingConstantsTest {
|
|||
assertEquals(FeneconBindingConstants.ADDRESSES.size(), findAddresses.size());
|
||||
assertTrue(findAddresses.containsAll(FeneconBindingConstants.ADDRESSES));
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkAllAddressesAreUnique() throws IllegalArgumentException, IllegalAccessException {
|
||||
List<Address> findAddresses = new ArrayList<>();
|
||||
|
||||
for (Field eachDeclaredField : FeneconBindingConstants.class.getDeclaredFields()) {
|
||||
if (eachDeclaredField.getName().endsWith("_ADDRESS")) {
|
||||
String address = (String) eachDeclaredField.get(FeneconBindingConstants.class);
|
||||
if (address != null) {
|
||||
Address findAddress = new Address(address);
|
||||
assertFalse(findAddresses.contains(findAddress),
|
||||
"Duplicate address found: " + findAddress + " for field " + eachDeclaredField.getName());
|
||||
findAddresses.add(findAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkAllAddressesConsistOfComponentAndChannel() throws IllegalArgumentException, IllegalAccessException {
|
||||
for (Field eachDeclaredField : FeneconBindingConstants.class.getDeclaredFields()) {
|
||||
if (eachDeclaredField.getName().endsWith("_ADDRESS")) {
|
||||
String address = (String) eachDeclaredField.get(FeneconBindingConstants.class);
|
||||
if (address != null) {
|
||||
assertDoesNotThrow(() -> new Address(address));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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.fenecon.internal.api;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.fenecon.internal.FeneconBindingConstants;
|
||||
|
||||
/**
|
||||
* Test for {@link AddressComponentChannelUtil}.
|
||||
*
|
||||
* @author Philipp Schneider - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AddressComponentChannelUtilTest {
|
||||
|
||||
@Test
|
||||
void testCreateComponentRequests() {
|
||||
// ARRANGE
|
||||
List<Address> expectedSumList = List.of(new Address(FeneconBindingConstants.STATE_ADDRESS),
|
||||
new Address(FeneconBindingConstants.GRID_MODE_ADDRESS), new Address("system/Version"),
|
||||
new Address("battery/SoH"), new Address("battery/Current"));
|
||||
|
||||
// ACT
|
||||
List<String> result = AddressComponentChannelUtil.createComponentRequests(expectedSumList);
|
||||
|
||||
// ASSERT
|
||||
assertTrue(result.size() == 3);
|
||||
assertTrue(result.contains("_sum/(GridMode%7CState)"));
|
||||
assertTrue(result.contains("system/(Version)"));
|
||||
assertTrue(result.contains("battery/(Current%7CSoH)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateComponentRequestsWithRegEx() {
|
||||
// ARRANGE
|
||||
List<Address> expectedSumList = List.of(new Address("system/Version"), new Address("battery0/SoH"),
|
||||
new Address("battery0/Current"), new Address("battery1/SoH"));
|
||||
|
||||
// ACT
|
||||
List<String> result = AddressComponentChannelUtil.createComponentRequests(expectedSumList);
|
||||
|
||||
// ASSERT
|
||||
assertTrue(result.size() == 2);
|
||||
assertTrue(result.contains("system/(Version)"));
|
||||
assertTrue(result.contains("battery.+/(Current%7CSoH)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSplit() {
|
||||
// ARRANGE
|
||||
List<Address> expectedSumList = List.of(new Address(FeneconBindingConstants.STATE_ADDRESS),
|
||||
new Address(FeneconBindingConstants.GRID_MODE_ADDRESS),
|
||||
new Address(FeneconBindingConstants.CONSUMPTION_ACTIVE_POWER_ADDRESS));
|
||||
|
||||
List<Address> expectedFantasyList = List.of(new Address("fantasy/Potter"));
|
||||
List<Address> expectedScyFiList = List.of(new Address("scify/Dune"), new Address("scify/Expanse"));
|
||||
|
||||
List<Address> addresses = Stream.of(expectedSumList, expectedFantasyList, expectedScyFiList)
|
||||
.flatMap(Collection::stream).toList();
|
||||
|
||||
// ACT
|
||||
Map<AddressComponent, Set<AddressChannel>> result = AddressComponentChannelUtil.split(addresses);
|
||||
|
||||
// ASSERT
|
||||
assertTrue(result.getOrDefault(new Address(FeneconBindingConstants.STATE_ADDRESS).getComponent(), Set.of())
|
||||
.containsAll(expectedSumList.stream().map(Address::getChannel).toList()));
|
||||
|
||||
assertTrue(result.getOrDefault(new AddressComponent("fantasy"), Set.of())
|
||||
.containsAll(expectedFantasyList.stream().map(Address::getChannel).toList()));
|
||||
|
||||
assertTrue(result.getOrDefault(new AddressComponent("scify"), Set.of())
|
||||
.containsAll(expectedScyFiList.stream().map(Address::getChannel).toList()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateRequest() {
|
||||
// ARRANGE
|
||||
List<Address> expectedSumList = List.of(new Address(FeneconBindingConstants.STATE_ADDRESS),
|
||||
new Address(FeneconBindingConstants.GRID_MODE_ADDRESS));
|
||||
|
||||
// ACT
|
||||
AddressComponent component = new AddressComponent("_sum");
|
||||
Map<AddressComponent, Set<AddressChannel>> split = AddressComponentChannelUtil.split(expectedSumList);
|
||||
Set<AddressChannel> sciFyChannels = split.getOrDefault(component, Set.of());
|
||||
String result = AddressComponentChannelUtil.createComponentRequest(component, sciFyChannels);
|
||||
|
||||
// ASSERT
|
||||
assertEquals("_sum/(GridMode%7CState)", result);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.fenecon.internal.api;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Test for {@link AddressComponent}.
|
||||
*
|
||||
* @author Philipp Schneider - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AddressComponentTest {
|
||||
|
||||
@Test
|
||||
void testFixComponent() {
|
||||
String component = "component";
|
||||
|
||||
AddressComponent result = new AddressComponent(component);
|
||||
|
||||
assertEquals("component", result.component());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testVariableComponentChangedForBundleRegexRequest1() {
|
||||
String component = "charger0";
|
||||
|
||||
AddressComponent result = new AddressComponent(component);
|
||||
|
||||
assertEquals("charger.+", result.component());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testVariableComponentChangedForBundleRegexRequest2() {
|
||||
String component = "charger1";
|
||||
|
||||
AddressComponent result = new AddressComponent(component);
|
||||
|
||||
assertEquals("charger.+", result.component());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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.fenecon.internal.api;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Test for {@link Address}.
|
||||
*
|
||||
* @author Philipp Schneider - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AddressTest {
|
||||
|
||||
@Test
|
||||
void testSplitAddress() {
|
||||
String adress = "component/channel";
|
||||
|
||||
Address restApiAddress = new Address(adress);
|
||||
|
||||
assertEquals("component", restApiAddress.getComponent().component());
|
||||
assertEquals("channel", restApiAddress.getChannel().channel());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInvalidAddress1() {
|
||||
String invalidAddress = "invalidAddress";
|
||||
|
||||
assertThrowsExactly(IllegalArgumentException.class, () -> {
|
||||
new Address(invalidAddress);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInvalidAddress2() {
|
||||
String invalidAddress = "in/valid/address";
|
||||
|
||||
assertThrowsExactly(IllegalArgumentException.class, () -> {
|
||||
new Address(invalidAddress);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCompareSameAddress() {
|
||||
Address adress1 = new Address("component/channel");
|
||||
Address adress2 = new Address("component/channel");
|
||||
|
||||
assertEquals(adress1, adress2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCompareNotSameAddress() {
|
||||
Address adress1 = new Address("component/channel1");
|
||||
Address adress2 = new Address("component/channel2");
|
||||
|
||||
assertNotEquals(adress1, adress2);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue