* [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>
/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
<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>
--- /dev/null
+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
--- /dev/null
+# 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"}
+```
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+/**
+ * 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";
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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();
+ }
+}
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+/**
+ * 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 = "";
+}
--- /dev/null
+/**
+ * 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();
+ }
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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);
+}
--- /dev/null
+/**
+ * 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;
+ }
+ }
+}
--- /dev/null
+/**
+ * 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<>();
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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";
+ }
+}
--- /dev/null
+/**
+ * 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();
+ }
+}
--- /dev/null
+/**
+ * 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";
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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";
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+ }
+ }
+}
--- /dev/null
+/**
+ * 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";
+ }
+}
--- /dev/null
+/**
+ * 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";
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+ }
+}
--- /dev/null
+/**
+ * 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";
+ }
+}
--- /dev/null
+/**
+ * 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";
+ }
+}
--- /dev/null
+/**
+ * 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";
+ }
+}
--- /dev/null
+/**
+ * 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";
+ }
+}
--- /dev/null
+/**
+ * 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";
+ }
+}
--- /dev/null
+/**
+ * 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";
+ }
+}
--- /dev/null
+/**
+ * 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";
+ }
+}
--- /dev/null
+/**
+ * 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";
+ }
+}
--- /dev/null
+/**
+ * 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";
+ }
+}
--- /dev/null
+/**
+ * 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";
+ }
+}
--- /dev/null
+/**
+ * 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";
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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());
+ }
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+ });
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
<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>