]> git.basschouten.com Git - openhab-addons.git/commitdiff
[solarwatt] Initial contribution for solarwatt energy manager (#10091)
authorSven Carstens <s.carstens@gmx.de>
Mon, 21 Jun 2021 16:21:50 +0000 (18:21 +0200)
committerGitHub <noreply@github.com>
Mon, 21 Jun 2021 16:21:50 +0000 (18:21 +0200)
* [ADD] First version which was tested with my local setup.

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
Signed-off-by: Sven Carstens <sven.carstens@aoe.com>
* [ADD] Refactoring, add calculated value for direct self consumption and prepare for more.

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [ADD] Add chanel description to README and make some channels advanced.

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [MOD] Remove wrong colon in channel types.

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [MOD] Put colon at the right place in channel types.

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [MOD] Fix spelling of PVPlant in constants and things.

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [MOD] Separate channelName from energy manager tagName.

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [MOD] Reduce loglevels and fix findings from code-analysis.

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [MOD] Move all custom calculations to the handler instances, remove custom tracking of child handlers, fix wrong calculation of the "direct consumed" values.

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [MOD] Improve README and remove unnecessary Nullable and NotNull annotations.

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [MOD] Change wrong ItemType for power items in README

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [MOD] Change modeConverter to Switch

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [MOD] Relay stateDevice to Thing status, anything but "OK" means ThingStatus.OFFLINE

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [MOD] Remove stateDevice from items as it is represented by the device status.

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [MOD] Improve README and remove unnecessary logging.

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [MOD] Improve README.

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [MOD] Improve README.

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [MOD] Allow child things to be configured by hand with a guid.

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [MOD] Allow child things to be configured by hand with a guid.

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [MOD] Improve README and move things config to separate config.xml

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [MOD] Improve README and remove all trace/debug logging.

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
* [MOD] Use real channel timestamp as the refresh trigger.

Signed-off-by: Sven Carstens <s.carstens@gmx.de>
Co-authored-by: Sven Carstens <sven.carstens@aoe.com>
49 files changed:
CODEOWNERS
bom/openhab-addons/pom.xml
bundles/org.openhab.binding.solarwatt/NOTICE [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/README.md [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/pom.xml [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/feature/feature.xml [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/SolarwattBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/SolarwattHandlerFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/channel/SolarwattChannelTypeProvider.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/configuration/SolarwattBridgeConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/configuration/SolarwattThingConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/discovery/SolarwattDevicesDiscoveryService.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/EnergyManagerCollection.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/SolarwattChannel.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/SolarwattTag.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/converter/JsonStateConverter.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/dto/DeviceDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/dto/EnergyManagerDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/dto/TagValueDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/BatteryConverter.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/Device.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/EVStation.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/EnergyManager.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/Forecast.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/GridFlow.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/Inverter.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/KebaEv.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/Location.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/MyReserve.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/MyReserveInverter.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/MyReservePowerMeter.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/PVPlant.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/PowerMeter.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/ProfileApp.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/S0Counter.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/ScheduleApp.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/SimpleSwitcher.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/SmartEnergyManagement.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/SunSpecInverter.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/exception/SolarwattConnectionException.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/factory/EnergyManagerDevicesFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/handler/EnergyManagerConnector.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/handler/EnergyManagerHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/handler/LocationHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/handler/SimpleDeviceHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/resources/OH-INF/binding/binding.xml [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/resources/OH-INF/config/config.xml [new file with mode: 0644]
bundles/org.openhab.binding.solarwatt/src/main/resources/OH-INF/thing/thing-types.xml [new file with mode: 0644]
bundles/pom.xml

index 9b767694b01ba5dd5f25c91344684e232a3fc8ab..300354bcf8e56a98829d81fbd6489339cb344b7c 100644 (file)
 /bundles/org.openhab.binding.snmp/ @openhab/add-ons-maintainers
 /bundles/org.openhab.binding.solaredge/ @alexf2015
 /bundles/org.openhab.binding.solarlog/ @johannrichard
+/bundles/org.openhab.binding.solarwatt/ @sven-carstens
 /bundles/org.openhab.binding.somfymylink/ @loungeflyz
 /bundles/org.openhab.binding.somfytahoma/ @octa22
 /bundles/org.openhab.binding.sonos/ @kgoderis @lolodomo
index b46effe12bb9f585d1ecdf4a49e8b0655526a9aa..518ac37fa16265decc5a5a65fc165e78f4f9f1f5 100644 (file)
       <artifactId>org.openhab.binding.solarlog</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.openhab.addons.bundles</groupId>
+      <artifactId>org.openhab.binding.solarwatt</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>org.openhab.addons.bundles</groupId>
       <artifactId>org.openhab.binding.somfymylink</artifactId>
diff --git a/bundles/org.openhab.binding.solarwatt/NOTICE b/bundles/org.openhab.binding.solarwatt/NOTICE
new file mode 100644 (file)
index 0000000..38d625e
--- /dev/null
@@ -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
diff --git a/bundles/org.openhab.binding.solarwatt/README.md b/bundles/org.openhab.binding.solarwatt/README.md
new file mode 100644 (file)
index 0000000..668117e
--- /dev/null
@@ -0,0 +1,252 @@
+# Solarwatt Binding
+
+Binding to query a [solarwatt](https://www.solarwatt.de/) [energy manager](https://www.solarwatt.de/energie-management/energymanager) and read the values of all attached devices.
+
+All supported values and devices were discovered while playing with my own energy manager.
+
+## Supported Things
+
+| Thing Type ID | Devices |
+|------|---------------|
+| energymanager | EnergyManager itself. |
+| location | Location part of the EnergyManager. | 
+| pvplant | Power producing part of the EnergyManager. |
+| gridflow | Grid interaction part of the EnergyManager. |
+| inverter | inverter producing AC current; e.g. MyReserve, Fronius |
+| batteryconverter | battery storage systems; e.g. MyReserve |
+| powermeter | powermeters; e.g. S0BusCounter, MyReserve |
+| evstation | electric-vehicle charging station; e.g. Keba Wallbox |
+
+## Discovery
+
+You have to enter the hostname or ip-address of the energymanager itself.
+The attached devices and supported channels are discovered automatically.
+
+## Thing Configuration
+
+### EnergyManager
+
+| Property | Default | Required | Description |
+|----------|---------|----------|-------------|
+| hostname | None | Yes | hostname or ip-address of the energy manager. |
+| refresh | 30 | No | Refresh interval in seconds for the current values of the channels. |
+| rescan | 5 | No | Rescan interval in minutes for the redetection of channgels and things. |
+
+### Child Things
+
+| Property | Default | Required | Description |
+|----------|---------|----------|-------------|
+| guid | None | Yes | Guid of the device as used by the solarwatt energymanager. |
+
+## Channels
+
+### EnergyManager
+
+| Channel Type ID | Item Type | Description |
+|-----------------|-----------|-------------|
+|timestamp | Number | Milliseconds since the epoch set to the last NTP time sync |
+|datetime | DateTime | Date and time of the last NTP time sync in the timezone of the energy manager |
+|idTimezone | String | Timezone the energy manager is running in. All  timestamps are milliseconds since the epoch within this timezone |
+|fractionCPULoadTotal | Number:Dimensionless | Total CPU load in % |
+|fractionCPULoadUser | Number:Dimensionless | Userspace CPU load in % |
+|fractionCPULoadKernel | Number:Dimensionless | Kernelspace CPU load in % |
+|fractionCPULoadAverageLastMinute | Number:Dimensionless | Average 1 minute CPU load in % |
+|fractionCPULoadAverageLastFiveMinutes | Number:Dimensionless | Average 5 minute CPU load in % |
+|fractionCPULoadAverageLastFifteenMinutes | Number:Dimensionless | Average 15 minute CPU load in % |
+
+### PVPlant
+
+| Channel Type ID | Item Type | Description |
+|-----------------|-----------|-------------|
+| powerACOut | Number:Power | Energy produced by the PV in watts |
+| workACOut | Number:Energy | Energy produced by the PV in watt hours |
+
+### Location
+
+| Channel Type ID | Item Type | Description |
+|-----------------|-----------|-------------|
+| powerBuffered | Number:Power | Power flow into the storage system
+| powerSelfConsumed | Number:Power | Power consumed direct from PV plus energy stored
+| powerSelfSupplied | Number:Power | Power consumed direct from PV plus energy consumed from storage
+| powerConsumedFromGrid | Number:Power | Power consumed from the grid  
+| powerConsumedFromStorage | Number:Power | Power consumed from storage
+| powerConsumedUnmetered | Number:Power | Power consumed in the inner side (outer consumers are subtracted)
+| powerConsumed | Number:Power | Total power consumed. All inner and outer consumers.
+| powerDirectConsumed | Number:Power | Power consumed directly from PV without buffering
+| powerProduced | Number:Power | Power produced by the PV
+| powerOut | Number:Power | Power delivered to the grid  
+| powerDirectConsumed | Number:Power | Power consumed directly without energy put into storage or taken from storage
+| workBuffered | Number:Energy | Energy flow into the storage system
+| workSelfConsumed | Number:Energy | Energy consumed direct from PV plus energy stored
+| workSelfSupplied | Number:Energy | Energy consumed direct from PV plus energy consumed from storage
+| workConsumedFromGrid | Number:Energy | Energy consumed from the grid  
+| workConsumedFromStorage | Number:Energy | Energy consumed from storage
+| workConsumedUnmetered | Number:Energy | Energy consumed in the inner side (outer consumers are subtracted)
+| workConsumed | Number:Energy | Total energy consumed. All inner and outer consumers.
+| workDirectConsumed | Number:Energy | Energy consumed directly from PV without buffering
+| workProduced | Number:Energy | Energy produced by the PV
+| workOut | Number:Energy | Energy delivered to the grid  
+| workDirectConsumed | Number:Energy | Energy consumed directly without energy put into storage or taken from storage
+
+### PowerMeter, S0Counter, MyReservePowerMeter
+
+| Channel Type ID | Item Type | Description |
+|-----------------|-----------|-------------|
+| channelDirectionMetering | String | Representing which energy flow directions are metered. One off *IN*, *OUT*, *BIDIRECTIONAL* 
+| powerIn | Number:Power | Power metered flowing into the consumer
+| powerOut | Number:Power | Power metered flowing out of the producer  
+| workIn | Number:Energy | Energy metered flowing into the consumer
+| workOut | Number:Energy | Energy metered flowing out of the producer  
+| consumptionEnergySum | Number:Energy | Total energy in watt hours 
+
+### Inverter, MyReserveInverter, SunSpecInverter
+
+| Channel Type ID | Item Type | Description |
+|-----------------|-----------|-------------|
+| powerACOutMax | Number:Power | Maximum power production 
+| powerACOutLimit | Number:Power | Limit of power production
+| powerACOut | Number:Power | Power delivered by the inverter
+| workACOut | Number:Energy | Energy delivered by the inverter
+| powerInstallledPeak | Number:Power | Technical peak power available 
+
+### BatteryConverter, MyReserve
+
+All of *Inverter* plus
+
+| Channel Type ID | Item Type | Description |
+|-----------------|-----------|-------------|
+| powerACIn | Number:Power | Power fed into battery
+| workACIn | Number:Energy | Energy fed into battery
+| stateOfCharge | Number | Charging state of battery in percent
+| stateOfHealth | Number | Internal health metric in percent
+| temperatureBattery | Number:Temperature | Temperature of the battery in celsius
+| modeConverter | Switch | Current mode of converter. *ON* or *OFF*
+| voltageBatteryCellMin | Number:Voltage | minimum voltage of all batteries
+| voltageBatteryCellMean | Number:Voltage | mean voltage of all batteries
+| voltageBatteryCellMax | Number:Voltage | maximum voltage of all batteries
+
+### EVStation, KebaEv
+
+| Channel Type ID | Item Type | Description |
+|-----------------|-----------|-------------|
+| powerACIn | Number:Power | Power consumed by the charger
+| workACIn | Number:Energy | Energy consumed by the charger
+| workACInSession | Number:Energy | Work consumed during current/last charging session
+| modeStation | String | Current mode of the charger. One off *STANDBY*, *CHARGING*, *OFF*
+| connectivityStatus | String | Current state of the charging connection. One off *ONLINE* or *OFFLINE*
+
+### GridFlow
+
+| Channel Type ID | Item Type | Description |
+|-----------------|-----------|-------------|
+| feedInLimit | Number:Dimensionless | Current derating setting in percent
+
+## Example
+
+demo.things:
+
+```
+Bridge solarwatt:energymanager:56f4ac2fa2 [hostname="192.168.0.64", refresh=30, rescan=5]
+// the individual things configured with their energy manager guid
+Thing solarwatt:batteryconverter:56f4ac2fa2:5c7d5929-8fa4-42c5-8737-48bef77b61f5 [guid="5c7d5929-8fa4-42c5-8737-48bef77b61f5"] (solarwatt:energymanager:56f4ac2fa2)
+Thing solarwatt:gridflow:56f4ac2fa2:urn-kiwigrid-gridflow-ERC05-000008007 [guid="urn:kiwigrid:gridflow:ERC05-000008007"] (solarwatt:energymanager:56f4ac2fa2)
+Thing solarwatt:evstation:56f4ac2fa2:urn-keba-evstation-20652876 [guid="urn:keba:evstation:2065287"] (solarwatt:energymanager:56f4ac2fa2)
+```
+
+demo.items:
+
+```
+// Location DeviceClass com.kiwigrid.devices.location.Location Guid b4e4978b96404e61977bfacd3eab299d
+Number:Power Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_PowerBuffered "PowerBuffered [%.2f W]" <energy> ["Measurement", "Power"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:powerBuffered"}
+Number:Power Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_PowerBufferedFromGrid "PowerBufferedFromGrid [%.2f W]" <energy> ["Measurement", "Power"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:powerBufferedFromGrid"}
+Number:Power Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_PowerBufferedFromProducers "PowerBufferedFromProducers [%.2f W]" <energy> ["Measurement", "Power"]  {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:powerBufferedFromProducers"}
+Number:Power Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_PowerConsumed "PowerConsumed [%.2f W]" <energy> ["Measurement", "Power"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:powerConsumed"}
+Number:Power Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_PowerConsumedUnmetered "PowerConsumedUnmetered [%.2f W]" <energy> ["Measurement", "Power"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:PowerConsumedUnmetered"}
+Number:Power Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_PowerDirectConsumed "PowerDirectConsumed [%.2f W]" <energy> ["Measurement", "Power"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:powerDirectConsumed"}
+Number:Power Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_PowerConsumedFromGrid "PowerConsumedFromGrid [%.2f W]" <energy> ["Measurement", "Power"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:powerConsumedFromGrid"}
+Number:Power Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_PowerConsumedFromStorage "PowerConsumedFromStorage [%.2f W]" <energy> ["Measurement", "Power"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:powerConsumedFromStorage"}
+Number:Power Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_PowerConsumedFromProducers "PowerConsumedFromProducers [%.2f W]" <energy> ["Measurement", "Power"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:powerConsumedFromProducers"}
+Number:Power Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_PowerIn "PowerIn [%.2f W]" <energy> ["Measurement", "Power"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:powerIn"}
+Number:Power Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_PowerProduced "PowerProduced [%.2f W]" <energy> ["Measurement", "Power"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:powerProduced"}
+Number:Power Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_PowerOut "PowerOut [%.2f W]" <energy> ["Measurement", "Power"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:powerOut"}
+Number:Power Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_PowerOutFromProducers "PowerOutFromProducers [%.2f W]" <energy> ["Measurement", "Power"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:powerOutFromProducers"}
+Number:Power Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_PowerOutFromStorage "PowerOutFromStorage [%.2f W]" <energy> ["Measurement", "Power"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:powerOutFromStorage"}
+Number:Power Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_PowerReleased "PowerReleased [%.2f W]" <energy> ["Measurement", "Power"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:powerReleased"}
+Number:Power Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_PowerSelfConsumed "PowerSelfConsumed [%.2f W]" <energy> ["Measurement", "Power"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:powerSelfConsumed"}
+Number:Power Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_PowerSelfSupplied "PowerSelfSupplied [%.2f W]" <energy> ["Measurement", "Power"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:powerSelfSupplied"}
+Number:Energy Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_WorkBuffered "WorkBuffered [%.2f Wh]" <energy> ["Measurement", "Energy"]  {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:workBuffered"}
+Number:Energy Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_WorkBufferedFromGrid "WorkBufferedFromGrid [%.2f Wh]" <energy> ["Measurement", "Energy"]  {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:workBufferedFromGrid"}
+Number:Energy Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_WorkBufferedFromProducers "WorkBufferedFromProducers [%.2f Wh]" <energy> [""]  {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:workBufferedFromProducers"}
+Number:Energy Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_WorkConsumed "WorkConsumed [%.2f Wh]" <energy> ["Measurement", "Energy"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:workConsumed"}
+Number:Energy Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_WorkConsumedUnmetered "WorkConsumedUnmetered [%.2f Wh]" <energy> ["Measurement", "Energy"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:WorkConsumedUnmetered"}
+Number:Energy Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_WorkDirectConsumed "WorkDirectConsumed [%.2f Wh]" <energy> ["Measurement", "Energy"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:workDirectConsumed"}
+Number:Energy Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_WorkConsumedFromGrid "WorkConsumedFromGrid [%.2f Wh]" <energy> ["Measurement", "Energy"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:workConsumedFromGrid"}
+Number:Energy Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_WorkConsumedFromStorage "WorkConsumedFromStorage [%.2f Wh]" <energy> ["Measurement", "Energy"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:workConsumedFromStorage"}
+Number:Energy Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_WorkConsumedFromProducers "WorkConsumedFromProducers [%.2f Wh]" <energy> ["Measurement", "Energy"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:workConsumedFromProducers"}
+Number:Energy Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_WorkIn "WorkIn [%.2f Wh]" <energy> ["Measurement", "Energy"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:workIn"}
+Number:Energy Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_WorkProduced "WorkProduced [%.2f Wh]" <energy> ["Measurement", "Energy"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:workProduced"}
+Number:Energy Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_WorkOut "WorkOut [%.2f Wh]" <energy> ["Measurement", "Energy"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:workOut"}
+Number:Energy Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_WorkOutFromProducers "WorkOutFromProducers [%.2f Wh]" <energy> ["Measurement", "Energy"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:workOutFromProducers"}
+Number:Energy Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_WorkOutFromStorage "WorkOutFromStorage [%.2f Wh]" <energy> ["Measurement", "Energy"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:workOutFromStorage"}
+Number:Energy Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_WorkReleased "WorkReleased [%.2f Wh]" <energy> ["Measurement", "Energy"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:workReleased"}
+Number:Energy Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_WorkSelfConsumed "WorkSelfConsumed [%.2f Wh]" <energy> ["Measurement", "Energy"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:workSelfConsumed"}
+Number:Energy Solarwatt_Location_b4e4978b96404e61977bfacd3eab299d_WorkSelfSupplied "WorkSelfSupplied [%.2f Wh]" <energy> ["Measurement", "Energy"] {channel="solarwatt:location:56f4ac2fa2:b4e4978b-9640-4e61-977b-facd3eab299d:workSelfSupplied"}
+
+// Inverter Fronius com.kiwigrid.devices.inverter.Inverter
+Number:Power Solarwatt_Inverter_UrnSunspecFroniusInverter31414368_PowerACOut "PowerACOut [%.2f W]" <energy> ["Measurement", "Power"]  {channel="solarwatt:inverter:56f4ac2fa2:urn-sunspec-fronius-inverter-31414368:powerACOut"}
+Number:Power Solarwatt_Inverter_UrnSunspecFroniusInverter31414368_PowerACOutLimit "PowerACOutLimit [%.2f W]" <energy> ["Point", "Power"]  {channel="solarwatt:inverter:56f4ac2fa2:urn-sunspec-fronius-inverter-31414368:powerACOutLimit"}
+Number:Energy Solarwatt_Inverter_UrnSunspecFroniusInverter31414368_WorkACOut "WorkACOut [%.2f Wh]" <energy> ["Measurement", "Energy"]  {channel="solarwatt:inverter:56f4ac2fa2:urn-sunspec-fronius-inverter-31414368:workACOut"}
+
+// MyReserve BatteryInverter com.kiwigrid.devices.bat…verter.BatteryConverter
+Switch Solarwatt_BatteryInverter_5c7d59298fa442c5873748bef77b61f5_ModeConverter "ModeConverter [%s]" <switch> ["Switch"]  {channel="solarwatt:batteryconverter:56f4ac2fa2:5c7d5929-8fa4-42c5-8737-48bef77b61f5:modeConverter"}
+Number Solarwatt_BatteryInverter_5c7d59298fa442c5873748bef77b61f5_StateOfCharge "StateOfCharge [%.2f %%]" <status> ["Status"]  {channel="solarwatt:batteryconverter:56f4ac2fa2:5c7d5929-8fa4-42c5-8737-48bef77b61f5:stateOfCharge"}
+Number Solarwatt_BatteryInverter_5c7d59298fa442c5873748bef77b61f5_StateOfHealth "StateOfHealth [%.2f %%]" <status> ["Status"]  {channel="solarwatt:batteryconverter:56f4ac2fa2:5c7d5929-8fa4-42c5-8737-48bef77b61f5:stateOfHealth"}
+Number:Temperature Solarwatt_BatteryInverter_5c7d59298fa442c5873748bef77b61f5_TemperatureBattery "TemperatureBattery [%.1f Â°C]" <temperature> ["Measurement", "Temperature"]  {channel="solarwatt:batteryconverter:56f4ac2fa2:5c7d5929-8fa4-42c5-8737-48bef77b61f5:temperatureBattery"}
+Number:Power Solarwatt_BatteryInverter_5c7d59298fa442c5873748bef77b61f5_PowerACIn "PowerACIn [%.2f W]" <energy> ["Measurement", "Power"]  {channel="solarwatt:batteryconverter:56f4ac2fa2:5c7d5929-8fa4-42c5-8737-48bef77b61f5:powerACIn"}
+Number:Power Solarwatt_BatteryInverter_5c7d59298fa442c5873748bef77b61f5_PowerACOut "PowerACOut [%.2f W]" <energy> ["Measurement", "Power"]  {channel="solarwatt:batteryconverter:56f4ac2fa2:5c7d5929-8fa4-42c5-8737-48bef77b61f5:powerACOut"}
+Number:Energy Solarwatt_BatteryInverter_5c7d59298fa442c5873748bef77b61f5_WorkACIn "WorkACIn [%.2f Wh]" <energy> ["Measurement", "Energy"]  {channel="solarwatt:batteryconverter:56f4ac2fa2:5c7d5929-8fa4-42c5-8737-48bef77b61f5:workACIn"}
+Number:Energy Solarwatt_BatteryInverter_5c7d59298fa442c5873748bef77b61f5_WorkACOut "WorkACOut [%.2f Wh]" <energy> ["Measurement", "Energy"]  {channel="solarwatt:batteryconverter:56f4ac2fa2:5c7d5929-8fa4-42c5-8737-48bef77b61f5:workACOut"}
+
+// S0Bus Meter com.kiwigrid.devices.powermeter.PowerMeter / com.kiwigrid.devices.s0counter.S0Counter
+String Solarwatt_Meter_46bb4ee8a9744ecea11ef43c68263ae9_DirectionMetering "DirectionMetering [%s]" <status> ["Status"]  {channel="solarwatt:powermeter:56f4ac2fa2:46bb4ee8-a974-4ece-a11e-f43c68263ae9:directionMetering"}
+Number:Power Solarwatt_Meter_46bb4ee8a9744ecea11ef43c68263ae9_PowerIn "PowerIn [%.2f W]" <energy> ["Measurement", "Power"] {channel="solarwatt:powermeter:56f4ac2fa2:46bb4ee8-a974-4ece-a11e-f43c68263ae9:powerIn"}
+Number:Energy Solarwatt_Meter_46bb4ee8a9744ecea11ef43c68263ae9_WorkIn "WorkIn [%.2f Wh]" <energy> ["Measurement", "Energy"] {channel="solarwatt:powermeter:56f4ac2fa2:46bb4ee8-a974-4ece-a11e-f43c68263ae9:workIn"}
+Number:Energy Solarwatt_Meter_46bb4ee8a9744ecea11ef43c68263ae9_ConsumptionEnergySum "ConsumptionEnergySum [%.2f Wh]" <energy> ["Measurement", "Energy"] {channel="solarwatt:powermeter:56f4ac2fa2:46bb4ee8-a974-4ece-a11e-f43c68263ae9:consumptionEnergySum"}
+
+// MyReservePowermeter Meter com.kiwigrid.devices.powermeter.PowerMeter
+String Solarwatt_Meter_2c5d089b98854f40ba8a3303bfb53e36_DirectionMetering "DirectionMetering [%s]" <status> ["Status"]  {channel="solarwatt:powermeter:56f4ac2fa2:2c5d089b-9885-4f40-ba8a-3303bfb53e36:directionMetering"}
+Number:Power Solarwatt_Meter_2c5d089b98854f40ba8a3303bfb53e36_PowerIn "PowerIn [%.2f W]" <energy> ["Measurement", "Power"] {channel="solarwatt:powermeter:56f4ac2fa2:2c5d089b-9885-4f40-ba8a-3303bfb53e36:powerIn"}
+Number:Power Solarwatt_Meter_2c5d089b98854f40ba8a3303bfb53e36_PowerOut "PowerOut [%.2f W]" <energy> ["Measurement", "Power"] {channel="solarwatt:powermeter:56f4ac2fa2:2c5d089b-9885-4f40-ba8a-3303bfb53e36:powerOut"}
+Number:Energy Solarwatt_Meter_2c5d089b98854f40ba8a3303bfb53e36_WorkIn "WorkIn [%.2f Wh]" <energy> ["Measurement", "Energy"] {channel="solarwatt:powermeter:56f4ac2fa2:2c5d089b-9885-4f40-ba8a-3303bfb53e36:workIn"}
+Number:Energy Solarwatt_Meter_2c5d089b98854f40ba8a3303bfb53e36_WorkOut "WorkOut [%.2f Wh]" <energy> ["Measurement", "Energy"] {channel="solarwatt:powermeter:56f4ac2fa2:2c5d089b-9885-4f40-ba8a-3303bfb53e36:workOut"}
+
+// Inverter MyReserve com.kiwigrid.devices.inverter.Inverter
+Number:Power Solarwatt_Inverter_4af659938b1149408a77ff87556389f3_PowerACOut "PowerACOut [%.2f W]" <energy> ["Measurement", "Power"]  {channel="solarwatt:inverter:56f4ac2fa2:4af65993-8b11-4940-8a77-ff87556389f3:powerACOut"}
+Number:Energy Solarwatt_Inverter_4af659938b1149408a77ff87556389f3_WorkACOut "WorkACOut [%.2f Wh]" <energy> ["Measurement", "Energy"]  {channel="solarwatt:inverter:56f4ac2fa2:4af65993-8b11-4940-8a77-ff87556389f3:workACOut"}
+
+// EVStation Keba com.kiwigrid.devices.evstation.EVStation
+String Solarwatt_EVStation_UrnKebaEvstation20652876_ModeStation "ModeStation [%s]" <status> ["Status"]  {channel="solarwatt:evstation:56f4ac2fa2:urn-keba-evstation-20652876:modeStation"}
+String Solarwatt_EVStation_UrnKebaEvstation20652876_ConnectivityStatus "ConnectivityStatus [%s]" <status> ["Status"]  {channel="solarwatt:evstation:56f4ac2fa2:urn-keba-evstation-20652876:connectivityStatus"}
+Number:Power Solarwatt_EVStation_UrnKebaEvstation20652876_PowerACIn "PowerACIn [%.2f W]" <energy> ["Measurement", "Power"]  {channel="solarwatt:evstation:56f4ac2fa2:urn-keba-evstation-20652876:powerACIn"}
+Number:Energy Solarwatt_EVStation_UrnKebaEvstation20652876_WorkACIn "WorkACIn [%.2f Wh]" <energy> ["Measurement", "Energy"]  {channel="solarwatt:evstation:56f4ac2fa2:urn-keba-evstation-20652876:workACIn"}
+Number:Energy Solarwatt_EVStation_UrnKebaEvstation20652876_WorkACInSession "WorkACInSession [%.2f Wh]" <energy> ["Measurement", "Energy"]  {channel="solarwatt:evstation:56f4ac2fa2:urn-keba-evstation-20652876:workACInSession"}
+
+// PVPlant com.kiwigrid.devices.pvplant.PVPlant
+Number:Power Solarwatt_PVPlant_4575c19cfa0a4f21839a5db2ae06b4d_PowerACOut "PowerACOut [%.2f W]" <energy> ["Measurement", "Power"]  {channel="solarwatt:pvplant:56f4ac2fa2:4575c19c-fa0a-4f21-839a-5db2ae06b4dc:powerACOut"}
+Number:Energy Solarwatt_PVPlant_4575c19cfa0a4f21839a5db2ae06b4d_WorkACOut "WorkACOut [%.2f Wh]" <energy> ["Measurement", "Energy"]  {channel="solarwatt:pvplant:56f4ac2fa2:4575c19c-fa0a-4f21-839a-5db2ae06b4dc:workACOut"}
+
+// Manager com.kiwigrid.devices.em.EnergyManager
+String Solarwatt_Manager_ERC05000008007_IdFirmware "IdFirmware [%s]" <status> ["Status"]  {channel="solarwatt:energymanager:56f4ac2fa2:idFirmware"}
+Number Solarwatt_Manager_ERC05000008007_Timestamp "Timestamp [%d]" <time> ["Status", "Timestamp"]  {channel="solarwatt:energymanager:56f4ac2fa2:timestamp"}
+DateTime Solarwatt_Manager_ERC05000008007_Datetime  "Update [%1$tH:%1$tM:%1$tS]" <time> ["Status", "Timestamp"] {channel="solarwatt:energymanager:56f4ac2fa2:datetime"}
+Number Solarwatt_Manager_ERC05000008007_FractionCPULoadTotal "FractionCPULoadUser [%.2f]" <status> ["Measurement"]  {channel="solarwatt:energymanager:56f4ac2fa2:fractionCPULoadTotal"}
+Number Solarwatt_Manager_ERC05000008007_FractionCPULoadUser "FractionCPULoadUser [%.2f]" <status> ["Measurement"]  {channel="solarwatt:energymanager:56f4ac2fa2:fractionCPULoadUser"}
+Number Solarwatt_Manager_ERC05000008007_FractionCPULoadKernel "FractionCPULoadKernel [%.2f]" <status> ["Measurement"]  {channel="solarwatt:energymanager:56f4ac2fa2:fractionCPULoadKernel"}
+Number Solarwatt_Manager_ERC05000008007_FractionCPULoadAverageLastMinute "FractionCPULoadAverageLastMinute [%.2f]" <status> ["Measurement"]  {channel="solarwatt:energymanager:56f4ac2fa2:fractionCPULoadAverageLastMinute"}
+Number Solarwatt_Manager_ERC05000008007_FractionCPULoadAverageLastFiveMinutes "FractionCPULoadAverageLastFiveMinutes [%.2f]" <status> ["Measurement"]  {channel="solarwatt:energymanager:56f4ac2fa2:fractionCPULoadAverageLastFiveMinutes"}
+Number Solarwatt_Manager_ERC05000008007_FractionCPULoadAverageLastFifteenMinutes "FractionCPULoadAverageLastFifteenMinutes [%.2f]" <status> ["Measurement"]  {channel="solarwatt:energymanager:56f4ac2fa2:fractionCPULoadAverageLastFifteenMinutes"}
+
+// Gridflow com.kiwigrid.kiwiapp.gridflow.GridFlow
+Number Solarwatt_Gridflow_UrnKiwigridGridflowERC05000008007_CurrentLimit "CurrentLimit [%d A]" <energy> ["Point"]  {channel="solarwatt:gridflow:56f4ac2fa2:urn-kiwigrid-gridflow-ERC05-000008007:currentLimit"}
+Number Solarwatt_Gridflow_UrnKiwigridGridflowERC05000008007_FeedInLimit "FeedInLimit [%d %%]" <status> ["Point"]  {channel="solarwatt:gridflow:56f4ac2fa2:urn-kiwigrid-gridflow-ERC05-000008007:feedInLimit"}
+```
diff --git a/bundles/org.openhab.binding.solarwatt/pom.xml b/bundles/org.openhab.binding.solarwatt/pom.xml
new file mode 100644 (file)
index 0000000..befdce9
--- /dev/null
@@ -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 http://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>3.1.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>org.openhab.binding.solarwatt</artifactId>
+
+  <name>openHAB Add-ons :: Bundles :: Solarwatt Binding</name>
+
+</project>
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/feature/feature.xml b/bundles/org.openhab.binding.solarwatt/src/main/feature/feature.xml
new file mode 100644 (file)
index 0000000..9eea0c8
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="org.openhab.binding.solarwatt-${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-solarwatt" description="Solarwatt Binding" version="${project.version}">
+               <feature>openhab-runtime-base</feature>
+               <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.solarwatt/${project.version}</bundle>
+       </feature>
+</features>
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/SolarwattBindingConstants.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/SolarwattBindingConstants.java
new file mode 100644 (file)
index 0000000..2545dbf
--- /dev/null
@@ -0,0 +1,122 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarwatt.internal.domain.SolarwattTag;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link SolarwattBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class SolarwattBindingConstants {
+
+    private SolarwattBindingConstants() {
+    }
+
+    public static final String BINDING_ID = "solarwatt";
+
+    // List of all Thing Type UIDs
+    public static final ThingTypeUID THING_TYPE_ENERGY_MANAGER = new ThingTypeUID(BINDING_ID, "energymanager");
+    public static final ThingTypeUID THING_TYPE_INVERTER = new ThingTypeUID(BINDING_ID, "inverter");
+    public static final ThingTypeUID THING_TYPE_LOCATION = new ThingTypeUID(BINDING_ID, "location");
+    public static final ThingTypeUID THING_TYPE_BATTERYCONVERTER = new ThingTypeUID(BINDING_ID, "batteryconverter");
+    public static final ThingTypeUID THING_TYPE_POWERMETER = new ThingTypeUID(BINDING_ID, "powermeter");
+    public static final ThingTypeUID THING_TYPE_EVSTATION = new ThingTypeUID(BINDING_ID, "evstation");
+    public static final ThingTypeUID THING_TYPE_PVPLANT = new ThingTypeUID(BINDING_ID, "pvplant");
+    public static final ThingTypeUID THING_TYPE_GRIDFLOW = new ThingTypeUID(BINDING_ID, "gridflow");
+
+    public static final String PROPERTY_ID_NAME = "IdName";
+    public static final String PROPERTY_ID_FIRMWARE = "IdFirmware";
+    public static final String PROPERTY_ID_MANUFACTURER = "IdManufacturer";
+
+    // List of all Channel ids, taken from the tagNames
+    public static final SolarwattTag CHANNEL_WORK_AC_OUT = new SolarwattTag("WorkACOut");
+    public static final SolarwattTag CHANNEL_WORK_AC_IN = new SolarwattTag("WorkACIn");
+    public static final SolarwattTag CHANNEL_WORK_AC_IN_SESSION = new SolarwattTag("WorkACInSession");
+    public static final SolarwattTag CHANNEL_POWER_INSTALLED_PEAK = new SolarwattTag("PowerInstalledPeak");
+    public static final SolarwattTag CHANNEL_POWER_AC_OUT_MAX = new SolarwattTag("PowerACOutMax");
+    public static final SolarwattTag CHANNEL_POWER_AC_OUT = new SolarwattTag("PowerACOut");
+    public static final SolarwattTag CHANNEL_POWER_AC_IN = new SolarwattTag("PowerACIn");
+    public static final SolarwattTag CHANNEL_POWER_AC_OUT_LIMIT = new SolarwattTag("PowerACOutLimit");
+    public static final SolarwattTag CHANNEL_DIRECTION_METERING = new SolarwattTag("DirectionMetering");
+    public static final SolarwattTag CHANNEL_POWER_IN = new SolarwattTag("PowerIn");
+    public static final SolarwattTag CHANNEL_POWER_OUT = new SolarwattTag("PowerOut");
+    public static final SolarwattTag CHANNEL_WORK_IN = new SolarwattTag("WorkIn");
+    public static final SolarwattTag CHANNEL_WORK_OUT = new SolarwattTag("WorkOut");
+    public static final SolarwattTag CHANNEL_CONSUMPTION_ENERGY_SUM = new SolarwattTag("ConsumptionEnergySum");
+    public static final SolarwattTag CHANNEL_MODE_STATION = new SolarwattTag("ModeStation");
+    public static final SolarwattTag CHANNEL_CONNECTIVITY_STATUS = new SolarwattTag("ConnectivityStatus");
+    public static final SolarwattTag CHANNEL_TIMESTAMP = new SolarwattTag("Timestamp");
+    public static final SolarwattTag CHANNEL_DATETIME = new SolarwattTag("Datetime");
+    public static final SolarwattTag CHANNEL_IDTIMEZONE = new SolarwattTag("IdTimezone");
+    public static final SolarwattTag CHANNEL_FRACTION_CPU_LOAD_TOTAL = new SolarwattTag("FractionCPULoadTotal");
+    public static final SolarwattTag CHANNEL_FRACTION_CPU_LOAD_USER = new SolarwattTag("FractionCPULoadUser");
+    public static final SolarwattTag CHANNEL_FRACTION_CPU_LOAD_KERNEL = new SolarwattTag("FractionCPULoadKernel");
+    public static final SolarwattTag CHANNEL_FRACTION_CPU_LOAD_AVERAGE_LAST_MINUTE = new SolarwattTag(
+            "FractionCPULoadAverageLastMinute");
+    public static final SolarwattTag CHANNEL_FRACTION_CPU_LOAD_AVERAGE_LAST_FIVE_MINUTES = new SolarwattTag(
+            "FractionCPULoadAverageLastFiveMinutes");
+    public static final SolarwattTag CHANNEL_FRACTION_CPU_LOAD_AVERAGE_LAST_FIFTEEN_MINUTES = new SolarwattTag(
+            "FractionCPULoadAverageLastFifteenMinutes");
+    public static final SolarwattTag CHANNEL_MODE_CONVERTER = new SolarwattTag("ModeConverter");
+    public static final SolarwattTag CHANNEL_STATE_OF_CHARGE = new SolarwattTag("StateOfCharge");
+    public static final SolarwattTag CHANNEL_STATE_OF_HEALTH = new SolarwattTag("StateOfHealth");
+    public static final SolarwattTag CHANNEL_TEMPERATURE_BATTERY = new SolarwattTag("TemperatureBattery");
+    public static final SolarwattTag CHANNEL_POWER_BUFFERED = new SolarwattTag("PowerBuffered");
+    public static final SolarwattTag CHANNEL_POWER_BUFFERED_FROM_GRID = new SolarwattTag("PowerBufferedFromGrid");
+    public static final SolarwattTag CHANNEL_POWER_BUFFERED_FROM_PRODUCERS = new SolarwattTag(
+            "PowerBufferedFromProducers");
+    public static final SolarwattTag CHANNEL_POWER_CONSUMED = new SolarwattTag("PowerConsumed");
+    public static final SolarwattTag CHANNEL_POWER_CONSUMED_UNMETERED = new SolarwattTag("PowerConsumedUnmetered");
+    public static final SolarwattTag CHANNEL_POWER_CONSUMED_FROM_GRID = new SolarwattTag("PowerConsumedFromGrid");
+    public static final SolarwattTag CHANNEL_POWER_CONSUMED_FROM_STORAGE = new SolarwattTag("PowerConsumedFromStorage");
+    public static final SolarwattTag CHANNEL_POWER_CONSUMED_FROM_PRODUCERS = new SolarwattTag(
+            "PowerConsumedFromProducers");
+    public static final SolarwattTag CHANNEL_POWER_PRODUCED = new SolarwattTag("PowerProduced");
+    public static final SolarwattTag CHANNEL_POWER_OUT_FROM_PRODUCERS = new SolarwattTag("PowerOutFromProducers");
+    public static final SolarwattTag CHANNEL_POWER_OUT_FROM_STORAGE = new SolarwattTag("PowerOutFromStorage");
+    public static final SolarwattTag CHANNEL_POWER_RELEASED = new SolarwattTag("PowerReleased");
+    public static final SolarwattTag CHANNEL_POWER_SELF_CONSUMED = new SolarwattTag("PowerSelfConsumed");
+    public static final SolarwattTag CHANNEL_POWER_DIRECT_CONSUMED = new SolarwattTag("PowerDirectConsumed");
+    public static final SolarwattTag CHANNEL_POWER_SELF_SUPPLIED = new SolarwattTag("PowerSelfSupplied");
+    public static final SolarwattTag CHANNEL_WORK_BUFFERED = new SolarwattTag("WorkBuffered");
+    public static final SolarwattTag CHANNEL_WORK_BUFFERED_FROM_GRID = new SolarwattTag("WorkBufferedFromGrid");
+    public static final SolarwattTag CHANNEL_WORK_BUFFERED_FROM_PRODUCERS = new SolarwattTag(
+            "WorkBufferedFromProducers");
+    public static final SolarwattTag CHANNEL_WORK_CONSUMED = new SolarwattTag("WorkConsumed");
+    public static final SolarwattTag CHANNEL_WORK_CONSUMED_UNMETERED = new SolarwattTag("WorkConsumedUnmetered");
+    public static final SolarwattTag CHANNEL_WORK_CONSUMED_FROM_GRID = new SolarwattTag("WorkConsumedFromGrid");
+    public static final SolarwattTag CHANNEL_WORK_CONSUMED_FROM_STORAGE = new SolarwattTag("WorkConsumedFromStorage");
+    public static final SolarwattTag CHANNEL_WORK_CONSUMED_FROM_PRODUCERS = new SolarwattTag(
+            "WorkConsumedFromProducers");
+    public static final SolarwattTag CHANNEL_WORK_PRODUCED = new SolarwattTag("WorkProduced");
+    public static final SolarwattTag CHANNEL_WORK_OUT_FROM_PRODUCERS = new SolarwattTag("WorkOutFromProducers");
+    public static final SolarwattTag CHANNEL_WORK_OUT_FROM_STORAGE = new SolarwattTag("WorkOutFromStorage");
+    public static final SolarwattTag CHANNEL_WORK_RELEASED = new SolarwattTag("WorkReleased");
+    public static final SolarwattTag CHANNEL_WORK_SELF_CONSUMED = new SolarwattTag("WorkSelfConsumed");
+    public static final SolarwattTag CHANNEL_WORK_DIRECT_CONSUMED = new SolarwattTag("WorkDirectConsumed");
+    public static final SolarwattTag CHANNEL_WORK_SELF_SUPPLIED = new SolarwattTag("WorkSelfSupplied");
+    public static final SolarwattTag CHANNEL_CURRENT_LIMIT = new SolarwattTag("CurrentLimit");
+    public static final SolarwattTag CHANNEL_FEED_IN_LIMIT = new SolarwattTag("FeedInLimit");
+    public static final SolarwattTag CHANNEL_VOLTAGE_BATTERY_CELL_MAX = new SolarwattTag("VoltageBatteryCellMax");
+    public static final SolarwattTag CHANNEL_VOLTAGE_BATTERY_CELL_MIN = new SolarwattTag("VoltageBatteryCellMin");
+    public static final SolarwattTag CHANNEL_VOLTAGE_BATTERY_CELL_MEAN = new SolarwattTag("VoltageBatteryCellMean");
+
+    // thing configuration and properties keys
+    public static final String THING_PROPERTIES_GUID = "guid";
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/SolarwattHandlerFactory.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/SolarwattHandlerFactory.java
new file mode 100644 (file)
index 0000000..52cd22d
--- /dev/null
@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal;
+
+import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.*;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.solarwatt.internal.channel.SolarwattChannelTypeProvider;
+import org.openhab.binding.solarwatt.internal.handler.EnergyManagerHandler;
+import org.openhab.binding.solarwatt.internal.handler.LocationHandler;
+import org.openhab.binding.solarwatt.internal.handler.SimpleDeviceHandler;
+import org.openhab.core.io.net.http.HttpClientFactory;
+import org.openhab.core.thing.Bridge;
+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 SolarwattHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.solarwatt", service = ThingHandlerFactory.class)
+public class SolarwattHandlerFactory extends BaseThingHandlerFactory {
+
+    private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ENERGY_MANAGER,
+            THING_TYPE_INVERTER, THING_TYPE_POWERMETER, THING_TYPE_EVSTATION, THING_TYPE_BATTERYCONVERTER,
+            THING_TYPE_LOCATION, THING_TYPE_PVPLANT, THING_TYPE_GRIDFLOW);
+
+    private final HttpClient commonHttpClient;
+    private final SolarwattChannelTypeProvider channelTypeProvider;
+
+    @Activate
+    public SolarwattHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
+            final @Reference SolarwattChannelTypeProvider channelTypeProvider) {
+        this.commonHttpClient = httpClientFactory.getCommonHttpClient();
+        this.channelTypeProvider = channelTypeProvider;
+    }
+
+    @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_ENERGY_MANAGER.equals(thingTypeUID)) {
+            // energy manager is a separate device as it is the bridge
+            return new EnergyManagerHandler((Bridge) thing, this.channelTypeProvider, this.commonHttpClient);
+        } else if (THING_TYPE_LOCATION.equals(thingTypeUID)) {
+            return new LocationHandler(thing, this.channelTypeProvider);
+        } else if (this.supportsThingType(thingTypeUID)) {
+            // standard device handling
+            return new SimpleDeviceHandler(thing, this.channelTypeProvider);
+        }
+
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/channel/SolarwattChannelTypeProvider.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/channel/SolarwattChannelTypeProvider.java
new file mode 100644 (file)
index 0000000..fec7559
--- /dev/null
@@ -0,0 +1,120 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.channel;
+
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solarwatt.internal.SolarwattBindingConstants;
+import org.openhab.binding.solarwatt.internal.domain.SolarwattChannel;
+import org.openhab.core.library.CoreItemFactory;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.type.ChannelType;
+import org.openhab.core.thing.type.ChannelTypeBuilder;
+import org.openhab.core.thing.type.ChannelTypeProvider;
+import org.openhab.core.thing.type.ChannelTypeUID;
+import org.openhab.core.thing.type.StateChannelTypeBuilder;
+import org.openhab.core.types.StateDescriptionFragmentBuilder;
+import org.openhab.core.types.util.UnitUtils;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * A {@link ChannelTypeProvider} that creates {@link ChannelType}s according to
+ * the requested tags. It creates one {@link ChannelType} per tag value.
+ *
+ * @author Matthias Steigenberger - Initial contribution
+ * @author Sven Carstens - Adapted to solarwatt binding
+ *
+ */
+@NonNullByDefault
+@Component(service = { ChannelTypeProvider.class, SolarwattChannelTypeProvider.class })
+public class SolarwattChannelTypeProvider implements ChannelTypeProvider {
+
+    private final Map<String, ChannelType> channelMap = new ConcurrentHashMap<>();
+
+    @Override
+    public Collection<ChannelType> getChannelTypes(@Nullable Locale locale) {
+        return this.channelMap.values();
+    }
+
+    @Override
+    public @Nullable ChannelType getChannelType(ChannelTypeUID channelTypeUID, @Nullable Locale locale) {
+        return this.channelMap.values().stream().filter(channelType -> channelType.getUID().equals(channelTypeUID))
+                .findFirst().orElse(null);
+    }
+
+    /**
+     * Assert that the {@link ChannelType} matching our requirements exists.
+     *
+     * Only create once for each tagname supplied via {@link SolarwattChannel}.
+     * 
+     * @param solarwattChannel channeltype requirements
+     * @return UID of existing channeltype
+     */
+    public ChannelTypeUID assertChannelType(SolarwattChannel solarwattChannel) {
+        ChannelType existingChannel = this.channelMap.get(solarwattChannel.getChannelName());
+        if (existingChannel == null) {
+            ChannelType createdChannel = this.getChannelType(solarwattChannel);
+            this.channelMap.put(solarwattChannel.getChannelName(), createdChannel);
+            return createdChannel.getUID();
+        } else {
+            return existingChannel.getUID();
+        }
+    }
+
+    private ChannelType getChannelType(SolarwattChannel solarwattChannel) {
+        StateChannelTypeBuilder stateDescriptionBuilder;
+        Unit<?> unit = solarwattChannel.getUnit();
+        if (unit != null) {
+            if ("switch".equals(solarwattChannel.getCategory())) {
+                stateDescriptionBuilder = ChannelTypeBuilder
+                        .state(new ChannelTypeUID(SolarwattBindingConstants.BINDING_ID,
+                                solarwattChannel.getChannelName()), solarwattChannel.getChannelName(),
+                                CoreItemFactory.SWITCH)
+                        .withCategory(solarwattChannel.getCategory()).isAdvanced(solarwattChannel.getAdvanced())
+                        .withStateDescriptionFragment(
+                                StateDescriptionFragmentBuilder.create().withReadOnly(true).build());
+            } else {
+                String dimension = ":" + UnitUtils.getDimensionName(unit);
+                String unitString = unit.toString();
+
+                if (Units.PERCENT.equals(unit)) {
+                    // strangely it is Angle
+                    dimension = ":Dimensionless";
+                    unitString = "%%";
+                }
+
+                stateDescriptionBuilder = ChannelTypeBuilder
+                        .state(new ChannelTypeUID(SolarwattBindingConstants.BINDING_ID,
+                                solarwattChannel.getChannelName()), solarwattChannel.getChannelName(),
+                                CoreItemFactory.NUMBER + dimension)
+                        .withCategory(solarwattChannel.getCategory()).isAdvanced(solarwattChannel.getAdvanced())
+                        .withStateDescriptionFragment(StateDescriptionFragmentBuilder.create().withReadOnly(true)
+                                .withPattern("%.2f " + unitString).build());
+            }
+        } else {
+            stateDescriptionBuilder = ChannelTypeBuilder
+                    .state(new ChannelTypeUID(SolarwattBindingConstants.BINDING_ID, solarwattChannel.getChannelName()),
+                            solarwattChannel.getChannelName(), CoreItemFactory.STRING)
+                    .withCategory(solarwattChannel.getCategory()).isAdvanced(solarwattChannel.getAdvanced())
+                    .withStateDescriptionFragment(StateDescriptionFragmentBuilder.create().withReadOnly(true).build());
+        }
+        return stateDescriptionBuilder.build();
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/configuration/SolarwattBridgeConfiguration.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/configuration/SolarwattBridgeConfiguration.java
new file mode 100644 (file)
index 0000000..919896a
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.configuration;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link SolarwattBridgeConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class SolarwattBridgeConfiguration {
+    private static final int DEFAULT_RESCAN_MINUTES = 5;
+    private static final int DEFAULT_REFRESH_SECONDS = 30;
+
+    /**
+     * Hostname or ip where the solarwatt energymanager is reachable.
+     *
+     * Energy manager does not set a default name via DHCP.
+     */
+    public String hostname = "";
+
+    /**
+     * Refresh interval for updating devices data
+     */
+    public int refresh = DEFAULT_REFRESH_SECONDS;
+
+    /**
+     * Refresh interval for reading of devices (not the devices data)
+     */
+    public int rescan = DEFAULT_RESCAN_MINUTES;
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/configuration/SolarwattThingConfiguration.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/configuration/SolarwattThingConfiguration.java
new file mode 100644 (file)
index 0000000..57c966b
--- /dev/null
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.configuration;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link SolarwattThingConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class SolarwattThingConfiguration {
+    /**
+     * Guid for the thing that is used by the energy manager
+     */
+    public String guid = "";
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/discovery/SolarwattDevicesDiscoveryService.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/discovery/SolarwattDevicesDiscoveryService.java
new file mode 100644 (file)
index 0000000..2f217cd
--- /dev/null
@@ -0,0 +1,194 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.discovery;
+
+import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.*;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solarwatt.internal.domain.model.BatteryConverter;
+import org.openhab.binding.solarwatt.internal.domain.model.Device;
+import org.openhab.binding.solarwatt.internal.domain.model.EVStation;
+import org.openhab.binding.solarwatt.internal.domain.model.GridFlow;
+import org.openhab.binding.solarwatt.internal.domain.model.Inverter;
+import org.openhab.binding.solarwatt.internal.domain.model.Location;
+import org.openhab.binding.solarwatt.internal.domain.model.PVPlant;
+import org.openhab.binding.solarwatt.internal.domain.model.PowerMeter;
+import org.openhab.binding.solarwatt.internal.handler.EnergyManagerHandler;
+import org.openhab.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Discovery service to discover devices attached to the energy manager.
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class SolarwattDevicesDiscoveryService extends AbstractDiscoveryService
+        implements ThingHandlerService, DiscoveryService {
+
+    private static final int TIMEOUT_SECONDS = 20;
+
+    private final Logger logger = LoggerFactory.getLogger(SolarwattDevicesDiscoveryService.class);
+    private @Nullable EnergyManagerHandler energyManagerHandler;
+
+    /**
+     * Job which will do the background scanning
+     */
+    private final EnergymanagerScan scanningRunnable;
+
+    /**
+     * Schedule for scanning
+     */
+    private @Nullable ScheduledFuture<?> scanningJob;
+
+    public SolarwattDevicesDiscoveryService() {
+        super(TIMEOUT_SECONDS);
+        this.scanningRunnable = new EnergymanagerScan();
+
+        this.activate(null);
+    }
+
+    @Override
+    public void setThingHandler(final @Nullable ThingHandler handler) {
+        if (handler instanceof EnergyManagerHandler) {
+            this.energyManagerHandler = (EnergyManagerHandler) handler;
+        }
+    }
+
+    @Override
+    public @Nullable ThingHandler getThingHandler() {
+        return this.energyManagerHandler;
+    }
+
+    @Override
+    public void deactivate() {
+        this.stopBackgroundDiscovery();
+    }
+
+    @Override
+    protected void startBackgroundDiscovery() {
+        ScheduledFuture<?> localScanningJob = this.scanningJob;
+        if (localScanningJob == null || localScanningJob.isCancelled()) {
+            this.scanningJob = this.scheduler.scheduleWithFixedDelay(this.scanningRunnable, 5, 5 * 60,
+                    TimeUnit.SECONDS);
+        }
+    }
+
+    @Override
+    protected void stopBackgroundDiscovery() {
+        ScheduledFuture<?> localScanningJob = this.scanningJob;
+        if (localScanningJob != null && !localScanningJob.isCancelled()) {
+            localScanningJob.cancel(true);
+            this.scanningJob = null;
+        }
+    }
+
+    @Override
+    protected synchronized void startScan() {
+        this.removeOlderResults(this.getTimestampOfLastScan());
+        final EnergyManagerHandler localEnergyManagerHandler = this.energyManagerHandler;
+
+        if (localEnergyManagerHandler == null
+                || localEnergyManagerHandler.getThing().getStatus() != ThingStatus.ONLINE) {
+            this.logger.warn("Energymanager handler not available: {}", localEnergyManagerHandler);
+            return;
+        }
+        this.scanForDeviceThings();
+    }
+
+    /**
+     * Scans for device things.
+     *
+     * Walks through the list of devices and adds discovery results for the supported devices.
+     */
+    private void scanForDeviceThings() {
+        EnergyManagerHandler localEnergyManagerHandler = this.energyManagerHandler;
+        if (localEnergyManagerHandler != null) {
+            final Map<String, Device> devices = localEnergyManagerHandler.getDevices();
+
+            final ThingUID bridgeUID = localEnergyManagerHandler.getThing().getUID();
+
+            if (devices == null) {
+                this.logger.warn("No device data for solarwatt devices in discovery for energy manager {}.", bridgeUID);
+            } else {
+                devices.forEach((key, entry) -> {
+                    if (entry instanceof BatteryConverter) {
+                        this.discover(bridgeUID, entry, THING_TYPE_BATTERYCONVERTER);
+                    } else if (entry instanceof Inverter) {
+                        this.discover(bridgeUID, entry, THING_TYPE_INVERTER);
+                    } else if (entry instanceof PowerMeter) {
+                        this.discover(bridgeUID, entry, THING_TYPE_POWERMETER);
+                    } else if (entry instanceof EVStation) {
+                        this.discover(bridgeUID, entry, THING_TYPE_EVSTATION);
+                    } else if (entry instanceof Location) {
+                        this.discover(bridgeUID, entry, THING_TYPE_LOCATION);
+                    } else if (entry instanceof PVPlant) {
+                        this.discover(bridgeUID, entry, THING_TYPE_PVPLANT);
+                    } else if (entry instanceof GridFlow) {
+                        this.discover(bridgeUID, entry, THING_TYPE_GRIDFLOW);
+                    }
+                });
+            }
+        }
+    }
+
+    /**
+     * Create a discovery result and add to result.
+     *
+     * @param bridgeID to which this device belongs
+     * @param entry describing the device
+     * @param typeUID for matching thing
+     */
+    private void discover(final ThingUID bridgeID, final Device entry, final ThingTypeUID typeUID) {
+        final ThingUID thingUID = new ThingUID(typeUID, bridgeID, this.rewriteGuid(entry.getGuid()));
+        final Map<String, Object> properties = new HashMap<>(5);
+
+        properties.put(THING_PROPERTIES_GUID, entry.getGuid());
+        final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeID)
+                .withRepresentationProperty(THING_PROPERTIES_GUID).withProperties(properties)
+                .withLabel("Solarwatt " + entry.getLabel()).build();
+        this.thingDiscovered(discoveryResult);
+    }
+
+    /**
+     * Rewrite energy manager guids to be acceptable to openhab.
+     *
+     * @param emGuid from energy manager
+     * @return guid for openhab
+     */
+    private String rewriteGuid(String emGuid) {
+        return emGuid.replaceAll(":", "-");
+    }
+
+    public class EnergymanagerScan implements Runnable {
+        @Override
+        public void run() {
+            SolarwattDevicesDiscoveryService.this.startScan();
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/EnergyManagerCollection.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/EnergyManagerCollection.java
new file mode 100644 (file)
index 0000000..6bc4610
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarwatt.internal.domain.dto.EnergyManagerDTO;
+import org.openhab.binding.solarwatt.internal.domain.model.Device;
+import org.openhab.binding.solarwatt.internal.factory.EnergyManagerDevicesFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Collection of all devices known to the energy manager including the energy manager itself.
+ *
+ * The {@link Device}s are generated from the {@link DeviceDTO}s inside of the {@link EnergyManagerDTO}
+ * 
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class EnergyManagerCollection {
+    private Logger logger = LoggerFactory.getLogger(EnergyManagerCollection.class);
+
+    private Map<String, Device> devices;
+
+    public EnergyManagerCollection(EnergyManagerDTO energyManagerDTO) {
+        this.devices = new HashMap<>();
+
+        energyManagerDTO.getItems().forEach(deviceDTO -> {
+            try {
+                Device device = EnergyManagerDevicesFactory.getEnergyManagerDevice(deviceDTO);
+
+                if (device != null) {
+                    this.devices.put(device.getGuid(), device);
+                }
+            } catch (Exception ex) {
+                this.logger.error("Error setting up initial device {}: {}", deviceDTO.getGuid(),
+                        deviceDTO.getDeviceModel(), ex);
+            }
+        });
+    }
+
+    public Map<String, Device> getDevices() {
+        return this.devices;
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/SolarwattChannel.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/SolarwattChannel.java
new file mode 100644 (file)
index 0000000..6a8179b
--- /dev/null
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain;
+
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Aggregation of the interesting parts to write into a channel.
+ *
+ * From this the {@link ChannelType}s are created.
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class SolarwattChannel {
+    private final String channelName;
+    private final @Nullable Unit<?> unit;
+    private final String category;
+    private final Boolean advanced;
+
+    public SolarwattChannel(String channelName, String category) {
+        this(channelName, category, false);
+    }
+
+    public SolarwattChannel(String channelName, Unit<?> unit, String category) {
+        this(channelName, unit, category, false);
+    }
+
+    public SolarwattChannel(String channelName, String category, Boolean advanced) {
+        this(channelName, null, category, advanced);
+    }
+
+    public SolarwattChannel(String channelName, @Nullable Unit<?> unit, String category, Boolean advanced) {
+        this.channelName = channelName;
+        this.unit = unit;
+        this.category = category;
+        this.advanced = advanced;
+    }
+
+    public String getChannelName() {
+        return this.channelName;
+    }
+
+    public @Nullable Unit<?> getUnit() {
+        return this.unit;
+    }
+
+    public String getCategory() {
+        return this.category;
+    }
+
+    public Boolean getAdvanced() {
+        return this.advanced;
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/SolarwattTag.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/SolarwattTag.java
new file mode 100644 (file)
index 0000000..b77e0b5
--- /dev/null
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Helper to handle different character cases between energy manager tagnames
+ * and openhab channel names.
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class SolarwattTag {
+    private final String tagName;
+    private final String channelName;
+
+    public SolarwattTag(String tagName) {
+        this.tagName = tagName;
+        char chars[] = tagName.toCharArray();
+        chars[0] = Character.toLowerCase(chars[0]);
+        this.channelName = new String(chars);
+    }
+
+    public String getTagName() {
+        return this.tagName;
+    }
+
+    public String getChannelName() {
+        return this.channelName;
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/converter/JsonStateConverter.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/converter/JsonStateConverter.java
new file mode 100644 (file)
index 0000000..8c7b321
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.converter;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.types.State;
+
+import com.google.gson.JsonElement;
+
+/**
+ * Interface bundling all converters from JsonElement to openhab State.
+ *
+ * We do not implement the interface but only pass closures.
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public interface JsonStateConverter {
+    State convert(JsonElement jsonElement);
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/dto/DeviceDTO.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/dto/DeviceDTO.java
new file mode 100644 (file)
index 0000000..e15e708
--- /dev/null
@@ -0,0 +1,226 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.dto;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solarwatt.internal.domain.converter.JsonStateConverter;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+
+/**
+ * DTO class for the devices returned by the energy manager.
+ *
+ * Properties without setters are only filled by gson JSON parsing.
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+public class DeviceDTO {
+    private final Logger logger = LoggerFactory.getLogger(DeviceDTO.class);
+
+    private String guid;
+    private Collection<DeviceModel> deviceModel;
+    private Map<String, TagValueDTO> tagValues;
+
+    public String getGuid() {
+        return this.guid;
+    }
+
+    public Collection<String> getDeviceModelStrings() {
+        return this.deviceModel.stream().map(DeviceModel::getDeviceClass).collect(Collectors.toList());
+    }
+
+    public Collection<DeviceModel> getDeviceModel() {
+        return this.deviceModel;
+    }
+
+    public Map<String, TagValueDTO> getTagValues() {
+        return this.tagValues;
+    }
+
+    /**
+     * Helper to get a string value from the list of tag values.
+     *
+     * @param tagName name of tag to read
+     * @return tag value
+     */
+    public @Nullable String getStringTag(String tagName) {
+        JsonPrimitive jsonPrimitive = this.getJsonPrimitiveFromTag(tagName);
+        if (jsonPrimitive != null) {
+            return jsonPrimitive.getAsString();
+        }
+
+        return null;
+    }
+
+    /**
+     * Helper to get a {@link JsonPrimitive} from the list of tag values.
+     *
+     * The primitves are later converted to the desired target type.
+     * 
+     * @param tagName name of tag to read
+     * @return raw json from tag value
+     */
+    protected @Nullable JsonPrimitive getJsonPrimitiveFromTag(String tagName) {
+        Map<String, TagValueDTO> localTagValues = this.getTagValues();
+        if (localTagValues != null) {
+            TagValueDTO localTag = localTagValues.get(tagName);
+            if (localTag != null && localTag.getValue().isJsonPrimitive()) {
+                return (JsonPrimitive) localTag.getValue();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Helper to get a {@link JsonObject} from the list of tag values.
+     *
+     * The objects are used by the concrete devices to read interesting
+     * but deeply nested values.
+     *
+     * @param tagName name of tag to read
+     * @return raw json from tag value
+     */
+    public @Nullable JsonObject getJsonObjectFromTag(String tagName) {
+        Map<String, TagValueDTO> localTagValues = this.getTagValues();
+        if (localTagValues != null) {
+            TagValueDTO localTag = localTagValues.get(tagName);
+            if (localTag != null && localTag.getValue().isJsonObject()) {
+                return (JsonObject) localTag.getValue();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Helper to get a {@link JsonObject} from a tag value which contains JSON.
+     *
+     * The objects are used by the concrete devices to read interesting
+     * but deeply nested values.
+     *
+     * @param tagName name of tag to read
+     * @return raw json from tag value
+     */
+    public JsonPrimitive getJsonPrimitiveFromPath(String tagName, String path) {
+        JsonElement jsonElement = this.getJsonFromPath(tagName, path);
+
+        if (jsonElement != null && jsonElement.isJsonPrimitive()) {
+            return (JsonPrimitive) jsonElement;
+        }
+        return null;
+    }
+
+    /**
+     * Helper to get a {@link JsonObject} from a tag value which contains JSON.
+     *
+     * The json path is traversed according to the supplied path. Only simple pathes
+     * are supported.
+     *
+     * @param tagName name of tag to read
+     * @return raw json from tag value
+     */
+    public JsonElement getJsonFromPath(String tagName, String path) {
+        JsonObject json = this.getJsonObjectFromTag(tagName);
+        if (json != null) {
+            String[] parts = path.split("\\.|\\[|\\]");
+            JsonElement result = json;
+
+            for (String key : parts) {
+
+                key = key.trim();
+                if (key.isEmpty()) {
+                    continue;
+                }
+
+                if (result == null) {
+                    result = JsonNull.INSTANCE;
+                    break;
+                }
+
+                if (result.isJsonObject()) {
+                    result = ((JsonObject) result).get(key);
+                } else if (result.isJsonArray()) {
+                    int ix = Integer.valueOf(key) - 1;
+                    result = ((JsonArray) result).get(ix);
+                } else {
+                    break;
+                }
+            }
+
+            return result;
+        }
+
+        return null;
+    }
+
+    /**
+     * Transform a value from a tag to a state.
+     *
+     * @param converter applied the the {@link JsonPrimitive}
+     * @param tagName to find value
+     * @return state for channel
+     */
+    public State getState(JsonStateConverter converter, String tagName) {
+        return this.getState(converter, tagName, tagName, null);
+    }
+
+    /**
+     * Transform a value specified via JSON path from a tag to a state.
+     *
+     * @param converter applied the the {@link JsonPrimitive}
+     * @param channelName for the state
+     * @param tagName to find value
+     * @param jsonPath to find value
+     * @return state for channel
+     */
+    public State getState(JsonStateConverter converter, String channelName, String tagName, String jsonPath) {
+        State state = UnDefType.UNDEF;
+        try {
+            JsonPrimitive jsonPrimitive = jsonPath == null ? this.getJsonPrimitiveFromTag(tagName)
+                    : this.getJsonPrimitiveFromPath(tagName, jsonPath);
+            if (jsonPrimitive != null) {
+                state = converter.convert(jsonPrimitive);
+            } else {
+                state = UnDefType.NULL;
+            }
+        } catch (Exception ex) {
+            this.logger.warn("failed getting state for {}", channelName, ex);
+        }
+
+        return state;
+    }
+
+    public static class DeviceModel {
+        private String deviceClass;
+
+        public String getDeviceClass() {
+            return this.deviceClass;
+        }
+
+        @Override
+        public String toString() {
+            return this.deviceClass;
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/dto/EnergyManagerDTO.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/dto/EnergyManagerDTO.java
new file mode 100644 (file)
index 0000000..a58d2ec
--- /dev/null
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.dto;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * DTO class for the complete structure delivered by the energy manager.
+ *
+ * Properties without setters are only filled by gson JSON parsing.
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+public class EnergyManagerDTO {
+    private Result result;
+
+    public Collection<DeviceDTO> getItems() {
+        return this.result.getItems();
+    }
+
+    public static class Result {
+        public Collection<DeviceDTO> getItems() {
+            return this.items;
+        }
+
+        private Collection<DeviceDTO> items = new ArrayList<>();
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/dto/TagValueDTO.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/dto/TagValueDTO.java
new file mode 100644 (file)
index 0000000..37b77ea
--- /dev/null
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.dto;
+
+import com.google.gson.JsonElement;
+
+/**
+ * DTO class to encapsulate the tag values from the energy manager.
+ *
+ * Properties without setters are only filled by gson JSON parsing.
+ * The concrete tag values can be anything. {@link com.google.gson.JsonPrimitive},
+ * {@link com.google.gson.JsonObject} or even strings containing json structures.
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+public class TagValueDTO {
+    private String tagName;
+    private String guid;
+    private JsonElement value;
+
+    public String getTagName() {
+        return this.tagName;
+    }
+
+    public String getGuid() {
+        return this.guid;
+    }
+
+    public JsonElement getValue() {
+        return this.value;
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/BatteryConverter.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/BatteryConverter.java
new file mode 100644 (file)
index 0000000..893e20f
--- /dev/null
@@ -0,0 +1,105 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+
+/**
+ * Base class for everything supplying battery base power.
+ *
+ * This fields have been identified to exist:
+ * com.kiwigrid.devices.batteryconverter.BatteryConverter=[
+ * PowerACIn,
+ * UpTimePDG,
+ * CurrentStringDCIn,
+ * ResistanceBatteryMean,
+ * CurrentBatteryIn,
+ * VoltageBatteryCellMax,
+ * VoltageGRMOut,
+ * FactorForecast,
+ * StateOfChargeMinimum,
+ * StateEqualizingChargeRequiredIsSet,
+ * WorkCharge,
+ * VoltageBatteryCellMin,
+ * VoltageBatteryCellMean,
+ * StateOfHealth,
+ * FactorForecastCAN,
+ * StateOfCharge,
+ * IdFirmwareGRM,
+ * WorkCapacity,
+ * CurrentGRMOut,
+ * StatePDG,
+ * TemperatureBatteryCellMax,
+ * TemperatureGRM,
+ * TemperatureBatteryMin,
+ * ResistanceBatteryMin,
+ * StateOfChargeMinimumLimit,
+ * IdUrlPdg,
+ * TemperatureBattery,
+ * VoltageGRMIn,
+ * CurrentGRMIn,
+ * IdSerialNumberGRM,
+ * ResistanceBatteryString,
+ * VoltageBatteryString,
+ * IdSerialNumberBatteryModules,
+ * CountBatteryModules,
+ * ModeConverter,
+ * TemperatureBatteryMax,
+ * TemperatureBatteryCellMin,
+ * StateOfChargeReactivateDischarging,
+ * IdMyReserveSetupRole,
+ * ResistanceBatteryMax,
+ * AvailableModes,
+ * PowerACInMax,
+ * PowerACInLimit,
+ * StateOfChargeShutDownLimit,
+ * CountBatteryContactor,
+ * CurrentBatteryOut,
+ * TimeEqualizingChargeRemaining,
+ * WorkACIn,
+ * MapInstallationDetails
+ * ]
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class BatteryConverter extends Inverter {
+    public static final String SOLAR_WATT_CLASSNAME = "com.kiwigrid.devices.batteryconverter.BatteryConverter";
+
+    public BatteryConverter(DeviceDTO deviceDTO) {
+        super(deviceDTO);
+    }
+
+    @Override
+    public void update(DeviceDTO deviceDTO) {
+        super.update(deviceDTO);
+
+        this.addSwitchState(CHANNEL_MODE_CONVERTER, deviceDTO);
+        this.addPercentQuantity(CHANNEL_STATE_OF_CHARGE, deviceDTO);
+        this.addPercentQuantity(CHANNEL_STATE_OF_HEALTH, deviceDTO);
+        this.addCelsiusQuantity(CHANNEL_TEMPERATURE_BATTERY, deviceDTO);
+        this.addWattHourQuantity(CHANNEL_WORK_AC_IN, deviceDTO);
+        this.addWattQuantity(CHANNEL_POWER_AC_IN, deviceDTO);
+        this.addVoltageQuantity(CHANNEL_VOLTAGE_BATTERY_CELL_MIN, deviceDTO, true);
+        this.addVoltageQuantity(CHANNEL_VOLTAGE_BATTERY_CELL_MEAN, deviceDTO, true);
+        this.addVoltageQuantity(CHANNEL_VOLTAGE_BATTERY_CELL_MAX, deviceDTO, true);
+    }
+
+    @Override
+    protected String getSolarWattLabel() {
+        return "BatteryConverter";
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/Device.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/Device.java
new file mode 100644 (file)
index 0000000..69b6644
--- /dev/null
@@ -0,0 +1,315 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solarwatt.internal.domain.SolarwattChannel;
+import org.openhab.binding.solarwatt.internal.domain.SolarwattTag;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+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.ThingStatus;
+import org.openhab.core.types.State;
+
+/**
+ * Base class for all devices which are connected to the energy manager.
+ *
+ * This fields have been identified to exist:
+ * com.kiwigrid.lib.device.Device=[
+ * IdFingerPrint,
+ * IdInterfaceList,
+ * IdName,
+ * IdFirmware,
+ * PasswordLock,
+ * IdLabelSet,
+ * StateDevice,
+ * StateVisibleIsSet,
+ * IdFingerPrintVersion,
+ * IdDriver,
+ * IdModelCode,
+ * StateLockedIsSet,
+ * IdManufacturer,
+ * IdSerialNumber,
+ * StateErrorList
+ * ]
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class Device {
+
+    public static final String SOLAR_WATT_CLASSNAME = "com.kiwigrid.lib.device.Device";
+    public static final String WATT_HOUR_CATEGORY = "energy";
+    public static final String WATT_CATEGORY = "energy";
+    private final String guid;
+    private @Nullable String idName;
+    private @Nullable String idFirmware;
+    private @Nullable String idManufacturer;
+    private ThingStatus stateDevice = ThingStatus.UNINITIALIZED;
+    protected final Map<String, State> stateValues;
+    protected final Map<String, SolarwattChannel> solarwattChannelSet;
+
+    public Device(DeviceDTO deviceDTO) {
+        this.stateValues = new HashMap<>();
+        this.solarwattChannelSet = new HashMap<>();
+        this.guid = deviceDTO.getGuid();
+
+        this.update(deviceDTO);
+    }
+
+    public void update(DeviceDTO deviceDTO) {
+        this.idName = deviceDTO.getStringTag("IdName");
+        this.idFirmware = deviceDTO.getStringTag("IdFirmware");
+        this.idManufacturer = deviceDTO.getStringTag("IdManufacturer");
+        if ("OK".equals(deviceDTO.getStringTag("StateDevice"))) {
+            this.stateDevice = ThingStatus.ONLINE;
+        } else {
+            this.stateDevice = ThingStatus.OFFLINE;
+        }
+    }
+
+    public BigDecimal getBigDecimalFromChannel(String channelName) {
+        State state = this.getStateValues().get(channelName);
+        if (state != null) {
+            @SuppressWarnings("rawtypes")
+            QuantityType quantity = state.as(QuantityType.class);
+            if (quantity != null) {
+                return quantity.toBigDecimal();
+            }
+        }
+        return BigDecimal.ZERO;
+    }
+
+    /**
+     * Add a channeltype to the known channel types.
+     *
+     * {@link org.openhab.core.thing.type.ChannelType} is only created if it does noct exist.
+     *
+     * @param tagName name for the channel
+     * @param unit unit for channel
+     * @param category text category
+     * @param advanced wether or not to display only in advanced
+     */
+    public void addChannel(String tagName, @Nullable Unit<?> unit, String category, Boolean advanced) {
+        this.solarwattChannelSet.computeIfAbsent(tagName, s -> new SolarwattChannel(tagName, unit, category, advanced));
+    }
+
+    /**
+     * Add a state with unit and BigInteger as value.
+     *
+     * @param solarwattTag combined tag and channel name
+     * @param deviceDTO raw device data
+     * @param unit unit for value
+     */
+    public void addStateBigInteger(SolarwattTag solarwattTag, DeviceDTO deviceDTO, Unit<?> unit) {
+        this.addState(solarwattTag.getChannelName(), deviceDTO.getState(
+                (jsonElement -> new QuantityType<>(jsonElement.getAsBigInteger(), unit)), solarwattTag.getTagName()));
+    }
+
+    /**
+     * Add a state from a json path with unit and BigInteger as value.
+     *
+     * @param channelName target channe
+     * @param tagName tag for value
+     * @param path to find value
+     * @param deviceDTO raw device data
+     * @param unit unit for value
+     */
+    public void addStateBigInteger(String channelName, String tagName, String path, DeviceDTO deviceDTO, Unit<?> unit) {
+        this.addState(channelName, deviceDTO.getState(
+                (jsonElement -> new QuantityType<>(jsonElement.getAsBigInteger(), unit)), channelName, tagName, path));
+    }
+
+    /**
+     * Add a state with unit and BigDecimal as value.
+     *
+     * @param solarwattTag combined tag and channel name
+     * @param deviceDTO raw device data
+     * @param unit unit for value
+     */
+    public void addStateBigDecimal(SolarwattTag solarwattTag, DeviceDTO deviceDTO, Unit<?> unit) {
+        this.addState(solarwattTag.getChannelName(), deviceDTO.getState(
+                (jsonElement -> new QuantityType<>(jsonElement.getAsBigDecimal(), unit)), solarwattTag.getTagName()));
+    }
+
+    /**
+     * Add a state with unit and BigDecimal as value.
+     *
+     * @param solarwattTag combined tag and channel name
+     * @param value BigDecimal value
+     * @param unit unit for value
+     */
+    public void addStateBigDecimal(SolarwattTag solarwattTag, BigDecimal value, Unit<?> unit) {
+        this.addState(solarwattTag.getChannelName(), new QuantityType<>(value, unit));
+    }
+
+    /**
+     * Add a string state.
+     *
+     * @param solarwattTag combined tag and channel name
+     * @param deviceDTO raw device data
+     */
+    public void addStateString(SolarwattTag solarwattTag, DeviceDTO deviceDTO) {
+        this.addState(solarwattTag.getChannelName(), deviceDTO
+                .getState((jsonElement -> new StringType(jsonElement.getAsString())), solarwattTag.getTagName()));
+    }
+
+    public void addStateSwitch(SolarwattTag solarwattTag, DeviceDTO deviceDTO) {
+        this.addState(solarwattTag.getChannelName(), deviceDTO
+                .getState((jsonElement -> OnOffType.from(jsonElement.getAsString())), solarwattTag.getTagName()));
+    }
+
+    public ThingStatus getStateDevice() {
+        return this.stateDevice;
+    }
+
+    public Map<String, State> getStateValues() {
+        return this.stateValues;
+    }
+
+    public Map<String, SolarwattChannel> getSolarwattChannelSet() {
+        return this.solarwattChannelSet;
+    }
+
+    protected void addWattHourQuantity(SolarwattTag solarwattTag, DeviceDTO deviceDTO) {
+        this.addWattHourQuantity(solarwattTag, deviceDTO, false);
+    }
+
+    protected void addWattHourQuantity(SolarwattTag solarwattTag, DeviceDTO deviceDTO, Boolean advanced) {
+        this.addChannel(solarwattTag.getChannelName(), Units.WATT_HOUR, WATT_HOUR_CATEGORY, advanced);
+
+        this.addStateBigDecimal(solarwattTag, deviceDTO, Units.WATT_HOUR);
+    }
+
+    protected void addWattQuantity(SolarwattTag solarwattTag, DeviceDTO deviceDTO) {
+        this.addWattQuantity(solarwattTag, deviceDTO, false);
+    }
+
+    protected void addWattQuantity(SolarwattTag solarwattTag, BigDecimal value, Boolean advanced) {
+        this.addChannel(solarwattTag.getChannelName(), Units.WATT, WATT_CATEGORY, advanced);
+
+        this.addStateBigDecimal(solarwattTag, value, Units.WATT);
+    }
+
+    protected void addWattQuantity(SolarwattTag solarwattTag, DeviceDTO deviceDTO, Boolean advanced) {
+        this.addChannel(solarwattTag.getChannelName(), Units.WATT, WATT_CATEGORY, advanced);
+
+        this.addStateBigDecimal(solarwattTag, deviceDTO, Units.WATT);
+    }
+
+    protected void addSecondsQuantity(String channelName, String tagName, String path, DeviceDTO deviceDTO) {
+        this.addChannel(channelName, Units.SECOND, "time", false);
+
+        this.addStateBigInteger(channelName, tagName, path, deviceDTO, Units.SECOND);
+    }
+
+    protected void addPercentQuantity(SolarwattTag solarwattTag, DeviceDTO deviceDTO) {
+        this.addChannel(solarwattTag.getChannelName(), Units.PERCENT, "status", false);
+
+        this.addStateBigDecimal(solarwattTag, deviceDTO, Units.PERCENT);
+    }
+
+    protected void addCelsiusQuantity(SolarwattTag solarwattTag, DeviceDTO deviceDTO) {
+        this.addChannel(solarwattTag.getChannelName(), SIUnits.CELSIUS, "temperature", false);
+
+        this.addStateBigDecimal(solarwattTag, deviceDTO, SIUnits.CELSIUS);
+    }
+
+    protected void addAmpereQuantity(SolarwattTag solarwattTag, DeviceDTO deviceDTO) {
+        this.addAmpereQuantity(solarwattTag, deviceDTO, false);
+    }
+
+    protected void addAmpereQuantity(SolarwattTag solarwattTag, DeviceDTO deviceDTO, Boolean advanced) {
+        this.addChannel(solarwattTag.getChannelName(), Units.AMPERE, "current", advanced);
+
+        this.addStateBigDecimal(solarwattTag, deviceDTO, Units.AMPERE);
+    }
+
+    protected void addVoltageQuantity(SolarwattTag solarwattTag, DeviceDTO deviceDTO) {
+        this.addVoltageQuantity(solarwattTag, deviceDTO, false);
+    }
+
+    protected void addVoltageQuantity(SolarwattTag solarwattTag, DeviceDTO deviceDTO, Boolean advanced) {
+        this.addChannel(solarwattTag.getChannelName(), Units.VOLT, "voltage", advanced);
+
+        this.addStateBigDecimal(solarwattTag, deviceDTO, Units.VOLT);
+    }
+
+    protected void addStringState(SolarwattTag solarwattTag, DeviceDTO deviceDTO) {
+        this.addChannel(solarwattTag.getChannelName(), null, "status", false);
+
+        this.addStateString(solarwattTag, deviceDTO);
+    }
+
+    protected void addSwitchState(SolarwattTag solarwattTag, DeviceDTO deviceDTO) {
+        this.addChannel(solarwattTag.getChannelName(), null, "switch", false);
+
+        this.addStateSwitch(solarwattTag, deviceDTO);
+    }
+
+    /**
+     * Add state to map and return it for further usage.
+     *
+     * @param channelName where to put the state
+     * @param state to put
+     */
+    public void addState(String channelName, @Nullable State state) {
+        if (state != null) {
+            this.stateValues.put(channelName, state);
+        }
+    }
+
+    /**
+     * Get state from map
+     * 
+     * @param channelName state to return
+     * @return {@link State} found
+     */
+    public @Nullable State getState(String channelName) {
+        return this.stateValues.get(channelName);
+    }
+
+    public String getGuid() {
+        return this.guid;
+    }
+
+    public @Nullable String getIdName() {
+        return this.idName;
+    }
+
+    public @Nullable String getIdFirmware() {
+        return this.idFirmware;
+    }
+
+    public @Nullable String getIdManufacturer() {
+        return this.idManufacturer;
+    }
+
+    protected String getSolarWattLabel() {
+        return "Device";
+    }
+
+    public String getLabel() {
+        return this.getSolarWattLabel() + " " + this.getIdName();
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/EVStation.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/EVStation.java
new file mode 100644 (file)
index 0000000..dcd7645
--- /dev/null
@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+
+/**
+ * Base class for a wallbox.
+ *
+ * This fields have been identified to exist:
+ * com.kiwigrid.devices.evstation.EVStation=[
+ * PowerACOutMax,
+ * PowerACIn,
+ * VoltageACL1,
+ * VoltageACL2,
+ * VoltageACL3,
+ * WorkACOut,
+ * ConnectivityStatus,
+ * StateOfChargeMinimum,
+ * WorkACInLimitSession,
+ * WorkCharge,
+ * PowerACOut,
+ * PowerACOutLimit,
+ * AvailableModes,
+ * PowerACInMax,
+ * StateOfCharge,
+ * PowerACInLimit,
+ * CurrentACInLimit,
+ * StateInfo,
+ * ModeStation,
+ * VehicleId,
+ * CurrentACInMinimum,
+ * WorkCapacity,
+ * WorkACInSession,
+ * UserId,
+ * TemperatureBattery,
+ * WorkACIn,
+ * CurrentACInL1,
+ * CurrentACInL3,
+ * PowerACInMin,
+ * CurrentACInL2
+ * ]
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class EVStation extends Device {
+    public static final String SOLAR_WATT_CLASSNAME = "com.kiwigrid.devices.evstation.EVStation";
+
+    public EVStation(DeviceDTO deviceDTO) {
+        super(deviceDTO);
+    }
+
+    @Override
+    public void update(DeviceDTO deviceDTO) {
+        super.update(deviceDTO);
+
+        this.addStringState(CHANNEL_CONNECTIVITY_STATUS, deviceDTO);
+        this.addStringState(CHANNEL_MODE_STATION, deviceDTO);
+        this.addWattQuantity(CHANNEL_POWER_AC_IN, deviceDTO);
+        this.addWattHourQuantity(CHANNEL_WORK_AC_IN, deviceDTO);
+        this.addWattHourQuantity(CHANNEL_WORK_AC_IN_SESSION, deviceDTO);
+    }
+
+    @Override
+    protected String getSolarWattLabel() {
+        return "EVStation";
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/EnergyManager.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/EnergyManager.java
new file mode 100644 (file)
index 0000000..1be1de5
--- /dev/null
@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+
+/**
+ * The energy manager itself which aggregates all other devices.
+ *
+ * This fields have been identified to exist:
+ * com.kiwigrid.devices.em.EnergyManager=[
+ * IdInterfacesMap,
+ * DateCloudLastSeen,
+ * IdBootLoaderVersion,
+ * URLProxy,
+ * IdTimezone,
+ * FractionCPULoadAverageLastFifteenMinutes,
+ * FractionCPULoadAverageLastFiveMinutes,
+ * IdSystemImageVersion,
+ * VersionPackagesMap,
+ * IdNotInstalledDevicesMap,
+ * StatusMonitoringMap,
+ * FractionCPULoadUser,
+ * VersionExtensionsMap,
+ * InstallConfiguration,
+ * URLCloud,
+ * SettingsProxyMap,
+ * IdDevicesMap,
+ * SettingsMap,
+ * ReasonReboots,
+ * IdDriverList,
+ * TimeSinceStart,
+ * ExchangeDevice,
+ * SettingsPrivacyMap,
+ * FractionTopFiveProcessesMap,
+ * StateAction,
+ * FractionCPULoadAverageLastMinute,
+ * SettingsNetworkMap,
+ * SettingsDatetimeMap,
+ * FractionCPULoadKernel,
+ * FractionCPULoadTotal,
+ * IdRemoteAppSet,
+ * IdOwner,
+ * VersionLocalApplicationsMap,
+ * AddressLocation,
+ * Command,
+ * DriverMap,
+ * DateTagCollectorWatchdogEvents,
+ * LocationGeographical
+ * ]
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class EnergyManager extends Device {
+    public static final String SOLAR_WATT_CLASSNAME = "com.kiwigrid.devices.em.EnergyManager";
+
+    public EnergyManager(DeviceDTO deviceDTO) {
+        super(deviceDTO);
+    }
+
+    @Override
+    public void update(DeviceDTO deviceDTO) {
+        super.update(deviceDTO);
+
+        this.addSecondsQuantity(CHANNEL_TIMESTAMP.getChannelName(), "SettingsDatetimeMap", ".timestamp", deviceDTO);
+        this.addStringState(CHANNEL_IDTIMEZONE, deviceDTO);
+        this.addPercentQuantity(CHANNEL_FRACTION_CPU_LOAD_TOTAL, deviceDTO);
+        this.addPercentQuantity(CHANNEL_FRACTION_CPU_LOAD_USER, deviceDTO);
+        this.addPercentQuantity(CHANNEL_FRACTION_CPU_LOAD_KERNEL, deviceDTO);
+        this.addPercentQuantity(CHANNEL_FRACTION_CPU_LOAD_AVERAGE_LAST_MINUTE, deviceDTO);
+        this.addPercentQuantity(CHANNEL_FRACTION_CPU_LOAD_AVERAGE_LAST_FIVE_MINUTES, deviceDTO);
+        this.addPercentQuantity(CHANNEL_FRACTION_CPU_LOAD_AVERAGE_LAST_FIFTEEN_MINUTES, deviceDTO);
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/Forecast.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/Forecast.java
new file mode 100644 (file)
index 0000000..3e7184f
--- /dev/null
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+
+/**
+ * Class for the weather forecast used by the energy manager to predict the produced power
+ * and plan the battery charging.
+ *
+ * This fields have been identified to exist:
+ * com.solarwatt.devices.forecast.Forecast=[
+ * CorrectionFactors,
+ * DiffuseCorrectionFactors,
+ * DateNextYieldTrendScheduler,
+ * ModePreventPVForecast,
+ * WeatherForecast,
+ * ForecastProperties,
+ * DateNextConsumptionTrendScheduler,
+ * DateNextFactorScheduler,
+ * WeatherAPIKey,
+ * WeatherHistory
+ * ]
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class Forecast extends Device {
+    public static final String SOLAR_WATT_CLASSNAME = "com.solarwatt.devices.forecast.Forecast";
+
+    public Forecast(DeviceDTO deviceDTO) {
+        super(deviceDTO);
+    }
+
+    @Override
+    protected String getSolarWattLabel() {
+        return "Forecast";
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/GridFlow.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/GridFlow.java
new file mode 100644 (file)
index 0000000..2fa6366
--- /dev/null
@@ -0,0 +1,224 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.CHANNEL_CURRENT_LIMIT;
+import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.CHANNEL_FEED_IN_LIMIT;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.Units;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonObject;
+
+/**
+ * Class planning the energy flow out/into the grid.
+ *
+ * This fields have been identified to exist:
+ * com.kiwigrid.kiwiapp.gridflow.GridFlow=[
+ * ConfigTimeshifting,
+ * ConfigEvStationControl,
+ * FractionPVTestLimit,
+ * ConfigInverterControl,
+ * LogLevel,
+ * PowerSetpoint,
+ * ConfigPeakshaving,
+ * ToEMRequestTag,
+ * CurrentLimit,
+ * ConfigBatteryControl,
+ * ConfigForecastBatteryControl,
+ * ConfigEvStationChargingControl,
+ * ConfigSgReady,
+ * ToCloudDataTag
+ * ]
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class GridFlow extends Device {
+    public static final String SOLAR_WATT_CLASSNAME = "com.kiwigrid.kiwiapp.gridflow.GridFlow";
+    private final Logger logger = LoggerFactory.getLogger(GridFlow.class);
+
+    private @Nullable ConfigInverterControl configInverterControl;
+
+    public GridFlow(DeviceDTO deviceDTO) {
+        super(deviceDTO);
+    }
+
+    @Override
+    public void update(DeviceDTO deviceDTO) {
+        super.update(deviceDTO);
+
+        this.addAmpereQuantity(CHANNEL_CURRENT_LIMIT, deviceDTO, true);
+
+        try {
+            JsonObject rawConfigInverterControl = deviceDTO.getJsonObjectFromTag("ConfigInverterControl");
+            Gson gson = new GsonBuilder().create();
+            this.configInverterControl = gson.fromJson(rawConfigInverterControl, GridFlow.ConfigInverterControl.class);
+        } catch (Exception ex) {
+            this.configInverterControl = null;
+            this.logger.warn("Could not read ConfigInverterControl", ex);
+        }
+
+        GridFlow.ConfigInverterControl localConfigInverterControl = this.configInverterControl;
+        if (localConfigInverterControl != null) {
+            // the only interesting value is the derailing (i.e. limit power flowing into the grid)
+            BigDecimal feedInLimit = localConfigInverterControl.getFeedInLimit();
+            if (feedInLimit != null) {
+                QuantityType<?> state = new QuantityType<>(feedInLimit.multiply(BigDecimal.valueOf(100)),
+                        Units.PERCENT);
+                this.stateValues.put(CHANNEL_FEED_IN_LIMIT.getChannelName(), state);
+
+                this.addChannel(CHANNEL_FEED_IN_LIMIT.getChannelName(), Units.PERCENT, "status", false);
+            }
+        }
+    }
+
+    @Override
+    protected String getSolarWattLabel() {
+        return "GridFlow";
+    }
+
+    public static class ConfigInverterControl {
+        private @Nullable BigDecimal testModeFeedInLimit;
+        private @Nullable ControllerParameter controllerParameter;
+        private @Nullable Boolean dynamicControllingOn;
+        private @Nullable BigDecimal feedInLimit;
+        private @Nullable DynamicControllerParameter dynamicControllerParameter;
+        private @Nullable Boolean on;
+        private @Nullable Boolean isErrorPVLimiting;
+        private @Nullable Boolean activeTestMode;
+        private @Nullable Boolean isPVPlantConfigured;
+        private @Nullable String selectedRcr;
+        private @Nullable Boolean limitByRcr;
+
+        public @Nullable BigDecimal getFeedInLimit() {
+            return this.feedInLimit;
+        }
+
+        public @Nullable BigDecimal getTestModeFeedInLimit() {
+            return this.testModeFeedInLimit;
+        }
+
+        public @Nullable ControllerParameter getControllerParameter() {
+            return this.controllerParameter;
+        }
+
+        public @Nullable Boolean getDynamicControllingOn() {
+            return this.dynamicControllingOn;
+        }
+
+        public @Nullable DynamicControllerParameter getDynamicControllerParameter() {
+            return this.dynamicControllerParameter;
+        }
+
+        public @Nullable Boolean getOn() {
+            return this.on;
+        }
+
+        public @Nullable Boolean getErrorPVLimiting() {
+            return this.isErrorPVLimiting;
+        }
+
+        public @Nullable Boolean getActiveTestMode() {
+            return this.activeTestMode;
+        }
+
+        public @Nullable Boolean getPVPlantConfigured() {
+            return this.isPVPlantConfigured;
+        }
+
+        public @Nullable String getSelectedRcr() {
+            return this.selectedRcr;
+        }
+
+        public @Nullable Boolean getLimitByRcr() {
+            return this.limitByRcr;
+        }
+
+        public static class ControllerParameter {
+            private @Nullable BigDecimal integrationRate;
+            private @Nullable BigInteger outputRampRateLimit;
+            private @Nullable BigDecimal differentialRate;
+            private @Nullable BigDecimal proportionalRate;
+            private @Nullable BigInteger outputValueLimit;
+            private @Nullable BigInteger controlFaultSumLimit;
+
+            public @Nullable BigDecimal getIntegrationRate() {
+                return this.integrationRate;
+            }
+
+            public @Nullable BigInteger getOutputRampRateLimit() {
+                return this.outputRampRateLimit;
+            }
+
+            public @Nullable BigDecimal getDifferentialRate() {
+                return this.differentialRate;
+            }
+
+            public @Nullable BigDecimal getProportionalRate() {
+                return this.proportionalRate;
+            }
+
+            public @Nullable BigInteger getOutputValueLimit() {
+                return this.outputValueLimit;
+            }
+
+            public @Nullable BigInteger getControlFaultSumLimit() {
+                return this.controlFaultSumLimit;
+            }
+        }
+
+        public static class DynamicControllerParameter {
+            private @Nullable BigDecimal integrationRate;
+            private @Nullable BigInteger outputRampRateLimit;
+            private @Nullable BigDecimal differentialRate;
+            private @Nullable BigDecimal proportionalRate;
+            private @Nullable BigInteger outputValueLimit;
+            private @Nullable BigInteger controlFaultSumLimit;
+
+            public @Nullable BigDecimal getIntegrationRate() {
+                return this.integrationRate;
+            }
+
+            public @Nullable BigInteger getOutputRampRateLimit() {
+                return this.outputRampRateLimit;
+            }
+
+            public @Nullable BigDecimal getDifferentialRate() {
+                return this.differentialRate;
+            }
+
+            public @Nullable BigDecimal getProportionalRate() {
+                return this.proportionalRate;
+            }
+
+            public @Nullable BigInteger getOutputValueLimit() {
+                return this.outputValueLimit;
+            }
+
+            public @Nullable BigInteger getControlFaultSumLimit() {
+                return this.controlFaultSumLimit;
+            }
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/Inverter.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/Inverter.java
new file mode 100644 (file)
index 0000000..565ee0c
--- /dev/null
@@ -0,0 +1,61 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+
+/**
+ * Base class for all inverters.
+ *
+ * This fields have been identified to exist:
+ * com.kiwigrid.devices.inverter.Inverter=[
+ * WorkACOut,
+ * PowerInstalledPeak,
+ * PowerStringDCIn,
+ * PowerACOutMax,
+ * PowerACOut,
+ * PowerACOutLimit,
+ * ACPower,
+ * PowerYieldSum
+ * ]
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class Inverter extends Device {
+    public static final String SOLAR_WATT_CLASSNAME = "com.kiwigrid.devices.inverter.Inverter";
+
+    public Inverter(DeviceDTO deviceDTO) {
+        super(deviceDTO);
+    }
+
+    @Override
+    public void update(DeviceDTO deviceDTO) {
+        super.update(deviceDTO);
+
+        this.addWattHourQuantity(CHANNEL_WORK_AC_OUT, deviceDTO);
+        this.addWattQuantity(CHANNEL_POWER_AC_OUT_MAX, deviceDTO);
+        this.addWattQuantity(CHANNEL_POWER_AC_OUT, deviceDTO);
+        this.addWattQuantity(CHANNEL_POWER_AC_OUT_LIMIT, deviceDTO);
+
+        this.addWattQuantity(CHANNEL_POWER_INSTALLED_PEAK, deviceDTO, true);
+    }
+
+    @Override
+    protected String getSolarWattLabel() {
+        return "Inverter";
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/KebaEv.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/KebaEv.java
new file mode 100644 (file)
index 0000000..54bdcd9
--- /dev/null
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+
+/**
+ * Specialised class for the Keba Wallbox.
+ *
+ * No additional fields where found.
+ * com.kiwigrid.devices.keba.ev.KebaEv=[]
+ * 
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class KebaEv extends EVStation {
+    public static final String SOLAR_WATT_CLASSNAME = "com.kiwigrid.devices.keba.ev.KebaEv";
+
+    public KebaEv(DeviceDTO deviceDTO) {
+        super(deviceDTO);
+    }
+
+    @Override
+    protected String getSolarWattLabel() {
+        return "KebaEVStation";
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/Location.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/Location.java
new file mode 100644 (file)
index 0000000..2370199
--- /dev/null
@@ -0,0 +1,208 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.*;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonObject;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Class that aggregates all devices which are found in one location
+ * and are working together to produce power.
+ *
+ * This fields have been identified to exist:
+ * com.kiwigrid.devices.location.Location=[
+ * WorkBufferedFromProducers,
+ * WorkOutFromProducers,
+ * PowerBufferedFromGrid,
+ * WorkProduced,
+ * WorkConsumedFromStorage,
+ * PowerSelfConsumed,
+ * PowerConsumedFromProducers,
+ * PowerConsumedFromStorage,
+ * PowerOutFromProducers,
+ * DatePowerProductionForecastStart,
+ * PowerOut,
+ * PowerProductionForecastNow,
+ * PowerOutFromStorage,
+ * IdDevicesMap,
+ * PowerBuffered,
+ * TimePowerProductionForecastGranularity,
+ * WorkOutFromStorage,
+ * CountPersons,
+ * DatePowerConsumptionForecastStart,
+ * PowerConsumptionForecastNow,
+ * PowerProduced,
+ * WorkBuffered,
+ * WorkConsumedFromProducers,
+ * PowerConsumed,
+ * WorkConsumedFromGrid,
+ * PowerConsumptionForecastValues,
+ * PowerSelfSupplied,
+ * WorkReleased,
+ * PowerConsumedFromGrid,
+ * TimePowerConsumptionForecastGranularity,
+ * WorkConsumed,
+ * AddressLocation,
+ * WorkIn,
+ * PriceWorkIn,
+ * PowerIn,
+ * WorkBufferedFromGrid,
+ * WorkSelfConsumed,
+ * PowerProductionForecastValues,
+ * LocationGeographical,
+ * PowerBufferedFromProducers,
+ * PowerReleased,
+ * WorkOut,
+ * WorkSelfSupplied
+ * ]
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class Location extends Device {
+    private final Logger logger = LoggerFactory.getLogger(Location.class);
+    public static final String SOLAR_WATT_CLASSNAME = "com.kiwigrid.devices.location.Location";
+
+    private @Nullable IdDevicesMap devicesMap;
+
+    public Location(DeviceDTO deviceDTO) {
+        super(deviceDTO);
+    }
+
+    @Override
+    public void update(DeviceDTO deviceDTO) {
+        super.update(deviceDTO);
+
+        // values to put on a overview dashboard
+        this.addWattQuantity(CHANNEL_POWER_BUFFERED, deviceDTO);
+        this.addWattQuantity(CHANNEL_POWER_SELF_CONSUMED, deviceDTO);
+        this.addWattQuantity(CHANNEL_POWER_SELF_SUPPLIED, deviceDTO);
+        this.addWattQuantity(CHANNEL_POWER_CONSUMED_FROM_GRID, deviceDTO);
+        this.addWattQuantity(CHANNEL_POWER_CONSUMED_FROM_STORAGE, deviceDTO);
+        this.addWattQuantity(CHANNEL_POWER_CONSUMED, deviceDTO);
+        this.addWattQuantity(CHANNEL_POWER_PRODUCED, deviceDTO);
+        this.addWattQuantity(CHANNEL_POWER_OUT, deviceDTO);
+        this.addWattHourQuantity(CHANNEL_WORK_BUFFERED, deviceDTO);
+        this.addWattHourQuantity(CHANNEL_WORK_SELF_CONSUMED, deviceDTO);
+        this.addWattHourQuantity(CHANNEL_WORK_SELF_SUPPLIED, deviceDTO);
+        this.addWattHourQuantity(CHANNEL_WORK_CONSUMED_FROM_GRID, deviceDTO);
+        this.addWattHourQuantity(CHANNEL_WORK_CONSUMED_FROM_STORAGE, deviceDTO);
+        this.addWattHourQuantity(CHANNEL_WORK_CONSUMED, deviceDTO);
+        this.addWattHourQuantity(CHANNEL_WORK_PRODUCED, deviceDTO);
+        this.addWattHourQuantity(CHANNEL_WORK_OUT, deviceDTO);
+
+        // not necessary for a dashboard, so marked as advanced
+        this.addWattQuantity(CHANNEL_POWER_BUFFERED_FROM_GRID, deviceDTO, true);
+        this.addWattQuantity(CHANNEL_POWER_BUFFERED_FROM_PRODUCERS, deviceDTO, true);
+        this.addWattQuantity(CHANNEL_POWER_CONSUMED_FROM_PRODUCERS, deviceDTO, true);
+        this.addWattQuantity(CHANNEL_POWER_IN, deviceDTO, true);
+        this.addWattQuantity(CHANNEL_POWER_OUT_FROM_PRODUCERS, deviceDTO, true);
+        this.addWattQuantity(CHANNEL_POWER_OUT_FROM_STORAGE, deviceDTO, true);
+        this.addWattQuantity(CHANNEL_POWER_RELEASED, deviceDTO, true);
+        this.addWattHourQuantity(CHANNEL_WORK_BUFFERED_FROM_GRID, deviceDTO, true);
+        this.addWattHourQuantity(CHANNEL_WORK_BUFFERED_FROM_PRODUCERS, deviceDTO, true);
+        this.addWattHourQuantity(CHANNEL_WORK_CONSUMED_FROM_PRODUCERS, deviceDTO, true);
+        this.addWattHourQuantity(CHANNEL_WORK_OUT_FROM_PRODUCERS, deviceDTO, true);
+        this.addWattHourQuantity(CHANNEL_WORK_OUT_FROM_STORAGE, deviceDTO, true);
+        this.addWattHourQuantity(CHANNEL_WORK_RELEASED, deviceDTO, true);
+
+        // read IdDevicesMap to find out which devices are located/metered where
+        // to get the unmetered consumption we need for {@link LocationHandler} to take Location.(Work|Power)Consumed
+        // and subtract the (Work|Power)(AC)In of the OUTER_CONSUMERs
+        try {
+            JsonObject rawDevicesMap = deviceDTO.getJsonObjectFromTag("IdDevicesMap");
+            Gson gson = new GsonBuilder().create();
+            this.devicesMap = gson.fromJson(rawDevicesMap, IdDevicesMap.class);
+        } catch (Exception ex) {
+            this.devicesMap = null;
+            this.logger.warn("Could not read IdDevicesMap", ex);
+        }
+    }
+
+    public IdDevicesMap getDevicesMap() {
+        IdDevicesMap returnDevicesMap = this.devicesMap;
+        if (returnDevicesMap != null) {
+            return returnDevicesMap;
+        }
+
+        return new IdDevicesMap();
+    }
+
+    @Override
+    protected String getSolarWattLabel() {
+        return "Location";
+    }
+
+    public static class IdDevicesMap {
+        @SerializedName("INNER_BUFFER")
+        private @Nullable Set<String> innerBuffer;
+        @SerializedName("INNER_CONSUMER")
+        private @Nullable Set<String> innerConsumer;
+        @SerializedName("POWERMETER_CONSUMPTION")
+        private @Nullable Set<String> powermeterConsumption;
+        @SerializedName("OUTER_CONSUMER")
+        private @Nullable Set<String> outerConsumer;
+        @SerializedName("OUTER_BUFFER")
+        private @Nullable Set<String> outerBuffer;
+        @SerializedName("POWERMETER_PRODUCTION")
+        private @Nullable Set<String> powermeterProduction;
+        @SerializedName("OUTER_PRODUCER")
+        private @Nullable Set<String> outerProducer;
+        @SerializedName("INNER_PRODUCER")
+        private @Nullable Set<String> innerProducer;
+
+        public @Nullable Set<String> getInnerBuffer() {
+            return this.innerBuffer;
+        }
+
+        public @Nullable Set<String> getInnerConsumer() {
+            return this.innerConsumer;
+        }
+
+        public @Nullable Set<String> getPowermeterConsumption() {
+            return this.powermeterConsumption;
+        }
+
+        public @Nullable Set<String> getOuterConsumer() {
+            return this.outerConsumer;
+        }
+
+        public @Nullable Set<String> getOuterBuffer() {
+            return this.outerBuffer;
+        }
+
+        public @Nullable Set<String> getPowermeterProduction() {
+            return this.powermeterProduction;
+        }
+
+        public @Nullable Set<String> getOuterProducer() {
+            return this.outerProducer;
+        }
+
+        public @Nullable Set<String> getInnerProducer() {
+            return this.innerProducer;
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/MyReserve.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/MyReserve.java
new file mode 100644 (file)
index 0000000..f18759b
--- /dev/null
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+
+/**
+ * Specialised class for the MyReserve battery.
+ *
+ * The MyReserve also contains {@link MyReservePowerMeter} and {@link MyReserveInverter}.
+ *
+ * No additional fields where found.
+ * com.kiwigrid.devices.solarwatt.MyReserve=[]
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class MyReserve extends BatteryConverter {
+    public static final String SOLAR_WATT_CLASSNAME = "com.kiwigrid.devices.solarwatt.MyReserve";
+
+    public MyReserve(DeviceDTO deviceDTO) {
+        super(deviceDTO);
+    }
+
+    @Override
+    protected String getSolarWattLabel() {
+        return "BatteryConverter";
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/MyReserveInverter.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/MyReserveInverter.java
new file mode 100644 (file)
index 0000000..dc53f32
--- /dev/null
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+
+/**
+ * Specialised class for the MyReserve inverter.
+ *
+ * The MyReserve also contains {@link MyReservePowerMeter} and {@link MyReserve}.
+ * This fields have been identified to exist:
+ * com.kiwigrid.devices.solarwatt.MyReserveInverter=[
+ * PowerBatteryDC,
+ * IdMyReserveSetupRole,
+ * IdMyReserveSetup,
+ * IdDcCoupledBatteriesList,
+ * IdInverter,
+ * Command
+ * ]
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class MyReserveInverter extends Inverter {
+    public static final String SOLAR_WATT_CLASSNAME = "com.kiwigrid.devices.solarwatt.MyReserveInverter";
+
+    public MyReserveInverter(DeviceDTO deviceDTO) {
+        super(deviceDTO);
+    }
+
+    @Override
+    protected String getSolarWattLabel() {
+        return "MyReserveInverter";
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/MyReservePowerMeter.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/MyReservePowerMeter.java
new file mode 100644 (file)
index 0000000..b118ad0
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+
+/**
+ * Specialised class for the MyReserve powermeter.
+ *
+ * The MyReserve also contains {@link MyReserve} and {@link MyReserveInverter}.
+ *
+ * This fields have been identified to exist:
+ * com.kiwigrid.devices.solarwatt.MyReservePowermeter=[
+ * PpmScaleFactor,
+ * IdAcsVersionNumber
+ * ]
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class MyReservePowerMeter extends PowerMeter {
+    public static final String SOLAR_WATT_CLASSNAME = "com.kiwigrid.devices.powermeter.PowerMeter";
+
+    public MyReservePowerMeter(DeviceDTO deviceDTO) {
+        super(deviceDTO);
+    }
+
+    @Override
+    protected String getSolarWattLabel() {
+        return "MyReservePowerMeter";
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/PVPlant.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/PVPlant.java
new file mode 100644 (file)
index 0000000..36b7250
--- /dev/null
@@ -0,0 +1,73 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.CHANNEL_POWER_AC_OUT;
+import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.CHANNEL_WORK_AC_OUT;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+
+/**
+ * Class to represent the producing parts of the photovoltaic installation.
+ *
+ * This fields have been identified to exist:
+ * com.kiwigrid.devices.pvplant.PVPlant=[
+ * PowerACOutMax,
+ * IdInverterList,
+ * TimePowerOutForecastGranularity,
+ * ForecastDateUpdate,
+ * WorkACOut,
+ * DegreeDirection,
+ * ForecastPowerOut,
+ * WorkAnnualYield,
+ * PowerACOut,
+ * DatePowerOutForecastStart,
+ * PowerLimit,
+ * IdMountingType,
+ * FractionDeratingLimit,
+ * DateInstallation,
+ * ForecastWorkOut,
+ * PowerOutForecastNow,
+ * PriceProfitFeedin,
+ * AddressLocation,
+ * PowerOutForecastValues,
+ * FractionConfigDeratingLimit,
+ * PowerInstalledPeak,
+ * LocationGeographical,
+ * DegreeInclination
+ * ]
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class PVPlant extends Device {
+    public static final String SOLAR_WATT_CLASSNAME = "com.kiwigrid.devices.pvplant.PVPlant";
+
+    public PVPlant(DeviceDTO deviceDTO) {
+        super(deviceDTO);
+    }
+
+    @Override
+    public void update(DeviceDTO deviceDTO) {
+        super.update(deviceDTO);
+
+        this.addWattQuantity(CHANNEL_POWER_AC_OUT, deviceDTO);
+        this.addWattHourQuantity(CHANNEL_WORK_AC_OUT, deviceDTO);
+    }
+
+    @Override
+    protected String getSolarWattLabel() {
+        return "PVPlant";
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/PowerMeter.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/PowerMeter.java
new file mode 100644 (file)
index 0000000..de611f5
--- /dev/null
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+
+/**
+ * Base class for all powermeters.
+ *
+ * This fields have been identified to exist:
+ * com.kiwigrid.devices.powermeter.PowerMeter=[
+ * InductiveEnergySum,
+ * ACCurrentConsumptionL1,
+ * ACCurrentConsumptionL2,
+ * ACCurrentN,
+ * ACCurrentConsumptionL3,
+ * ApparentPowerL2,
+ * ConsumptionPowerL3,
+ * ConsumptionPowerL2,
+ * ApparentPowerL3,
+ * ApparentPowerL1,
+ * ConsumptionPowerNet,
+ * ReactivePowerL1,
+ * ReactivePowerL2,
+ * InductiveEnergyL2,
+ * InductiveEnergyL1,
+ * ReactivePowerL3,
+ * CapacitiveEnergyL2,
+ * CapacitiveEnergyL1,
+ * PhaseOfMaximumImbalance,
+ * InjectionPowerL1,
+ * ReactivePowerSum,
+ * ActivePowerL3,
+ * InjectionPowerL2,
+ * InjectionPowerL3,
+ * ActivePowerL1,
+ * ConsumptionPowerL1,
+ * InjectionEnergySum,
+ * ActivePowerL2,
+ * ImbalanceL1,
+ * InjectionPowerNet,
+ * ImbalanceL2,
+ * ImbalanceL3,
+ * CapacitiveEnergyL3,
+ * DirectionMetering,
+ * ConsumptionEnergySum,
+ * PowerOut,
+ * InjectionEnergyNet,
+ * ACVoltageL1,
+ * ACVoltageL2,
+ * ACVoltageL3,
+ * MaximumImbalance,
+ * CosinusPhiL3,
+ * CosinusPhiL2,
+ * CosinusPhiL1,
+ * FrequencyL1,
+ * FrequencyL3,
+ * FrequencyL2,
+ * ACCurrentInjectionL3,
+ * InjectionEnergyL3,
+ * ACCurrentInjectionL2,
+ * ConsumptionPowerSum,
+ * ACCurrentInjectionL1,
+ * InjectionEnergyL1,
+ * InjectionEnergyL2,
+ * ActivePowerSum,
+ * ConsumptionEnergyL2,
+ * ConsumptionEnergyL1,
+ * PowerFactorL1,
+ * PowerFactorL2,
+ * InjectionPowerSum,
+ * ConsumptionEnergyL3,
+ * PowerFactorL3,
+ * ACCurrentL3,
+ * CapacitiveEnergySum,
+ * WorkIn,
+ * InductiveEnergyL3,
+ * PowerIn,
+ * ACCurrentL1,
+ * ACCurrentL2,
+ * FrequencyGrid,
+ * ConsumptionEnergyNet,
+ * ApparentPowerSum,
+ * WorkOut
+ * ]
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class PowerMeter extends Device {
+    public static final String SOLAR_WATT_CLASSNAME = "com.kiwigrid.devices.powermeter.PowerMeter";
+
+    public PowerMeter(DeviceDTO deviceDTO) {
+        super(deviceDTO);
+
+        this.addStringState(CHANNEL_DIRECTION_METERING, deviceDTO);
+        this.addWattQuantity(CHANNEL_POWER_IN, deviceDTO);
+        this.addWattQuantity(CHANNEL_POWER_OUT, deviceDTO);
+        this.addWattHourQuantity(CHANNEL_WORK_IN, deviceDTO);
+        this.addWattHourQuantity(CHANNEL_WORK_OUT, deviceDTO);
+        this.addWattHourQuantity(CHANNEL_CONSUMPTION_ENERGY_SUM, deviceDTO);
+    }
+
+    @Override
+    protected String getSolarWattLabel() {
+        return "PowerMeter";
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/ProfileApp.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/ProfileApp.java
new file mode 100644 (file)
index 0000000..5ab3690
--- /dev/null
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+
+/**
+ * Class for things I haven't got a clue off.
+ *
+ * This fields have been identified to exist:
+ * com.kiwigrid.application.schedule.ProfileApp=[
+ * IdPriorizedDeviceList,
+ * ProfilesList,
+ * IdProfileActive
+ * ]
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class ProfileApp extends Device {
+    public static final String SOLAR_WATT_CLASSNAME = "com.kiwigrid.application.schedule.ProfileApp";
+
+    public ProfileApp(DeviceDTO deviceDTO) {
+        super(deviceDTO);
+    }
+
+    @Override
+    protected String getSolarWattLabel() {
+        return "ProfileApp";
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/S0Counter.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/S0Counter.java
new file mode 100644 (file)
index 0000000..e7c4fcf
--- /dev/null
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+
+/**
+ * Specialised class for the powermeter connected via S0 bus.
+ *
+ * This fields have been identified to exist:
+ * com.kiwigrid.devices.s0counter.S0Counter=[
+ * SettingRateImpulses,
+ * CountPulses
+ * ]
+ * 
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class S0Counter extends PowerMeter {
+    public static final String SOLAR_WATT_CLASSNAME = "com.kiwigrid.devices.s0counter.S0Counter";
+
+    public S0Counter(DeviceDTO deviceDTO) {
+        super(deviceDTO);
+    }
+
+    @Override
+    protected String getSolarWattLabel() {
+        return "S0Counter";
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/ScheduleApp.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/ScheduleApp.java
new file mode 100644 (file)
index 0000000..5729b9f
--- /dev/null
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+
+/**
+ * Class representing the scheduler of the energy manager.
+ *
+ * This fields have been identified to exist:
+ * com.kiwigrid.application.schedule.ScheduleApp=[
+ * ScheduleEvents,
+ * StateSchedule,
+ * ExcessMinSleepTime,
+ * ScheduleOverride,
+ * ExcessMinRuntime,
+ * ExcessMinRuntimePerDay,
+ * PowerInAverage,
+ * ExcessMinDuration,
+ * PowerLimitDefaults,
+ * SchedulesList,
+ * Schedule
+ * ]
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class ScheduleApp extends Device {
+    public static final String SOLAR_WATT_CLASSNAME = "com.kiwigrid.application.schedule.ScheduleApp";
+
+    public ScheduleApp(DeviceDTO deviceDTO) {
+        super(deviceDTO);
+    }
+
+    @Override
+    protected String getSolarWattLabel() {
+        return "ScheduleApp";
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/SimpleSwitcher.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/SimpleSwitcher.java
new file mode 100644 (file)
index 0000000..a0ee164
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+
+/**
+ * Deprecated class.
+ *
+ * Was superseded by {@link ScheduleApp}.
+ *
+ * This fields have been identified to exist:
+ * com.kiwigrid.devices.simpleswitcher.SimpleSwitcher=[
+ * MigratedToScheduleApp,
+ * ScheduleSwitchStates
+ * ]
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class SimpleSwitcher extends Device {
+    public static final String SOLAR_WATT_CLASSNAME = "com.kiwigrid.devices.simpleswitcher.SimpleSwitcher";
+
+    public SimpleSwitcher(DeviceDTO deviceDTO) {
+        super(deviceDTO);
+    }
+
+    @Override
+    protected String getSolarWattLabel() {
+        return "SimpleSwitcher";
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/SmartEnergyManagement.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/SmartEnergyManagement.java
new file mode 100644 (file)
index 0000000..5fd1818
--- /dev/null
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+
+/**
+ * Deprecated class.
+ *
+ * Was superseded by {@link ScheduleApp}.
+ *
+ * This fields have been identified to exist:
+ * com.solarwatt.devices.sem.SmartEnergyManagement=[
+ * FractionFeedInLimit,
+ * FractionFeedInTestLimit,
+ * ModeManagement,
+ * ModeActive,
+ * IdConsumerSettingsMap,
+ * IdConsumerSelectionList,
+ * ModeTestActive,
+ * PowerInSwitchedOnDevices,
+ * MigratedToScheduleApp,
+ * IdDevicesMap,
+ * IdConsumerManagementIntervalsMap,
+ * IdManageableDeviceInfo
+ * ]
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class SmartEnergyManagement extends Device {
+    public static final String SOLAR_WATT_CLASSNAME = "com.solarwatt.devices.sem.SmartEnergyManagement";
+
+    public SmartEnergyManagement(DeviceDTO deviceDTO) {
+        super(deviceDTO);
+    }
+
+    @Override
+    protected String getSolarWattLabel() {
+        return "SmartEnergyManagement";
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/SunSpecInverter.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/domain/model/SunSpecInverter.java
new file mode 100644 (file)
index 0000000..9509346
--- /dev/null
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.domain.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+
+/**
+ * Specialised class for inverters adhering to the sunspec modbus protocol.
+ *
+ * This fields have been identified to exist:
+ * com.kiwigrid.devices.sunspec.SunSpecInverter=[
+ * IdSunSpecBlocks
+ * ]
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class SunSpecInverter extends Inverter {
+    public static final String SOLAR_WATT_CLASSNAME = "com.kiwigrid.devices.sunspec.SunSpecInverter";
+
+    public SunSpecInverter(DeviceDTO deviceDTO) {
+        super(deviceDTO);
+    }
+
+    @Override
+    protected String getSolarWattLabel() {
+        return "SunSpecInverter";
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/exception/SolarwattConnectionException.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/exception/SolarwattConnectionException.java
new file mode 100644 (file)
index 0000000..07a17ff
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.exception;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Exception to be used whenever anything goes wrong talking to the energy manager.
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class SolarwattConnectionException extends Exception {
+    static final long serialVersionUID = 3387516924229948L;
+
+    public SolarwattConnectionException(final @Nullable String message) {
+        super(message);
+    }
+
+    public SolarwattConnectionException(final @Nullable String message, final Exception e) {
+        super(message, e);
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/factory/EnergyManagerDevicesFactory.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/factory/EnergyManagerDevicesFactory.java
new file mode 100644 (file)
index 0000000..ad8d6ef
--- /dev/null
@@ -0,0 +1,136 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.factory;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solarwatt.internal.domain.EnergyManagerCollection;
+import org.openhab.binding.solarwatt.internal.domain.dto.DeviceDTO;
+import org.openhab.binding.solarwatt.internal.domain.dto.EnergyManagerDTO;
+import org.openhab.binding.solarwatt.internal.domain.model.BatteryConverter;
+import org.openhab.binding.solarwatt.internal.domain.model.Device;
+import org.openhab.binding.solarwatt.internal.domain.model.EVStation;
+import org.openhab.binding.solarwatt.internal.domain.model.EnergyManager;
+import org.openhab.binding.solarwatt.internal.domain.model.Forecast;
+import org.openhab.binding.solarwatt.internal.domain.model.GridFlow;
+import org.openhab.binding.solarwatt.internal.domain.model.Inverter;
+import org.openhab.binding.solarwatt.internal.domain.model.KebaEv;
+import org.openhab.binding.solarwatt.internal.domain.model.Location;
+import org.openhab.binding.solarwatt.internal.domain.model.MyReserve;
+import org.openhab.binding.solarwatt.internal.domain.model.MyReserveInverter;
+import org.openhab.binding.solarwatt.internal.domain.model.MyReservePowerMeter;
+import org.openhab.binding.solarwatt.internal.domain.model.PVPlant;
+import org.openhab.binding.solarwatt.internal.domain.model.PowerMeter;
+import org.openhab.binding.solarwatt.internal.domain.model.ProfileApp;
+import org.openhab.binding.solarwatt.internal.domain.model.S0Counter;
+import org.openhab.binding.solarwatt.internal.domain.model.ScheduleApp;
+import org.openhab.binding.solarwatt.internal.domain.model.SimpleSwitcher;
+import org.openhab.binding.solarwatt.internal.domain.model.SmartEnergyManagement;
+import org.openhab.binding.solarwatt.internal.domain.model.SunSpecInverter;
+
+/**
+ * Factory to produce concrete instances which match the device structure returned by the energy manager.
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class EnergyManagerDevicesFactory {
+    /**
+     * Generate a concrete collection of devices which where discovered by the energy manager.
+     *
+     * @param energyManagerDTO raw transfer object generated from json
+     * @return collection of all devices
+     */
+    public static EnergyManagerCollection getEnergyManagerCollection(EnergyManagerDTO energyManagerDTO) {
+        return new EnergyManagerCollection(energyManagerDTO);
+    }
+
+    /**
+     * Generate one concrete device instance from one raw device.
+     *
+     * Specific implementation to use is determined by looking at the
+     * deviceModels supported by the device and prioritising them according from the bottom up.
+     *
+     * @param deviceDTO raw transfer object for one device
+     * @return device fille from the {@link DeviceDTO}
+     */
+    public static @Nullable Device getEnergyManagerDevice(DeviceDTO deviceDTO) {
+        // objects on level 3
+        if (deviceDTO.getDeviceModelStrings().contains(MyReserve.SOLAR_WATT_CLASSNAME)) {
+            return new MyReserve(deviceDTO);
+        }
+        // objects on level 2
+        if (deviceDTO.getDeviceModelStrings().contains(S0Counter.SOLAR_WATT_CLASSNAME)) {
+            return new S0Counter(deviceDTO);
+        }
+        if (deviceDTO.getDeviceModelStrings().contains(KebaEv.SOLAR_WATT_CLASSNAME)) {
+            return new KebaEv(deviceDTO);
+        }
+        if (deviceDTO.getDeviceModelStrings().contains(MyReservePowerMeter.SOLAR_WATT_CLASSNAME)) {
+            return new MyReservePowerMeter(deviceDTO);
+        }
+        if (deviceDTO.getDeviceModelStrings().contains(SunSpecInverter.SOLAR_WATT_CLASSNAME)) {
+            return new SunSpecInverter(deviceDTO);
+        }
+        if (deviceDTO.getDeviceModelStrings().contains(MyReserveInverter.SOLAR_WATT_CLASSNAME)) {
+            return new MyReserveInverter(deviceDTO);
+        }
+        if (deviceDTO.getDeviceModelStrings().contains(BatteryConverter.SOLAR_WATT_CLASSNAME)) {
+            return new BatteryConverter(deviceDTO);
+        }
+        // objects on level 1
+        if (deviceDTO.getDeviceModelStrings().contains(ScheduleApp.SOLAR_WATT_CLASSNAME)) {
+            return new ScheduleApp(deviceDTO);
+        }
+        if (deviceDTO.getDeviceModelStrings().contains(SmartEnergyManagement.SOLAR_WATT_CLASSNAME)) {
+            return new SmartEnergyManagement(deviceDTO);
+        }
+        if (deviceDTO.getDeviceModelStrings().contains(EnergyManager.SOLAR_WATT_CLASSNAME)) {
+            return new EnergyManager(deviceDTO);
+        }
+        if (deviceDTO.getDeviceModelStrings().contains(Forecast.SOLAR_WATT_CLASSNAME)) {
+            return new Forecast(deviceDTO);
+        }
+        if (deviceDTO.getDeviceModelStrings().contains(Location.SOLAR_WATT_CLASSNAME)) {
+            return new Location(deviceDTO);
+        }
+        if (deviceDTO.getDeviceModelStrings().contains(EVStation.SOLAR_WATT_CLASSNAME)) {
+            return new EVStation(deviceDTO);
+        }
+        if (deviceDTO.getDeviceModelStrings().contains(PowerMeter.SOLAR_WATT_CLASSNAME)) {
+            return new PowerMeter(deviceDTO);
+        }
+        if (deviceDTO.getDeviceModelStrings().contains(SimpleSwitcher.SOLAR_WATT_CLASSNAME)) {
+            return new SimpleSwitcher(deviceDTO);
+        }
+        if (deviceDTO.getDeviceModelStrings().contains(GridFlow.SOLAR_WATT_CLASSNAME)) {
+            return new GridFlow(deviceDTO);
+        }
+        if (deviceDTO.getDeviceModelStrings().contains(PVPlant.SOLAR_WATT_CLASSNAME)) {
+            return new PVPlant(deviceDTO);
+        }
+        if (deviceDTO.getDeviceModelStrings().contains(Inverter.SOLAR_WATT_CLASSNAME)) {
+            return new Inverter(deviceDTO);
+        }
+        if (deviceDTO.getDeviceModelStrings().contains(ProfileApp.SOLAR_WATT_CLASSNAME)) {
+            return new ProfileApp(deviceDTO);
+        }
+
+        // Objects on level 0
+        if (deviceDTO.getDeviceModelStrings().contains(Device.SOLAR_WATT_CLASSNAME)) {
+            return new Device(deviceDTO);
+        }
+
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/handler/EnergyManagerConnector.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/handler/EnergyManagerConnector.java
new file mode 100644 (file)
index 0000000..eaf384b
--- /dev/null
@@ -0,0 +1,122 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.handler;
+
+import java.net.URI;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpStatus;
+import org.openhab.binding.solarwatt.internal.configuration.SolarwattBridgeConfiguration;
+import org.openhab.binding.solarwatt.internal.domain.EnergyManagerCollection;
+import org.openhab.binding.solarwatt.internal.domain.dto.EnergyManagerDTO;
+import org.openhab.binding.solarwatt.internal.exception.SolarwattConnectionException;
+import org.openhab.binding.solarwatt.internal.factory.EnergyManagerDevicesFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * Class to talk to the energy anager via HTTP and return the concrete device instances.
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class EnergyManagerConnector {
+    private static final String PROTOCOL = "http://";
+    private static final String WIZARD_DEVICES_URL = "/rest/kiwigrid/wizard/devices";
+    private static final long CONNECT_TIMEOUT_SECONDS = 30;
+
+    private final Logger logger = LoggerFactory.getLogger(EnergyManagerConnector.class);
+    private final Gson gson = new GsonBuilder().create();
+    private final HttpClient httpClient;
+    private @Nullable URI energyManagerURI;
+
+    public EnergyManagerConnector(final HttpClient httpClient) {
+        this.httpClient = httpClient;
+    }
+
+    /**
+     * Pass in the configuration to know which host to talk to.
+     *
+     * @param configuration containing the hostname.
+     */
+    public void setConfiguration(final @Nullable SolarwattBridgeConfiguration configuration) {
+        if (configuration != null) {
+            String hostname = configuration.hostname;
+
+            if (!hostname.isEmpty()) {
+                this.energyManagerURI = URI.create(PROTOCOL + hostname + WIZARD_DEVICES_URL);
+            }
+        }
+    }
+
+    /**
+     * Get the collection of devices represented by the energy manager.
+     *
+     * Read the JSON and transform everything into concrete instances.
+     *
+     * @return wrapping the devices
+     * @throws SolarwattConnectionException on any communication error
+     */
+    public EnergyManagerCollection retrieveDevices() throws SolarwattConnectionException {
+        try {
+            final Request request = this.httpClient.newRequest(this.energyManagerURI).timeout(CONNECT_TIMEOUT_SECONDS,
+                    TimeUnit.SECONDS);
+            final ContentResponse response = request.send();
+
+            return this.getEnergyManagerCollectionFromJson(response);
+        } catch (final InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new SolarwattConnectionException("Interrupted");
+        } catch (TimeoutException | ExecutionException e) {
+            throw new SolarwattConnectionException("Connection problem", e);
+        }
+    }
+
+    /**
+     * Parse body content from energy manager from json into our DTO.
+     *
+     * @param response
+     * @return collection containing all {@link DeviceDTO}s
+     * @throws SolarwattConnectionException on communication errors
+     */
+    private EnergyManagerCollection getEnergyManagerCollectionFromJson(ContentResponse response)
+            throws SolarwattConnectionException {
+        final String content = response.getContentAsString();
+
+        try {
+            if (response.getStatus() == HttpStatus.OK_200) {
+                EnergyManagerDTO energyManagerDTO = this.gson.fromJson(content, EnergyManagerDTO.class);
+                if (energyManagerDTO == null) {
+                    throw new SolarwattConnectionException("No data received");
+                }
+                return EnergyManagerDevicesFactory.getEnergyManagerCollection(energyManagerDTO);
+            } else {
+                throw new SolarwattConnectionException(response.getReason());
+            }
+        } catch (final JsonSyntaxException e) {
+            this.logger.warn("Error parsing json: {}", content, e);
+            throw new SolarwattConnectionException(e.getMessage());
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/handler/EnergyManagerHandler.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/handler/EnergyManagerHandler.java
new file mode 100644 (file)
index 0000000..2106736
--- /dev/null
@@ -0,0 +1,394 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.handler;
+
+import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.*;
+
+import java.math.BigDecimal;
+import java.time.DateTimeException;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+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.solarwatt.internal.channel.SolarwattChannelTypeProvider;
+import org.openhab.binding.solarwatt.internal.configuration.SolarwattBridgeConfiguration;
+import org.openhab.binding.solarwatt.internal.configuration.SolarwattThingConfiguration;
+import org.openhab.binding.solarwatt.internal.discovery.SolarwattDevicesDiscoveryService;
+import org.openhab.binding.solarwatt.internal.domain.SolarwattChannel;
+import org.openhab.binding.solarwatt.internal.domain.model.Device;
+import org.openhab.binding.solarwatt.internal.domain.model.EnergyManager;
+import org.openhab.binding.solarwatt.internal.exception.SolarwattConnectionException;
+import org.openhab.core.cache.ExpiringCache;
+import org.openhab.core.library.types.DateTimeType;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseBridgeHandler;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.thing.binding.builder.ThingBuilder;
+import org.openhab.core.thing.type.ChannelTypeUID;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.State;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link EnergyManagerHandler} is responsible for handling energy manager thing itself
+ * and handle data retrieval for the child things.
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class EnergyManagerHandler extends BaseBridgeHandler {
+
+    private final Logger logger = LoggerFactory.getLogger(EnergyManagerHandler.class);
+
+    private final EnergyManagerConnector connector;
+    private final SolarwattChannelTypeProvider channelTypeProvider;
+
+    private @Nullable ExpiringCache<Map<String, Device>> devicesCache;
+    private @Nullable ScheduledFuture<?> refreshJob;
+
+    private @Nullable ZoneId zoneId;
+
+    /**
+     * Guid of this energy manager itself.
+     */
+    private @Nullable String energyManagerGuid;
+
+    /**
+     * Runner for the {@link ExpiringCache} refresh.
+     *
+     * Triggers update of all child things.
+     */
+    private final Runnable refreshRunnable = () -> {
+        EnergyManagerHandler.this.updateChannels();
+        EnergyManagerHandler.this.updateAllChildThings();
+    };
+
+    /**
+     * Create the handler.
+     *
+     * @param thing for which the handler is responsible
+     * @param channelTypeProvider provider for the channels
+     * @param httpClient connect to energy manager via this client
+     */
+    public EnergyManagerHandler(final Bridge thing, final SolarwattChannelTypeProvider channelTypeProvider,
+            final HttpClient httpClient) {
+        super(thing);
+        this.connector = new EnergyManagerConnector(httpClient);
+        this.channelTypeProvider = channelTypeProvider;
+    }
+
+    /**
+     * Get services which are provided by this handler.
+     *
+     * Only service discovery is provided by
+     * 
+     * @return collection containing our discovery service
+     */
+    @Override
+    public Collection<Class<? extends ThingHandlerService>> getServices() {
+        return Collections.singleton(SolarwattDevicesDiscoveryService.class);
+    }
+
+    /**
+     * Execute the desired commands.
+     *
+     * Only refresh is supported and relayed to all childs of this thing.
+     *
+     * @param channelUID for which the command is issued
+     * @param command command issued
+     */
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        if (command instanceof RefreshType) {
+            this.updateChannels();
+        }
+    }
+
+    /**
+     * Dynnamically updates all known channel states of the energy manager.
+     */
+    public void updateChannels() {
+        Map<String, Device> devices = this.getDevices();
+        if (devices != null) {
+            if (this.energyManagerGuid == null) {
+                try {
+                    this.findEnergyManagerGuid(devices);
+                } catch (SolarwattConnectionException ex) {
+                    this.logger.warn("Failed updating EnergyManager channels: {}", ex.getMessage());
+                }
+            }
+            EnergyManager energyManager = (EnergyManager) devices.get(this.energyManagerGuid);
+
+            if (energyManager != null) {
+                this.calculateUpdates(energyManager);
+
+                energyManager.getStateValues().forEach((stateName, stateValue) -> {
+                    this.updateState(stateName, stateValue);
+                });
+            } else {
+                this.logger.warn("updateChannels failed, missing device EnergyManager {}", this.energyManagerGuid);
+            }
+        }
+    }
+
+    private void calculateUpdates(EnergyManager energyManager) {
+        State timezoneState = energyManager.getState(CHANNEL_IDTIMEZONE.getChannelName());
+        if (timezoneState != null) {
+            this.zoneId = ZoneId.of(timezoneState.toFullString());
+        }
+
+        BigDecimal timestamp = energyManager.getBigDecimalFromChannel(CHANNEL_TIMESTAMP.getChannelName());
+        if (timestamp.compareTo(BigDecimal.ONE) > 0) {
+            energyManager.addState(CHANNEL_DATETIME.getChannelName(),
+                    new DateTimeType(this.getFromMilliTimestamp(timestamp)));
+        }
+    }
+
+    /**
+     * Initial setup of the channels available for this thing.
+     *
+     * @param device which provides the channels
+     */
+    protected void initDeviceChannels(Device device) {
+        this.assertChannel(new SolarwattChannel(CHANNEL_DATETIME.getChannelName(), "time"));
+
+        device.getSolarwattChannelSet().forEach((channelTag, solarwattChannel) -> {
+            this.assertChannel(solarwattChannel);
+        });
+    }
+
+    /**
+     * Assert that all channels inside of our thing are well defined.
+     *
+     * Only channel which can not be found are created.
+     *
+     * @param solarwattChannel channel description with name and unit
+     */
+    protected void assertChannel(SolarwattChannel solarwattChannel) {
+        ChannelUID channelUID = new ChannelUID(this.getThing().getUID(), solarwattChannel.getChannelName());
+        ChannelTypeUID channelType = this.channelTypeProvider.assertChannelType(solarwattChannel);
+        if (this.getThing().getChannel(channelUID) == null) {
+            ThingBuilder thingBuilder = this.editThing();
+            thingBuilder.withChannel(
+                    SimpleDeviceHandler.getChannelBuilder(solarwattChannel, channelUID, channelType).build());
+
+            this.updateThing(thingBuilder.build());
+        }
+    }
+
+    /**
+     * Finds the guid of the energy manager inside of the known devices.
+     *
+     * @param devices list with known devices
+     * @throws SolarwattConnectionException if there is no energy manager available
+     */
+    private void findEnergyManagerGuid(Map<String, Device> devices) throws SolarwattConnectionException {
+        devices.forEach((guid, device) -> {
+            if (device instanceof EnergyManager) {
+                this.energyManagerGuid = guid;
+            }
+        });
+
+        if (this.energyManagerGuid == null) {
+            throw new SolarwattConnectionException("unable to find energy manager");
+        }
+    }
+
+    /**
+     * Setup the handler and trigger initial load via {@link EnergyManagerHandler::refreshDevices}.
+     *
+     * Web request against energy manager and loading of devices is deferred and will send the ONLINE
+     * event after loading all devices.
+     */
+    @Override
+    public void initialize() {
+        SolarwattBridgeConfiguration localConfig = this.getConfigAs(SolarwattBridgeConfiguration.class);
+        this.initRefresh(localConfig);
+        this.initDeviceCache(localConfig);
+    }
+
+    private void initDeviceCache(SolarwattBridgeConfiguration localConfig) {
+        if (localConfig.hostname.isEmpty()) {
+            this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Hostname is not set");
+        } else {
+            this.updateStatus(ThingStatus.ONLINE, ThingStatusDetail.CONFIGURATION_PENDING,
+                    "Waiting to retrieve devices.");
+            this.connector.setConfiguration(localConfig);
+
+            this.devicesCache = new ExpiringCache<>(Duration.of(localConfig.refresh, ChronoUnit.SECONDS),
+                    this::refreshDevices);
+
+            ExpiringCache<Map<String, Device>> localDevicesCache = this.devicesCache;
+            if (localDevicesCache != null) {
+                // trigger initial load
+                this.scheduler.execute(localDevicesCache::getValue);
+            }
+        }
+    }
+
+    /**
+     * Stop the refresh job and remove devices.
+     */
+    @Override
+    public void dispose() {
+        ScheduledFuture<?> localRefreshJob = this.refreshJob;
+        if (localRefreshJob != null && !localRefreshJob.isCancelled()) {
+            localRefreshJob.cancel(true);
+            this.refreshJob = null;
+        }
+
+        this.devicesCache = null;
+    }
+
+    private synchronized void initRefresh(SolarwattBridgeConfiguration localConfig) {
+        ScheduledFuture<?> localRefreshJob = this.refreshJob;
+        if (localRefreshJob == null || localRefreshJob.isCancelled()) {
+            this.refreshJob = this.scheduler.scheduleWithFixedDelay(this.refreshRunnable, 0, localConfig.refresh,
+                    TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * Fetch the map of devices from the cache.
+     *
+     * Used by all childs to get their values.
+     *
+     * @return map with all {@link Device}s
+     */
+    public @Nullable Map<String, Device> getDevices() {
+        ExpiringCache<Map<String, Device>> localDevicesCache = this.devicesCache;
+        if (localDevicesCache != null) {
+            Map<String, Device> cache = localDevicesCache.getValue();
+            if (cache != null) {
+                this.updateStatus(ThingStatus.ONLINE);
+            } else {
+                this.updateStatus(ThingStatus.OFFLINE);
+            }
+
+            return cache;
+        } else {
+            return new HashMap<>();
+        }
+    }
+
+    /**
+     * Get a device for a specific guid.
+     *
+     * @param guid to search for
+     * @return device belonging to guid or null if not found.
+     */
+    public @Nullable Device getDeviceFromGuid(String guid) {
+        Map<String, Device> localDevices = this.getDevices();
+        if (localDevices != null && localDevices.containsKey(guid)) {
+            return localDevices.get(guid);
+        }
+
+        return null;
+    }
+
+    /**
+     * Convert the energy manager millisecond timestamps to {@link ZonedDateTime}
+     *
+     * The energy manager is the only point that knows about the timezone and
+     * it is available to all other devices. All timestamps used by all devices
+     * are in milliseconds since the epoch.
+     *
+     * @param timestamp milliseconds since the epoch
+     * @return date time in timezone
+     */
+    public ZonedDateTime getFromMilliTimestamp(BigDecimal timestamp) {
+        Map<String, Device> devices = this.getDevices();
+        if (devices != null) {
+            EnergyManager energyManager = (EnergyManager) devices.get(this.energyManagerGuid);
+            if (energyManager != null) {
+                BigDecimal[] bigDecimals = timestamp.divideAndRemainder(BigDecimal.valueOf(1_000));
+                Instant instant = Instant.ofEpochSecond(bigDecimals[0].longValue(),
+                        bigDecimals[1].multiply(BigDecimal.valueOf(1_000_000)).longValue());
+
+                ZoneId localZoneID = this.zoneId;
+                if (localZoneID != null) {
+                    return ZonedDateTime.ofInstant(instant, localZoneID);
+                }
+            }
+        }
+
+        throw new DateTimeException("Timezone from energy manager missing.");
+    }
+
+    /**
+     * Reload all devices from the energy manager.
+     *
+     * This method is called via the {@link ExpiringCache}.
+     * 
+     * @return map from guid to {@link Device}}
+     */
+    private @Nullable Map<String, Device> refreshDevices() {
+        try {
+            final Map<String, Device> devicesData = this.connector.retrieveDevices().getDevices();
+            this.updateStatus(ThingStatus.ONLINE);
+
+            // trigger refresh of the available channels
+            if (devicesData.containsKey(this.energyManagerGuid)) {
+                Device device = devicesData.get(this.energyManagerGuid);
+                if (device != null) {
+                    this.initDeviceChannels(device);
+                }
+            } else {
+                this.logger.warn("{}: initDeviceChannels missing energy manager {}", this, this.getThing().getUID());
+            }
+
+            return devicesData;
+        } catch (final SolarwattConnectionException e) {
+            this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+        }
+
+        return null;
+    }
+
+    /**
+     * Trigger an update on all child things of this bridge.
+     */
+    private void updateAllChildThings() {
+        this.getThing().getThings().forEach(childThing -> {
+            try {
+                ThingHandler childHandler = childThing.getHandler();
+                if (childHandler != null) {
+                    childHandler.handleCommand(new ChannelUID(childThing.getUID(), CHANNEL_TIMESTAMP.getChannelName()),
+                            RefreshType.REFRESH);
+                } else {
+                    this.logger.warn("no handler found for thing/device {}",
+                            childThing.getConfiguration().as(SolarwattThingConfiguration.class).guid);
+                }
+            } catch (Exception ex) {
+                this.logger.warn("Error processing child with uid {}", childThing.getUID(), ex);
+            }
+        });
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/handler/LocationHandler.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/handler/LocationHandler.java
new file mode 100644 (file)
index 0000000..4513a3a
--- /dev/null
@@ -0,0 +1,164 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.handler;
+
+import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.*;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solarwatt.internal.channel.SolarwattChannelTypeProvider;
+import org.openhab.binding.solarwatt.internal.domain.model.Device;
+import org.openhab.binding.solarwatt.internal.domain.model.EVStation;
+import org.openhab.binding.solarwatt.internal.domain.model.Location;
+import org.openhab.binding.solarwatt.internal.domain.model.PowerMeter;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.types.State;
+
+/**
+ * The concrete device handlers process the location specific commands.
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class LocationHandler extends SimpleDeviceHandler {
+
+    public LocationHandler(Thing thing, SolarwattChannelTypeProvider channelTypeProvider) {
+        super(thing, channelTypeProvider);
+    }
+
+    /**
+     * Add calculated states for unmetered consum.
+     *
+     * First calculate and then call super to submit the state
+     */
+    @Override
+    protected void updateDeviceChannels() {
+        // add calculated states
+        this.updateCalculated();
+
+        // submits all states
+        super.updateDeviceChannels();
+    }
+
+    /**
+     * Add calculated channels for unmetered consum.
+     *
+     * First calculate and then call super to submit the channels
+     */
+    @Override
+    protected void initDeviceChannels() {
+        // add calculated channels
+        final EnergyManagerHandler bridgeHandler = this.getEnergyManagerHandler();
+        if (bridgeHandler != null) {
+            // update the unmetered channel via subtracting the outerconsumers
+            // from the powerConsumed
+            Location locationDevice = (Location) this.getDevice();
+            if (locationDevice != null) {
+                locationDevice.addChannel(CHANNEL_POWER_DIRECT_CONSUMED.getChannelName(), Units.WATT,
+                        Device.WATT_CATEGORY, false);
+                locationDevice.addChannel(CHANNEL_WORK_DIRECT_CONSUMED.getChannelName(), Units.WATT_HOUR,
+                        Device.WATT_HOUR_CATEGORY, false);
+                locationDevice.addChannel(CHANNEL_POWER_CONSUMED_UNMETERED.getChannelName(), Units.WATT,
+                        Device.WATT_CATEGORY, false);
+                locationDevice.addChannel(CHANNEL_WORK_CONSUMED_UNMETERED.getChannelName(), Units.WATT_HOUR,
+                        Device.WATT_HOUR_CATEGORY, false);
+            }
+        }
+
+        // submit all channels
+        super.initDeviceChannels();
+    }
+
+    private void updateCalculated() {
+        final EnergyManagerHandler bridgeHandler = this.getEnergyManagerHandler();
+        final Location locationDevice = (Location) this.getDevice();
+        if (bridgeHandler != null && locationDevice != null) {
+            this.calculateDirectConsumption(locationDevice);
+            this.calculateUnmeteredConsumption(bridgeHandler, locationDevice);
+        }
+    }
+
+    private void calculateUnmeteredConsumption(EnergyManagerHandler bridgeHandler, Location locationDevice) {
+        // update the unmetered channels via subtracting
+        // the outerconsumers from the powerConsumed
+        BigDecimal powerConsumed = locationDevice.getBigDecimalFromChannel(CHANNEL_POWER_CONSUMED.getChannelName());
+        BigDecimal workConsumed = locationDevice.getBigDecimalFromChannel(CHANNEL_WORK_CONSUMED.getChannelName());
+
+        final List<BigDecimal> powerOuter = new ArrayList<>();
+        final List<BigDecimal> workOuter = new ArrayList<>();
+
+        Set<String> outerConsumers = locationDevice.getDevicesMap().getOuterConsumer();
+        if (outerConsumers != null) {
+            outerConsumers.stream().map(bridgeHandler::getDeviceFromGuid).forEach(outerDevice -> {
+                if (outerDevice instanceof PowerMeter) {
+                    powerOuter.add(outerDevice.getBigDecimalFromChannel(CHANNEL_POWER_IN.getChannelName()));
+                    workOuter.add(outerDevice.getBigDecimalFromChannel(CHANNEL_WORK_IN.getChannelName()));
+                } else if (outerDevice instanceof EVStation) {
+                    powerOuter.add(outerDevice.getBigDecimalFromChannel(CHANNEL_POWER_AC_IN.getChannelName()));
+                    workOuter.add(outerDevice.getBigDecimalFromChannel(CHANNEL_WORK_AC_IN.getChannelName()));
+                }
+            });
+
+            BigDecimal powerConsumedUnmetered = powerOuter.stream().reduce(powerConsumed, BigDecimal::subtract);
+            if (powerConsumedUnmetered.compareTo(BigDecimal.ONE) > 0) {
+                // sometimes the powerConsumed is exactly the power of the unmetered devices
+                // the resulting zero for unmetered consumption is not correct
+                locationDevice.addStateBigDecimal(CHANNEL_POWER_CONSUMED_UNMETERED, powerConsumedUnmetered, Units.WATT);
+            }
+            locationDevice.addStateBigDecimal(CHANNEL_WORK_CONSUMED_UNMETERED,
+                    workOuter.stream().reduce(workConsumed, BigDecimal::subtract), Units.WATT_HOUR);
+        }
+    }
+
+    private void calculateDirectConsumption(Location locationDevice) {
+        // calculate direct consumption for display purposes
+        locationDevice.addState(CHANNEL_POWER_DIRECT_CONSUMED.getChannelName(),
+                this.calculateQuantityDifference(locationDevice.getState(CHANNEL_POWER_SELF_CONSUMED.getChannelName()),
+                        locationDevice.getState(CHANNEL_POWER_BUFFERED_FROM_PRODUCERS.getChannelName())));
+
+        locationDevice.addState(CHANNEL_WORK_DIRECT_CONSUMED.getChannelName(),
+                this.calculateQuantityDifference(locationDevice.getState(CHANNEL_WORK_SELF_CONSUMED.getChannelName()),
+                        locationDevice.getState(CHANNEL_WORK_BUFFERED_FROM_PRODUCERS.getChannelName())));
+    }
+
+    /**
+     * Helper to generate a new state calculated from the difference between two states.
+     *
+     * channelTarget = channelValue - channelSubtract
+     *
+     * @param stateValue value from which we subtract
+     * @param stateSubtract value to substrct
+     * @return {@link State} of calculated value
+     */
+    private @Nullable State calculateQuantityDifference(@Nullable State stateValue, @Nullable State stateSubtract) {
+        if (stateValue != null && stateSubtract != null) {
+            @SuppressWarnings("rawtypes")
+            QuantityType quantityValue = stateValue.as(QuantityType.class);
+            @SuppressWarnings("rawtypes")
+            QuantityType quantitySubtract = stateSubtract.as(QuantityType.class);
+
+            if (quantityValue != null && quantitySubtract != null) {
+                return quantityValue.subtract(quantitySubtract);
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/handler/SimpleDeviceHandler.java b/bundles/org.openhab.binding.solarwatt/src/main/java/org/openhab/binding/solarwatt/internal/handler/SimpleDeviceHandler.java
new file mode 100644 (file)
index 0000000..c8df935
--- /dev/null
@@ -0,0 +1,250 @@
+/**
+ * Copyright (c) 2010-2021 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.solarwatt.internal.handler;
+
+import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.*;
+
+import java.text.MessageFormat;
+import java.util.Map;
+
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solarwatt.internal.channel.SolarwattChannelTypeProvider;
+import org.openhab.binding.solarwatt.internal.configuration.SolarwattThingConfiguration;
+import org.openhab.binding.solarwatt.internal.domain.SolarwattChannel;
+import org.openhab.binding.solarwatt.internal.domain.model.Device;
+import org.openhab.core.library.CoreItemFactory;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.thing.binding.BridgeHandler;
+import org.openhab.core.thing.binding.builder.ChannelBuilder;
+import org.openhab.core.thing.binding.builder.ThingBuilder;
+import org.openhab.core.thing.type.ChannelKind;
+import org.openhab.core.thing.type.ChannelTypeUID;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.util.UnitUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link SimpleDeviceHandler} bundles everything related to generic talking to devices.
+ *
+ * @author Sven Carstens - Initial contribution
+ */
+@NonNullByDefault
+public class SimpleDeviceHandler extends BaseThingHandler {
+    private final Logger logger = LoggerFactory.getLogger(SimpleDeviceHandler.class);
+
+    private final SolarwattChannelTypeProvider channelTypeProvider;
+
+    public SimpleDeviceHandler(Thing thing, SolarwattChannelTypeProvider channelTypeProvider) {
+        super(thing);
+        this.channelTypeProvider = channelTypeProvider;
+    }
+
+    /**
+     * Bring the thing online and update state from the bridge.
+     */
+    @Override
+    public void initialize() {
+        final EnergyManagerHandler bridgeHandler = this.getEnergyManagerHandler();
+        if (bridgeHandler != null) {
+            this.initDeviceChannels();
+            this.updateDeviceProperties();
+            this.updateDeviceChannels();
+        } else {
+            this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+                    "Received null bridge while initializing!");
+        }
+    }
+
+    /**
+     * Process the command for this thing.
+     *
+     * Only refresh is supported in this case.
+     *
+     * @param channelUID channel for which the command was issued
+     * @param command to execute
+     */
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        final EnergyManagerHandler bridgeHandler = this.getEnergyManagerHandler();
+        if (bridgeHandler != null) {
+            if (command instanceof RefreshType) {
+                this.updateDeviceProperties();
+                this.updateDeviceChannels();
+            }
+        } else {
+            this.logger.warn("Thing {} has no bridgeHandler for Bridge {}", this.getThing().getUID(),
+                    this.getThing().getBridgeUID());
+        }
+    }
+
+    /**
+     * Update the state of all channels.
+     */
+    protected void updateDeviceChannels() {
+        // find device for the thing
+        Device device = this.getDevice();
+
+        if (device != null) {
+            device.getStateValues().forEach(this::updateState);
+        }
+    }
+
+    /**
+     * Assert that all {@link org.openhab.core.thing.type.ChannelType}s are registered for this thing.
+     */
+    protected void initDeviceChannels() {
+        // find device for the thing
+        Device device = this.getDevice();
+
+        if (device != null) {
+            device.getSolarwattChannelSet().forEach((channelTag, solarwattChannel) -> {
+                this.assertChannel(solarwattChannel);
+            });
+        }
+    }
+
+    /**
+     * Update the properties for this device.
+     */
+    protected void updateDeviceProperties() {
+        // find device for the thing
+        Device device = this.getDevice();
+
+        if (device != null) {
+            // update properties
+            Map<String, String> properties = this.editProperties();
+            this.putProperty(properties, PROPERTY_ID_NAME, device.getIdName());
+            this.putProperty(properties, PROPERTY_ID_FIRMWARE, device.getIdFirmware());
+            this.putProperty(properties, PROPERTY_ID_MANUFACTURER, device.getIdManufacturer());
+            this.updateProperties(properties);
+
+            // relay state of device to status
+            this.updateStatus(device.getStateDevice());
+        }
+    }
+
+    private void putProperty(Map<String, String> properties, String name, @Nullable String value) {
+        if (value != null) {
+            properties.put(name, value);
+        }
+    }
+
+    /**
+     * Assert that all channels inside of our thing are well defined.
+     *
+     * Only channels which can not be found are created.
+     *
+     * @param solarwattChannel channel description with name and unit
+     */
+    protected void assertChannel(SolarwattChannel solarwattChannel) {
+        ChannelUID channelUID = new ChannelUID(this.getThing().getUID(), solarwattChannel.getChannelName());
+        ChannelTypeUID channelType = this.channelTypeProvider.assertChannelType(solarwattChannel);
+        if (this.getThing().getChannel(channelUID) == null) {
+            ThingBuilder thingBuilder = this.editThing();
+            thingBuilder.withChannel(getChannelBuilder(solarwattChannel, channelUID, channelType).build());
+
+            this.updateThing(thingBuilder.build());
+        }
+    }
+
+    /**
+     * Get a builder for a channel type according to the {@link SolarwattChannel}
+     *
+     * @param solarwattChannel channel type definition
+     * @param channelUID uid of the channel
+     * @param channelType uid of the channel type
+     * @return builder for that channel type
+     */
+    public static ChannelBuilder getChannelBuilder(SolarwattChannel solarwattChannel, ChannelUID channelUID,
+            ChannelTypeUID channelType) {
+        String itemType = CoreItemFactory.STRING;
+        Unit<?> unit = solarwattChannel.getUnit();
+        if (unit != null) {
+            String dimension = UnitUtils.getDimensionName(unit);
+
+            if (Units.PERCENT.equals(unit)) {
+                // strangely it is Angle
+                dimension = ":Dimensionless";
+            }
+
+            itemType = CoreItemFactory.NUMBER;
+            if (dimension != null && !dimension.isEmpty()) {
+                itemType = CoreItemFactory.NUMBER + ":" + dimension;
+            }
+        }
+        ChannelBuilder channelBuilder = ChannelBuilder.create(channelUID, itemType);
+
+        channelBuilder.withLabel(solarwattChannel.getChannelName()).withType(channelType).withDescription(MessageFormat
+                .format("Value for {0} with Unit: {1}", solarwattChannel.getChannelName(), solarwattChannel.getUnit()))
+                .withKind(ChannelKind.STATE);
+        return channelBuilder;
+    }
+
+    /**
+     * Get the {@link EnergyManagerHandler}.
+     *
+     * Only the {@link EnergyManagerHandler} has knowledge about the devices itself.
+     *
+     * @return instance responsible for this handler
+     */
+    protected @Nullable EnergyManagerHandler getEnergyManagerHandler() {
+        Bridge bridge = this.getBridge();
+        if (bridge != null) {
+            BridgeHandler bridgeHandler = bridge.getHandler();
+            if (bridgeHandler instanceof EnergyManagerHandler) {
+                return (EnergyManagerHandler) bridgeHandler;
+            } else {
+                // happens while dynamically reloading the binding
+                this.logger.warn("BridgeHandler is not implementing EnergyManagerHandler {}", bridgeHandler);
+            }
+        } else {
+            // this handler can't work without a bridge
+            this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+                    "Received null bridge while initializing!");
+        }
+
+        return null;
+    }
+
+    /**
+     * Get the {@link Device} from the {@link EnergyManagerHandler}.
+     *
+     * @return model with values
+     */
+    protected @Nullable Device getDevice() {
+        final EnergyManagerHandler bridgeHandler = this.getEnergyManagerHandler();
+
+        if (bridgeHandler != null) {
+            Map<String, Device> bridgeDevices = bridgeHandler.getDevices();
+            if (bridgeDevices != null) {
+                return bridgeDevices.get(this.getConfigAs(SolarwattThingConfiguration.class).guid);
+            }
+        }
+
+        this.logger.warn("Device not found for thing with guid {}",
+                this.getConfigAs(SolarwattThingConfiguration.class).guid);
+
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.solarwatt/src/main/resources/OH-INF/binding/binding.xml
new file mode 100644 (file)
index 0000000..1d8a535
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<binding:binding id="solarwatt" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
+
+       <name>Solarwatt Binding</name>
+       <description>This is the binding for Solarwatt Energymanager.</description>
+
+</binding:binding>
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.solarwatt/src/main/resources/OH-INF/config/config.xml
new file mode 100644 (file)
index 0000000..5a79eaa
--- /dev/null
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<config-description:config-descriptions
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
+       https://openhab.org/schemas/config-description-1.0.0.xsd">
+       <config-description uri="thing-type:solarwatt:energymanager">
+               <parameter name="hostname" type="text">
+                       <label>Host Name</label>
+                       <description>The host name/ip address of the solarwatt energymanager.</description>
+                       <context>network-address</context>
+               </parameter>
+               <parameter name="refresh" type="integer" unit="s">
+                       <label>Refresh Data Period</label>
+                       <description>Period between updates to the devices data in seconds.
+                       </description>
+                       <default>30</default>
+                       <unitLabel>s</unitLabel>
+                       <advanced>true</advanced>
+               </parameter>
+               <parameter name="rescan" type="integer" unit="min">
+                       <label>Redetect Devices Period</label>
+                       <description>Period between updates to the detected devices in minutes.
+                       </description>
+                       <default>5</default>
+                       <unitLabel>min</unitLabel>
+                       <advanced>true</advanced>
+               </parameter>
+       </config-description>
+
+       <config-description uri="thing-type:solarwatt:device">
+               <parameter name="guid" type="text">
+                       <label>Guid of Device</label>
+                       <description>Guid of the device as used by the solarwatt energymanager.</description>
+               </parameter>
+       </config-description>
+</config-description:config-descriptions>
diff --git a/bundles/org.openhab.binding.solarwatt/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.solarwatt/src/main/resources/OH-INF/thing/thing-types.xml
new file mode 100644 (file)
index 0000000..de96778
--- /dev/null
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="solarwatt"
+       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">
+
+       <bridge-type id="energymanager">
+               <label>Solarwatt Energymanager</label>
+               <description>Solarwatt Energymanager is the bridge to all things attached to the PV production system.
+               </description>
+
+               <representation-property>hostname</representation-property>
+
+               <config-description-ref uri="thing-type:solarwatt:energymanager"/>
+       </bridge-type>
+
+       <thing-type id="evstation">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="energymanager"/>
+               </supported-bridge-type-refs>
+
+               <label>EV Station</label>
+               <description>Electric vehicle charger station</description>
+
+               <representation-property>IdName</representation-property>
+
+               <config-description-ref uri="thing-type:solarwatt:device"/>
+       </thing-type>
+
+       <thing-type id="batteryconverter">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="energymanager"/>
+               </supported-bridge-type-refs>
+
+               <label>Battery Converter</label>
+               <description>Battery converter to supply AC from battery storage.</description>
+
+               <representation-property>IdName</representation-property>
+
+               <config-description-ref uri="thing-type:solarwatt:device"/>
+       </thing-type>
+
+       <thing-type id="location">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="energymanager"/>
+               </supported-bridge-type-refs>
+
+               <label>Location</label>
+               <description>Location aggregates all things taking part in the production process.</description>
+
+               <representation-property>IdName</representation-property>
+
+               <config-description-ref uri="thing-type:solarwatt:device"/>
+       </thing-type>
+
+       <thing-type id="pvplant">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="energymanager"/>
+               </supported-bridge-type-refs>
+
+               <label>PV Plant</label>
+               <description>Photovoltaic plant generating DC from solar energy.</description>
+
+               <representation-property>IdName</representation-property>
+
+               <config-description-ref uri="thing-type:solarwatt:device"/>
+       </thing-type>
+
+       <thing-type id="gridflow">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="energymanager"/>
+               </supported-bridge-type-refs>
+
+               <label>Gridflow</label>
+               <description>Gridflow regulates interaction with the external power grid.</description>
+
+               <representation-property>IdName</representation-property>
+
+               <config-description-ref uri="thing-type:solarwatt:device"/>
+       </thing-type>
+
+       <thing-type id="powermeter">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="energymanager"/>
+               </supported-bridge-type-refs>
+
+               <label>Power Meter</label>
+               <description>Power meter for produced or consumed energy</description>
+
+               <representation-property>IdName</representation-property>
+
+               <config-description-ref uri="thing-type:solarwatt:device"/>
+       </thing-type>
+
+       <thing-type id="inverter">
+               <supported-bridge-type-refs>
+                       <bridge-type-ref id="energymanager"/>
+               </supported-bridge-type-refs>
+
+               <label>Inverter</label>
+               <description>Inverter supplying AC from DC.</description>
+
+               <representation-property>IdName</representation-property>
+
+               <config-description-ref uri="thing-type:solarwatt:device"/>
+       </thing-type>
+
+</thing:thing-descriptions>
index 2c0da99ff300952bd93bfda9b328d5e73c3915bd..f5af13e76739a2d7e2367d2002ef3729383ad7ea 100644 (file)
     <module>org.openhab.binding.snmp</module>
     <module>org.openhab.binding.solaredge</module>
     <module>org.openhab.binding.solarlog</module>
+    <module>org.openhab.binding.solarwatt</module>
     <module>org.openhab.binding.somfymylink</module>
     <module>org.openhab.binding.somfytahoma</module>
     <module>org.openhab.binding.sonos</module>