mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[fenecon] Initial contribution (#17174)
* Initial implementation of the FENECON Binding Signed-off-by: Philipp Schneider <philipp.schneider@nixo-soft.de>
This commit is contained in:
parent
66cfcc2f64
commit
6b2462ca22
@ -112,6 +112,7 @@
|
|||||||
/bundles/org.openhab.binding.exec/ @kgoderis
|
/bundles/org.openhab.binding.exec/ @kgoderis
|
||||||
/bundles/org.openhab.binding.feed/ @openhab/add-ons-maintainers
|
/bundles/org.openhab.binding.feed/ @openhab/add-ons-maintainers
|
||||||
/bundles/org.openhab.binding.feican/ @Hilbrand
|
/bundles/org.openhab.binding.feican/ @Hilbrand
|
||||||
|
/bundles/org.openhab.binding.fenecon/ @nixoso
|
||||||
/bundles/org.openhab.binding.fineoffsetweatherstation/ @Andy2003
|
/bundles/org.openhab.binding.fineoffsetweatherstation/ @Andy2003
|
||||||
/bundles/org.openhab.binding.flicbutton/ @pfink
|
/bundles/org.openhab.binding.flicbutton/ @pfink
|
||||||
/bundles/org.openhab.binding.fmiweather/ @ssalonen
|
/bundles/org.openhab.binding.fmiweather/ @ssalonen
|
||||||
|
@ -551,6 +551,11 @@
|
|||||||
<artifactId>org.openhab.binding.feican</artifactId>
|
<artifactId>org.openhab.binding.feican</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.binding.fenecon</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openhab.addons.bundles</groupId>
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
<artifactId>org.openhab.binding.fineoffsetweatherstation</artifactId>
|
<artifactId>org.openhab.binding.fineoffsetweatherstation</artifactId>
|
||||||
|
13
bundles/org.openhab.binding.fenecon/NOTICE
Normal file
13
bundles/org.openhab.binding.fenecon/NOTICE
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
This content is produced and maintained by the openHAB project.
|
||||||
|
|
||||||
|
* Project home: https://www.openhab.org
|
||||||
|
|
||||||
|
== Declared Project Licenses
|
||||||
|
|
||||||
|
This program and the accompanying materials are made available under the terms
|
||||||
|
of the Eclipse Public License 2.0 which is available at
|
||||||
|
https://www.eclipse.org/legal/epl-2.0/.
|
||||||
|
|
||||||
|
== Source Code
|
||||||
|
|
||||||
|
https://github.com/openhab/openhab-addons
|
160
bundles/org.openhab.binding.fenecon/README.md
Normal file
160
bundles/org.openhab.binding.fenecon/README.md
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
# FENECON Binding
|
||||||
|
|
||||||
|
The FENECON Binding integrates the [FENECON energy storage system](https://fenecon.de/) device into the openHAB system via [REST-API](https://docs.fenecon.de/_/de/fems/fems-app/OEM_App_REST_JSON.html).
|
||||||
|
|
||||||
|
With the binding, it is possible to request status information from FENECON Home to allow you home automation decisions based on the current energy management.
|
||||||
|
|
||||||
|
This makes it possible, for example, to switch on other consumers such as the dishwasher or washing machine in the case of power overproduction.
|
||||||
|
|
||||||
|
## Supported Things
|
||||||
|
|
||||||
|
Currently only one Thing is supported: The `home-device` connection to the FENECON energy storage system.
|
||||||
|
|
||||||
|
This Binding was tested with an [FENECON HOME 10](https://fenecon.de/fenecon-home-10/) device.
|
||||||
|
|
||||||
|
## Discovery
|
||||||
|
|
||||||
|
Auto-discovery is not supported.
|
||||||
|
|
||||||
|
## Thing Configuration
|
||||||
|
|
||||||
|
The FENECON Thing only needs to be configured with the `hostname`, all other parameters are optional and prefilled with the suitable default values:
|
||||||
|
|
||||||
|
| Parameter | Description |
|
||||||
|
|-----------------|----------------------------------------------------------------------------------|
|
||||||
|
| hostname | Hostname or IP address of the FENECON device, e.g. 192.168.1.11 |
|
||||||
|
| password | Password of the FENECON device. The password for guest access is set by default. |
|
||||||
|
| port | Port of the FENECON device. Default: 8084 |
|
||||||
|
| refreshInterval | Interval the device is polled in sec. Default 30 seconds |
|
||||||
|
|
||||||
|
## Channels
|
||||||
|
|
||||||
|
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. |
|
||||||
|
|
||||||
|
## Full Example
|
||||||
|
|
||||||
|
### fenecon.things
|
||||||
|
|
||||||
|
```java
|
||||||
|
Thing fenecon:home-device:local "FENECON Home" [hostname="192.168.1.11", refreshInterval=5]
|
||||||
|
```
|
||||||
|
|
||||||
|
### demo.items
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Sitemap Items
|
||||||
|
Group Home "MyHome" <house> ["Indoor"]
|
||||||
|
Group GF "GroundFloor" <groundfloor> (Home) ["GroundFloor"]
|
||||||
|
// Utility room
|
||||||
|
Group GF_UtilityRoom "Utility room" <energy> (Home, 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"}
|
||||||
|
|
||||||
|
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"}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
```perl
|
||||||
|
sitemap demo label="FENECON Example Sitemap" {
|
||||||
|
Frame label="Groundfloor" icon="groundfloor" {
|
||||||
|
Group item=GF_UtilityRoom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### demo.rules
|
||||||
|
|
||||||
|
:::: tabs
|
||||||
|
|
||||||
|
::: tab DSL
|
||||||
|
|
||||||
|
```java
|
||||||
|
rule "Blackout detection"
|
||||||
|
when
|
||||||
|
Item EmergencyPowerMode changed to ON
|
||||||
|
then
|
||||||
|
val msg = "🚨 Power blackout detected, emergency power mode running."
|
||||||
|
logInfo("PowerBlackout", msg)
|
||||||
|
sendBroadcastNotification(msg)
|
||||||
|
end
|
||||||
|
|
||||||
|
rule "Battery 100 percent"
|
||||||
|
when
|
||||||
|
Item EssSoc changed
|
||||||
|
then
|
||||||
|
var batteryState = (EssSoc.getState() as Number).intValue()
|
||||||
|
if(batteryState == 100){
|
||||||
|
val msg = "🔋 Full battery, consumers can be activated."
|
||||||
|
logInfo("FullBattery", msg)
|
||||||
|
sendBroadcastNotification(msg)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
rule "Calculation sold energy"
|
||||||
|
when
|
||||||
|
Item TotalSellEnergy changed
|
||||||
|
then
|
||||||
|
val sellingPricePerKiloWattHour = 0.07 // €
|
||||||
|
var current = (TotalSellEnergy.getState() as Number).intValue()
|
||||||
|
var result = current * sellingPricePerKiloWattHour;
|
||||||
|
SoldEnergy.postUpdate(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
rule "Calculation purchased energy"
|
||||||
|
when
|
||||||
|
Item TotalBuyEnergy changed
|
||||||
|
then
|
||||||
|
val purchasedPricePerKiloWattHour = 0.32 // €
|
||||||
|
var current = (TotalBuyEnergy.getState() as Number).intValue()
|
||||||
|
var result = current * purchasedPricePerKiloWattHour;
|
||||||
|
PurchasedEnergy.postUpdate(result)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
::::
|
17
bundles/org.openhab.binding.fenecon/pom.xml
Normal file
17
bundles/org.openhab.binding.fenecon/pom.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||||
|
<version>4.3.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>org.openhab.binding.fenecon</artifactId>
|
||||||
|
|
||||||
|
<name>openHAB Add-ons :: Bundles :: FENECON Binding</name>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<features name="org.openhab.binding.fenecon-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||||
|
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||||
|
|
||||||
|
<feature name="openhab-binding-fenecon" description="FENECON Binding" version="${project.version}">
|
||||||
|
<feature>openhab-runtime-base</feature>
|
||||||
|
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.fenecon/${project.version}</bundle>
|
||||||
|
</feature>
|
||||||
|
</features>
|
@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link FeneconBindingConstants} class defines common constants, which are
|
||||||
|
* used across the whole binding.
|
||||||
|
*
|
||||||
|
* @author Philipp Schneider - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class FeneconBindingConstants {
|
||||||
|
|
||||||
|
private static final String BINDING_ID = "fenecon";
|
||||||
|
|
||||||
|
// List of all Thing Type UIDs
|
||||||
|
public static final ThingTypeUID THING_TYPE_HOME_DEVICE = new ThingTypeUID(BINDING_ID, "home-device");
|
||||||
|
|
||||||
|
// List of all FENECON Addresses
|
||||||
|
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";
|
||||||
|
public static final String CONSUMPTION_ACTIVE_POWER_PHASE1_ADDRESS = "_sum/ConsumptionActivePowerL1";
|
||||||
|
public static final String CONSUMPTION_ACTIVE_POWER_PHASE2_ADDRESS = "_sum/ConsumptionActivePowerL2";
|
||||||
|
public static final String CONSUMPTION_ACTIVE_POWER_PHASE3_ADDRESS = "_sum/ConsumptionActivePowerL3";
|
||||||
|
public static final String CONSUMPTION_MAX_ACTIVE_POWER_ADDRESS = "_sum/ConsumptionMaxActivePower";
|
||||||
|
public static final String PRODUCTION_MAX_ACTIVE_POWER_ADDRESS = "_sum/ProductionMaxActivePower";
|
||||||
|
public static final String PRODUCTION_ACTIVE_POWER_ADDRESS = "_sum/ProductionActivePower";
|
||||||
|
public static final String GRID_ACTIVE_POWER_ADDRESS = "_sum/GridActivePower";
|
||||||
|
public static final String ESS_DISCHARGE_POWER_ADDRESS = "_sum/EssDischargePower";
|
||||||
|
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 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);
|
||||||
|
|
||||||
|
// List of all Channel IDs
|
||||||
|
public static final String STATE_CHANNEL = "state";
|
||||||
|
public static final String ESS_SOC_CHANNEL = "ess-soc";
|
||||||
|
public static final String CONSUMPTION_ACTIVE_POWER_CHANNEL = "consumption-active-power";
|
||||||
|
public static final String CONSUMPTION_ACTIVE_POWER_PHASE1_CHANNEL = "consumption-active-power-l1";
|
||||||
|
public static final String CONSUMPTION_ACTIVE_POWER_PHASE2_CHANNEL = "consumption-active-power-l2";
|
||||||
|
public static final String CONSUMPTION_ACTIVE_POWER_PHASE3_CHANNEL = "consumption-active-power-l3";
|
||||||
|
public static final String CONSUMPTION_MAX_ACTIVE_POWER_CHANNEL = "consumption-max-active-power";
|
||||||
|
public static final String PRODUCTION_MAX_ACTIVE_POWER_CHANNEL = "production-max-active-power";
|
||||||
|
public static final String PRODUCTION_ACTIVE_POWER_CHANNEL = "production-active-power";
|
||||||
|
public static final String EXPORT_TO_GRID_POWER_CHANNEL = "export-to-grid-power";
|
||||||
|
public static final String IMPORT_FROM_GRID_POWER_CHANNEL = "import-from-grid-power";
|
||||||
|
public static final String ESS_CHARGER_POWER_CHANNEL = "charger-power";
|
||||||
|
public static final String ESS_DISCHARGER_POWER_CHANNEL = "discharger-power";
|
||||||
|
public static final String EMERGENCY_POWER_MODE_CHANNEL = "emergency-power-mode";
|
||||||
|
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";
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link FeneconConfiguration} class contains fields mapping thing configuration parameters.
|
||||||
|
*
|
||||||
|
* @author Philipp Schneider - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class FeneconConfiguration {
|
||||||
|
|
||||||
|
public String hostname = "";
|
||||||
|
public String password = "user";
|
||||||
|
public int port = 8084;
|
||||||
|
public int refreshInterval = 30;
|
||||||
|
}
|
@ -0,0 +1,189 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
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.BatteryPower;
|
||||||
|
import org.openhab.binding.fenecon.internal.api.FeneconController;
|
||||||
|
import org.openhab.binding.fenecon.internal.api.FeneconResponse;
|
||||||
|
import org.openhab.binding.fenecon.internal.api.GridPower;
|
||||||
|
import org.openhab.binding.fenecon.internal.api.State;
|
||||||
|
import org.openhab.binding.fenecon.internal.exception.FeneconException;
|
||||||
|
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.Units;
|
||||||
|
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.types.Command;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link FeneconHandler} is responsible for handling commands, which are
|
||||||
|
* sent to one of the channels.
|
||||||
|
*
|
||||||
|
* @author Philipp Schneider - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class FeneconHandler extends BaseThingHandler {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(FeneconHandler.class);
|
||||||
|
|
||||||
|
private FeneconConfiguration config = new FeneconConfiguration();
|
||||||
|
private @Nullable ScheduledFuture<?> pollingJob;
|
||||||
|
private @Nullable FeneconController feneconController;
|
||||||
|
private final HttpClient httpClient;
|
||||||
|
|
||||||
|
public FeneconHandler(Thing thing, HttpClient httpClient) {
|
||||||
|
super(thing);
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
config = getConfigAs(FeneconConfiguration.class);
|
||||||
|
|
||||||
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
|
|
||||||
|
feneconController = new FeneconController(config, this.httpClient);
|
||||||
|
pollingJob = scheduler.scheduleWithFixedDelay(this::pollingCode, 0, config.refreshInterval, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pollingCode() {
|
||||||
|
for (String eachChannel : FeneconBindingConstants.ADDRESSES) {
|
||||||
|
try {
|
||||||
|
@SuppressWarnings("null")
|
||||||
|
Optional<FeneconResponse> response = feneconController.requestChannel(eachChannel);
|
||||||
|
|
||||||
|
if (response.isPresent()) {
|
||||||
|
processDataPoint(response.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
} catch (FeneconException err) {
|
||||||
|
logger.trace("FENECON - connection problem on FENECON channel {}", eachChannel, err);
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, err.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set last successful update cycle
|
||||||
|
updateState(FeneconBindingConstants.LAST_UPDATE_CHANNEL, new DateTimeType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processDataPoint(FeneconResponse response) throws FeneconException {
|
||||||
|
switch (response.address()) {
|
||||||
|
case FeneconBindingConstants.STATE_ADDRESS:
|
||||||
|
// {"address":"_sum/State","type":"INTEGER","accessMode":"RO","text":"0:Ok, 1:Info, 2:Warning,
|
||||||
|
// 3:Fault","unit":"","value":0}
|
||||||
|
State state = State.get(response);
|
||||||
|
updateState(FeneconBindingConstants.STATE_CHANNEL, new StringType(state.state()));
|
||||||
|
break;
|
||||||
|
case FeneconBindingConstants.ESS_SOC_ADDRESS:
|
||||||
|
updateState(FeneconBindingConstants.ESS_SOC_CHANNEL,
|
||||||
|
new QuantityType<>(Integer.valueOf(response.value()), Units.PERCENT));
|
||||||
|
break;
|
||||||
|
case FeneconBindingConstants.CONSUMPTION_ACTIVE_POWER_ADDRESS:
|
||||||
|
updateState(FeneconBindingConstants.CONSUMPTION_ACTIVE_POWER_CHANNEL,
|
||||||
|
new QuantityType<>(Integer.valueOf(response.value()), Units.WATT));
|
||||||
|
break;
|
||||||
|
case FeneconBindingConstants.CONSUMPTION_ACTIVE_POWER_PHASE1_ADDRESS:
|
||||||
|
updateState(FeneconBindingConstants.CONSUMPTION_ACTIVE_POWER_PHASE1_CHANNEL,
|
||||||
|
new QuantityType<>(Integer.valueOf(response.value()), Units.WATT));
|
||||||
|
break;
|
||||||
|
case FeneconBindingConstants.CONSUMPTION_ACTIVE_POWER_PHASE2_ADDRESS:
|
||||||
|
updateState(FeneconBindingConstants.CONSUMPTION_ACTIVE_POWER_PHASE2_CHANNEL,
|
||||||
|
new QuantityType<>(Integer.valueOf(response.value()), Units.WATT));
|
||||||
|
break;
|
||||||
|
case FeneconBindingConstants.CONSUMPTION_ACTIVE_POWER_PHASE3_ADDRESS:
|
||||||
|
updateState(FeneconBindingConstants.CONSUMPTION_ACTIVE_POWER_PHASE3_CHANNEL,
|
||||||
|
new QuantityType<>(Integer.valueOf(response.value()), Units.WATT));
|
||||||
|
break;
|
||||||
|
case FeneconBindingConstants.CONSUMPTION_MAX_ACTIVE_POWER_ADDRESS:
|
||||||
|
updateState(FeneconBindingConstants.CONSUMPTION_MAX_ACTIVE_POWER_CHANNEL,
|
||||||
|
new QuantityType<>(Integer.valueOf(response.value()), Units.WATT));
|
||||||
|
break;
|
||||||
|
case FeneconBindingConstants.PRODUCTION_MAX_ACTIVE_POWER_ADDRESS:
|
||||||
|
updateState(FeneconBindingConstants.PRODUCTION_MAX_ACTIVE_POWER_CHANNEL,
|
||||||
|
new QuantityType<>(Integer.valueOf(response.value()), Units.WATT));
|
||||||
|
break;
|
||||||
|
case FeneconBindingConstants.PRODUCTION_ACTIVE_POWER_ADDRESS:
|
||||||
|
updateState(FeneconBindingConstants.PRODUCTION_ACTIVE_POWER_CHANNEL,
|
||||||
|
new QuantityType<>(Integer.valueOf(response.value()), Units.WATT));
|
||||||
|
break;
|
||||||
|
case FeneconBindingConstants.GRID_ACTIVE_POWER_ADDRESS:
|
||||||
|
// Grid exchange power. Negative values for sell-to-grid; positive for buy-from-grid"
|
||||||
|
GridPower gridPower = GridPower.get(response);
|
||||||
|
|
||||||
|
updateState(FeneconBindingConstants.EXPORT_TO_GRID_POWER_CHANNEL,
|
||||||
|
new QuantityType<>(gridPower.sellTo(), Units.WATT));
|
||||||
|
updateState(FeneconBindingConstants.IMPORT_FROM_GRID_POWER_CHANNEL,
|
||||||
|
new QuantityType<>(gridPower.buyFrom(), Units.WATT));
|
||||||
|
break;
|
||||||
|
case FeneconBindingConstants.ESS_DISCHARGE_POWER_ADDRESS:
|
||||||
|
// Actual AC-side battery discharge power of Energy Storage System.
|
||||||
|
// Negative values for charge; positive for discharge
|
||||||
|
BatteryPower batteryPower = BatteryPower.get(response);
|
||||||
|
|
||||||
|
updateState(FeneconBindingConstants.ESS_CHARGER_POWER_CHANNEL,
|
||||||
|
new QuantityType<>(batteryPower.chargerPower(), Units.WATT));
|
||||||
|
updateState(FeneconBindingConstants.ESS_DISCHARGER_POWER_CHANNEL,
|
||||||
|
new QuantityType<>(batteryPower.dischargerPower(), Units.WATT));
|
||||||
|
break;
|
||||||
|
case FeneconBindingConstants.GRID_MODE_ADDRESS:
|
||||||
|
// text":"1:On-Grid, 2:Off-Grid","unit":"","value":1
|
||||||
|
Integer gridMod = Integer.valueOf(response.value());
|
||||||
|
updateState(FeneconBindingConstants.EMERGENCY_POWER_MODE_CHANNEL,
|
||||||
|
gridMod == 2 ? OnOffType.ON : OnOffType.OFF);
|
||||||
|
break;
|
||||||
|
case FeneconBindingConstants.GRID_SELL_ACTIVE_ENERGY_ADDRESS:
|
||||||
|
// {"address":"_sum/GridSellActiveEnergy","type":"LONG","accessMode":"RO","text":"","unit":"Wh_Σ","value":374242}
|
||||||
|
updateState(FeneconBindingConstants.EXPORTED_TO_GRID_ENERGY_CHANNEL,
|
||||||
|
new QuantityType<>(Integer.valueOf(response.value()), Units.WATT_HOUR));
|
||||||
|
break;
|
||||||
|
case FeneconBindingConstants.GRID_BUY_ACTIVE_ENERGY_ADDRESS:
|
||||||
|
// "address":"_sum/GridBuyActiveEnergy","type":"LONG","accessMode":"RO","text":"","unit":"Wh_Σ","value":1105}
|
||||||
|
updateState(FeneconBindingConstants.IMPORTED_FROM_GRID_ENERGY_CHANNEL,
|
||||||
|
new QuantityType<>(Integer.valueOf(response.value()), Units.WATT_HOUR));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.trace("FENECON - No channel ID to address {} found.", response.address());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
ScheduledFuture<?> job = pollingJob;
|
||||||
|
if (job != null) {
|
||||||
|
job.cancel(true);
|
||||||
|
pollingJob = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
|
// Noop
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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;
|
||||||
|
|
||||||
|
import static org.openhab.binding.fenecon.internal.FeneconBindingConstants.THING_TYPE_HOME_DEVICE;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
|
import org.openhab.core.thing.Thing;
|
||||||
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandler;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||||
|
import org.osgi.service.component.annotations.Activate;
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
import org.osgi.service.component.annotations.Reference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link FeneconHandlerFactory} is responsible for creating things and thing
|
||||||
|
* handlers.
|
||||||
|
*
|
||||||
|
* @author Philipp Schneider - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@Component(configurationPid = "binding.fenecon", service = ThingHandlerFactory.class)
|
||||||
|
public class FeneconHandlerFactory extends BaseThingHandlerFactory {
|
||||||
|
|
||||||
|
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_HOME_DEVICE);
|
||||||
|
private final HttpClientFactory httpClientFactory;
|
||||||
|
|
||||||
|
@Activate
|
||||||
|
public FeneconHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
|
||||||
|
this.httpClientFactory = httpClientFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||||
|
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||||
|
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||||
|
|
||||||
|
if (THING_TYPE_HOME_DEVICE.equals(thingTypeUID)) {
|
||||||
|
return new FeneconHandler(thing, httpClientFactory.getCommonHttpClient());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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 BatteryPower} is a small helper class to convert the power value from battery.
|
||||||
|
*
|
||||||
|
* @author Philipp Schneider - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public record BatteryPower(int chargerPower, int dischargerPower) {
|
||||||
|
|
||||||
|
public static BatteryPower get(FeneconResponse response) {
|
||||||
|
// Actual AC-side battery discharge power of Energy Storage System.
|
||||||
|
// Negative values for charge; positive for discharge
|
||||||
|
Integer powerValue = Integer.valueOf(response.value());
|
||||||
|
int chargerPower = 0;
|
||||||
|
int dischargerPower = 0;
|
||||||
|
if (powerValue < 0) {
|
||||||
|
chargerPower = powerValue * -1;
|
||||||
|
} else {
|
||||||
|
dischargerPower = powerValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BatteryPower(chargerPower, dischargerPower);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.api.AuthenticationStore;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.eclipse.jetty.client.util.BasicAuthentication;
|
||||||
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
import org.openhab.binding.fenecon.internal.FeneconConfiguration;
|
||||||
|
import org.openhab.binding.fenecon.internal.exception.FeneconAuthenticationException;
|
||||||
|
import org.openhab.binding.fenecon.internal.exception.FeneconCommunicationException;
|
||||||
|
import org.openhab.binding.fenecon.internal.exception.FeneconException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link FeneconController} class provides API access to the FENECON system.
|
||||||
|
*
|
||||||
|
* @author Philipp Schneider - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class FeneconController {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(FeneconController.class);
|
||||||
|
|
||||||
|
private final FeneconConfiguration config;
|
||||||
|
private final HttpClient httpClient;
|
||||||
|
|
||||||
|
public FeneconController(FeneconConfiguration config, HttpClient httpClient) {
|
||||||
|
this.config = config;
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
|
||||||
|
logger.debug("FENECON: initialize REST-API connection to {} with polling interval: {} sec", getBaseUrl(config),
|
||||||
|
config.refreshInterval);
|
||||||
|
|
||||||
|
// Set BasicAuthentication for all requests on the http connection
|
||||||
|
AuthenticationStore auth = httpClient.getAuthenticationStore();
|
||||||
|
URI uri = URI.create(getBaseUrl(config));
|
||||||
|
auth.addAuthenticationResult(new BasicAuthentication.BasicResult(uri, "x", config.password));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getBaseUrl(FeneconConfiguration config) {
|
||||||
|
return "http://" + config.hostname + ":" + config.port + "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries the data for a specified channel.
|
||||||
|
*
|
||||||
|
* @param channel Channel to be queried, e.g. _sum/State .
|
||||||
|
* @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 {
|
||||||
|
try {
|
||||||
|
URI uri = new URI(getBaseUrl(config) + "rest/channel/" + channel);
|
||||||
|
|
||||||
|
Request request = httpClient.newRequest(uri).timeout(10, TimeUnit.SECONDS).method(HttpMethod.GET);
|
||||||
|
logger.trace("FENECON - request: {}", request);
|
||||||
|
|
||||||
|
ContentResponse response = request.send();
|
||||||
|
logger.trace("FENECON - response status code: {} body: {}", response.getStatus(),
|
||||||
|
response.getContentAsString());
|
||||||
|
|
||||||
|
int statusCode = response.getStatus();
|
||||||
|
if (statusCode > 300) {
|
||||||
|
// Authentication error
|
||||||
|
if (statusCode == 401) {
|
||||||
|
throw new FeneconAuthenticationException(
|
||||||
|
"Authentication on the FENECON system was not possible. Check password.");
|
||||||
|
} else {
|
||||||
|
throw new FeneconCommunicationException("Unexpected http status code: " + statusCode);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return createResponseFromJson(JsonParser.parseString(response.getContentAsString()).getAsJsonObject());
|
||||||
|
}
|
||||||
|
} catch (TimeoutException | ExecutionException | UnsupportedOperationException | InterruptedException err) {
|
||||||
|
throw new FeneconCommunicationException("Communication error with FENECON system on channel: " + channel,
|
||||||
|
err);
|
||||||
|
} catch (URISyntaxException | JsonSyntaxException err) {
|
||||||
|
throw new FeneconCommunicationException("Syntax error on channel: " + channel, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<FeneconResponse> createResponseFromJson(JsonObject response) {
|
||||||
|
// Example response: {"address":"_sum/EssSoc","type":"INTEGER","accessMode":"RO","text":"Range
|
||||||
|
// 0..100","unit":"%","value":99}
|
||||||
|
|
||||||
|
if (response.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();
|
||||||
|
|
||||||
|
return Optional.of(new FeneconResponse(address, text, value));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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 FeneconResponse} class provides the response from the FENECON system.
|
||||||
|
*
|
||||||
|
* @author Philipp Schneider - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public record FeneconResponse(String address, String text, String value) {
|
||||||
|
};
|
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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 GridPower} is a small helper class to convert the grid value.
|
||||||
|
*
|
||||||
|
* @author Philipp Schneider - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public record GridPower(int sellTo, int buyFrom) {
|
||||||
|
|
||||||
|
public static GridPower get(FeneconResponse response) {
|
||||||
|
// Grid exchange power. Negative values for sell-to-grid; positive for buy-from-grid"
|
||||||
|
Integer gridValue = Integer.valueOf(response.value());
|
||||||
|
int selltoGridPower = 0;
|
||||||
|
int buyFromGridPower = 0;
|
||||||
|
if (gridValue < 0) {
|
||||||
|
selltoGridPower = gridValue * -1;
|
||||||
|
} else {
|
||||||
|
buyFromGridPower = gridValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GridPower(selltoGridPower, buyFromGridPower);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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 State} is a small helper class to convert the state value.
|
||||||
|
*
|
||||||
|
* @author Philipp Schneider - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public record State(String state) {
|
||||||
|
|
||||||
|
public static State get(FeneconResponse response) {
|
||||||
|
// {"address":"_sum/State","type":"INTEGER","accessMode":"RO","text":"0:Ok, 1:Info, 2:Warning,
|
||||||
|
// 3:Fault","unit":"","value":0}
|
||||||
|
String text = response.text();
|
||||||
|
int begin = text.indexOf(response.value() + ":");
|
||||||
|
int end = text.indexOf(",", begin);
|
||||||
|
|
||||||
|
// No value to text mapping
|
||||||
|
if (begin < 0) {
|
||||||
|
return new State("Unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last text
|
||||||
|
if (end < 0) {
|
||||||
|
end = text.length();
|
||||||
|
}
|
||||||
|
return new State(text.substring(begin + 2, end));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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.exception;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class {@link FeneconAuthenticationException} is thrown if a authentication on the FENECON system is not possible.
|
||||||
|
*
|
||||||
|
* @author Philipp Schneider - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class FeneconAuthenticationException extends FeneconException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -9206453599559316730L;
|
||||||
|
|
||||||
|
public FeneconAuthenticationException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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.exception;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class {@link FeneconCommunicationException} is thrown if a communication problem occurs with the FENECON system.
|
||||||
|
*
|
||||||
|
* @author Philipp Schneider - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class FeneconCommunicationException extends FeneconException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -4334759327203382902L;
|
||||||
|
|
||||||
|
public FeneconCommunicationException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FeneconCommunicationException(String message, Exception exception) {
|
||||||
|
super(message, exception);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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.exception;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link FeneconException} class provides general exception for this binding.
|
||||||
|
*
|
||||||
|
* @author Philipp Schneider - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class FeneconException extends Exception {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 4454633961827361165L;
|
||||||
|
|
||||||
|
public FeneconException() {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
public FeneconException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FeneconException(Exception exception) {
|
||||||
|
super(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FeneconException(String message, Exception exception) {
|
||||||
|
super(message, exception);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<addon:addon id="fenecon" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
|
||||||
|
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
|
||||||
|
|
||||||
|
<type>binding</type>
|
||||||
|
<name>FENECON Home Binding</name>
|
||||||
|
<description>This is the binding for FENECON Home.</description>
|
||||||
|
<connection>local</connection>
|
||||||
|
|
||||||
|
</addon:addon>
|
@ -0,0 +1,57 @@
|
|||||||
|
# add-on
|
||||||
|
|
||||||
|
addon.fenecon.name = FENECON Home Binding
|
||||||
|
addon.fenecon.description = This is the binding for FENECON Home.
|
||||||
|
|
||||||
|
# thing types
|
||||||
|
|
||||||
|
thing-type.fenecon.home-device.label = FENECON Home
|
||||||
|
thing-type.fenecon.home-device.description = Thing for FENECON Home Device
|
||||||
|
|
||||||
|
# thing types config
|
||||||
|
|
||||||
|
thing-type.config.fenecon.home-device.hostname.label = Hostname
|
||||||
|
thing-type.config.fenecon.home-device.hostname.description = Hostname or IP address of the FENECON device, e.g. 192.168.1.11
|
||||||
|
thing-type.config.fenecon.home-device.password.label = Password
|
||||||
|
thing-type.config.fenecon.home-device.password.description = Password to access the device. The password for guest access is set by default.
|
||||||
|
thing-type.config.fenecon.home-device.port.label = Port
|
||||||
|
thing-type.config.fenecon.home-device.port.description = Port of the FENECON device
|
||||||
|
thing-type.config.fenecon.home-device.refreshInterval.label = Refresh Interval
|
||||||
|
thing-type.config.fenecon.home-device.refreshInterval.description = Interval the device is polled in sec.
|
||||||
|
|
||||||
|
# channel types
|
||||||
|
|
||||||
|
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.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.label = Consumer Power
|
||||||
|
channel-type.fenecon.consumption-active-power.description = Current active power consumer load in watt.
|
||||||
|
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.discharger-power.label = Discharger Power
|
||||||
|
channel-type.fenecon.discharger-power.description = Current discharger power of energy storage system in watt.
|
||||||
|
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.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.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.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.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.last-update.label = Last Update
|
||||||
|
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-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.state.label = System State
|
||||||
|
channel-type.fenecon.state.description = FENECON system state
|
||||||
|
channel-type.fenecon.state.state.option.OK = Ok
|
||||||
|
channel-type.fenecon.state.state.option.INFO = Info
|
||||||
|
channel-type.fenecon.state.state.option.WARN = Warning
|
||||||
|
channel-type.fenecon.state.state.option.FAULT = Fault
|
@ -0,0 +1,177 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<thing:thing-descriptions bindingId="fenecon"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||||
|
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||||
|
|
||||||
|
<!-- Sample Thing Type -->
|
||||||
|
<thing-type id="home-device">
|
||||||
|
|
||||||
|
<label>FENECON Home</label>
|
||||||
|
<description>Thing for FENECON Home Device</description>
|
||||||
|
<category>Solarplant</category>
|
||||||
|
|
||||||
|
<channels>
|
||||||
|
<channel id="state" typeId="state"/>
|
||||||
|
<channel id="last-update" typeId="last-update"/>
|
||||||
|
<channel id="ess-soc" typeId="ess-soc"/>
|
||||||
|
<channel id="charger-power" typeId="charger-power"/>
|
||||||
|
<channel id="discharger-power" typeId="discharger-power"/>
|
||||||
|
<channel id="emergency-power-mode" typeId="emergency-power-mode"/>
|
||||||
|
<channel id="consumption-active-power" typeId="consumption-active-power"/>
|
||||||
|
<channel id="consumption-active-power-l1" typeId="consumption-active-power-phase"/>
|
||||||
|
<channel id="consumption-active-power-l2" typeId="consumption-active-power-phase"/>
|
||||||
|
<channel id="consumption-active-power-l3" typeId="consumption-active-power-phase"/>
|
||||||
|
<channel id="consumption-max-active-power" typeId="consumption-max-active-power"/>
|
||||||
|
<channel id="production-max-active-power" typeId="production-max-active-power"/>
|
||||||
|
<channel id="production-active-power" typeId="production-active-power"/>
|
||||||
|
<channel id="export-to-grid-power" typeId="export-to-grid-power"/>
|
||||||
|
<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"/>
|
||||||
|
</channels>
|
||||||
|
|
||||||
|
<config-description>
|
||||||
|
<parameter name="hostname" type="text" required="true">
|
||||||
|
<context>network-address</context>
|
||||||
|
<label>Hostname</label>
|
||||||
|
<description>Hostname or IP address of the FENECON device, e.g. 192.168.1.11</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="password" type="text">
|
||||||
|
<context>password</context>
|
||||||
|
<label>Password</label>
|
||||||
|
<default>user</default>
|
||||||
|
<description>Password to access the device. The password for guest access is set by default.</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="port" type="integer" min="1">
|
||||||
|
<context>network-address</context>
|
||||||
|
<label>Port</label>
|
||||||
|
<description>Port of the FENECON device</description>
|
||||||
|
<default>8084</default>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="refreshInterval" type="integer" unit="s" min="1">
|
||||||
|
<label>Refresh Interval</label>
|
||||||
|
<description>Interval the device is polled in sec.</description>
|
||||||
|
<default>30</default>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
</config-description>
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
<!-- Channel Types -->
|
||||||
|
<channel-type id="state">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>System State</label>
|
||||||
|
<description>FENECON system state</description>
|
||||||
|
<category>Text</category>
|
||||||
|
<state readOnly="true" pattern="%s">
|
||||||
|
<options>
|
||||||
|
<option value="OK">Ok</option>
|
||||||
|
<option value="INFO">Info</option>
|
||||||
|
<option value="WARN">Warning</option>
|
||||||
|
<option value="FAULT">Fault</option>
|
||||||
|
</options>
|
||||||
|
</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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<category>Energy</category>
|
||||||
|
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="emergency-power-mode">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>Emergency Power Mode</label>
|
||||||
|
<description>Indicates if there is no power from the grid and the emergency power mode is on.</description>
|
||||||
|
<category>Switch</category>
|
||||||
|
<state readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<category>Energy</category>
|
||||||
|
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
</thing:thing-descriptions>
|
@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link FeneconBindingConstants}.
|
||||||
|
*
|
||||||
|
* @author Philipp Schneider - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class FeneconBindingConstantsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void checkAllAddressesAreListed() throws IllegalArgumentException, IllegalAccessException {
|
||||||
|
List<String> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(FeneconBindingConstants.ADDRESSES.size(), findAddresses.size());
|
||||||
|
assertTrue(findAddresses.containsAll(FeneconBindingConstants.ADDRESSES));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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;
|
||||||
|
import org.openhab.binding.fenecon.internal.FeneconBindingConstants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link BatteryPower}.
|
||||||
|
*
|
||||||
|
* @author Philipp Schneider - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BatteryPowerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCharging() {
|
||||||
|
BatteryPower batteryPower = BatteryPower
|
||||||
|
.get(new FeneconResponse(FeneconBindingConstants.ESS_DISCHARGE_POWER_ADDRESS, "comment", "-1777"));
|
||||||
|
assertEquals(1777, batteryPower.chargerPower());
|
||||||
|
assertEquals(0, batteryPower.dischargerPower());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDischarging() {
|
||||||
|
BatteryPower batteryPower = BatteryPower
|
||||||
|
.get(new FeneconResponse(FeneconBindingConstants.ESS_DISCHARGE_POWER_ADDRESS, "comment", "1777"));
|
||||||
|
assertEquals(1777, batteryPower.dischargerPower());
|
||||||
|
assertEquals(0, batteryPower.chargerPower());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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;
|
||||||
|
import org.openhab.binding.fenecon.internal.FeneconBindingConstants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link GridPower}.
|
||||||
|
*
|
||||||
|
* @author Philipp Schneider - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class GridPowerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSelling() {
|
||||||
|
GridPower gridValue = GridPower
|
||||||
|
.get(new FeneconResponse(FeneconBindingConstants.GRID_ACTIVE_POWER_ADDRESS, "comment", "-1777"));
|
||||||
|
assertEquals(1777, gridValue.sellTo());
|
||||||
|
assertEquals(0, gridValue.buyFrom());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBuying() {
|
||||||
|
GridPower gridValue = GridPower
|
||||||
|
.get(new FeneconResponse(FeneconBindingConstants.GRID_ACTIVE_POWER_ADDRESS, "comment", "1777"));
|
||||||
|
assertEquals(1777, gridValue.buyFrom());
|
||||||
|
assertEquals(0, gridValue.sellTo());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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;
|
||||||
|
import org.openhab.binding.fenecon.internal.FeneconBindingConstants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link State}.
|
||||||
|
*
|
||||||
|
* @author Philipp Schneider - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class StateTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStateOk() {
|
||||||
|
State state = State.get(
|
||||||
|
new FeneconResponse(FeneconBindingConstants.STATE_ADDRESS, "0:Ok, 1:Info, 2:Warning, 3:Fault", "0"));
|
||||||
|
assertEquals("Ok", state.state());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStateInfo() {
|
||||||
|
State state = State.get(
|
||||||
|
new FeneconResponse(FeneconBindingConstants.STATE_ADDRESS, "0:Ok, 1:Info, 2:Warning, 3:Fault", "1"));
|
||||||
|
assertEquals("Info", state.state());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStateWarning() {
|
||||||
|
State state = State.get(
|
||||||
|
new FeneconResponse(FeneconBindingConstants.STATE_ADDRESS, "0:Ok, 1:Info, 2:Warning, 3:Fault", "2"));
|
||||||
|
assertEquals("Warning", state.state());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStateFault() {
|
||||||
|
State state = State.get(
|
||||||
|
new FeneconResponse(FeneconBindingConstants.STATE_ADDRESS, "0:Ok, 1:Info, 2:Warning, 3:Fault", "3"));
|
||||||
|
assertEquals("Fault", state.state());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStateUnknown() {
|
||||||
|
State state = State.get(
|
||||||
|
new FeneconResponse(FeneconBindingConstants.STATE_ADDRESS, "0:Ok, 1:Info, 2:Warning, 3:Fault", "4"));
|
||||||
|
assertEquals("Unknown", state.state());
|
||||||
|
}
|
||||||
|
}
|
@ -145,6 +145,7 @@
|
|||||||
<module>org.openhab.binding.exec</module>
|
<module>org.openhab.binding.exec</module>
|
||||||
<module>org.openhab.binding.feed</module>
|
<module>org.openhab.binding.feed</module>
|
||||||
<module>org.openhab.binding.feican</module>
|
<module>org.openhab.binding.feican</module>
|
||||||
|
<module>org.openhab.binding.fenecon</module>
|
||||||
<module>org.openhab.binding.fineoffsetweatherstation</module>
|
<module>org.openhab.binding.fineoffsetweatherstation</module>
|
||||||
<module>org.openhab.binding.flicbutton</module>
|
<module>org.openhab.binding.flicbutton</module>
|
||||||
<module>org.openhab.binding.fmiweather</module>
|
<module>org.openhab.binding.fmiweather</module>
|
||||||
|
Loading…
Reference in New Issue
Block a user