From 9498590edb98799bfb7debe87c867c46e2b47da9 Mon Sep 17 00:00:00 2001 From: basse04 Date: Thu, 4 Feb 2021 23:17:04 +0100 Subject: [PATCH] [kostalinverter] Add Second Generation (Piko10-20) type inverters (#8574) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Binding redesigned 20200923 by Örjan Backsell Signed-off-by: basse04 orjan.backsell@gmail.com Also-by: Christian Schneider <> Also-by: René Stakemeier <> Also-by: Christoph Weitkamp <> This is the upgraded version of Kostal Inverter extended with functionality for the Inverter type of Second generation PIKO 10-20 [kostalinverter], regarded OH3. [WIP] Signed-off-by: basse04 * Binding redesigned 20200923 by Örjan Backsell Signed-off-by: basse04 orjan.backsell@gmail.com Also-by: Christian Schneider <> Also-by: René Stakemeier <> Also-by: Christoph Weitkamp <> This is the upgraded version of Kostal Inverter extended with functionality for the Inverter type of Second generation PIKO 10-20 [kostalinverter] regarded oH3 [WIP] Signed-off-by: basse04 * Binding redesigned 20200923 by Örjan Backsell Signed-off-by: basse04 orjan.backsell@gmail.com Also-by: Christian Schneider <> Also-by: René Stakemeier <> Also-by: Christoph Weitkamp <> This is the upgraded version of Kostal Inverter extended with functionality for the Inverter type of Second generation PIKO 10-20 [kostalinverter] regarded oH3 [WIP] Signed-off-by: basse04 * Binding redesigned 20200923 by Örjan Backsell Signed-off-by: basse04 orjan.backsell@gmail.com Also-by: Christian Schneider <> Also-by: René Stakemeier <> Also-by: Christoph Weitkamp <> This is the upgraded version of Kostal Inverter extended with functionality for the Inverter type of Second generation PIKO 10-20 [kostalinverter] regarded oH3 [WIP] Signed-off-by: basse04 * Binding redesigned 20200924 by Örjan Backsell Signed-off-by: basse04 orjan.backsell@gmail.com Also-by: Christian Schneider <> Also-by: René Stakemeier <> Also-by: Christoph Weitkamp <> This is the upgraded version of Kostal Inverter extended with functionality for the Inverter type of Second generation PIKO 10-20 [kostalinverter] regarded oH3 [WIP] Signed-off-by: basse04 * Binding redesigned 20200924 by Örjan Backsell Signed-off-by: basse04 orjan.backsell@gmail.com Also-by: Christian Schneider <> Also-by: René Stakemeier <> Also-by: Christoph Weitkamp <> Changes made in org.openhab.binding.internal.kostal.inverter.secondgeneration.SecondGenerationHandler.java found by Travis tests. Signed-off-by: basse04 * Binding redesigned 20201007 by Örjan Backsell Signed-off-by: basse04 orjan.backsell@gmail.com Also-by: Christian Schneider <> Also-by: René Stakemeier <> Also-by: Christoph Weitkamp <> Done changes in: Channels.xml PIKO1020.xml README.md SecondGeneration.xml SecondGenerationChannelConfiguration.java Signed-off-by: basse04 * Binding redesigned 20201008 by Örjan Backsell Signed-off-by: basse04 * Binding redesigned 20201012 by Örjan Backsell, regarded to @fwolter requested changes Signed-off-by: basse04 orjan.backsell@gmail.com Also-by: Christian Schneider <> Also-by: René Stakemeier <> Also-by: Christoph Weitkamp <> Signed-off-by: basse04 * Binding redesigned 20201028 by Örjan Backsell, regarded to @cpmeister requested changes. Signed-off-by: basse04 orjan.backsell@gmail.com Also-by: Christian Schneider <> Also-by: René Stakemeier <> Also-by: Christoph Weitkamp <> Signed-off-by: basse04 * Binding redesigned 20201105 by Örjan Backsell Signed-off-by: basse04 orjan.backsell@gmail.com Also-by: Christian Schneider <> Also-by: René Stakemeier <> Also-by: Christoph Weitkamp <> Signed-off-by: basse04 * Binding redesigned 20200923 by Örjan Backsell ' Signed-off-by: basse04 orjan.backsell@gmail.com Also-by: Christian Schneider <> Also-by: René Stakemeier <> Also-by: Christoph Weitkamp <> ' Signed-off-by: basse04 * Binding redesigned 20201111 by Örjan Backsell Signed-off-by: basse04 * Binding redesigned 20201117 by Örjan Backsell ' Signed-off-by: basse04 orjan.backsell@gmail.com Also-by: Christian Schneider <> Also-by: René Stakemeier <> Also-by: Christoph Weitkamp <> This is the upgraded version of Kostal Inverter extended with functionality for the Inverter type of Second generation PIKO 10-20 [kostalinverter] [WIP] ' Signed-off-by: basse04 * Binding redesigned 20201119 by Örjan Backsell Signed-off-by: basse04 * Binding redesigned 20201125 by Örjan Backsell Signed-off-by: basse04 * Binding redesigned 20201130 by Örjan Backsell Signed-off-by: basse04 * Binding redesigned 20201130, 1630 by Örjan Backsell Signed-off-by: basse04 * Binding redesigned 20201202 by Örjan Backsell Signed-off-by: basse04 * Binding redesigned 20201204 by Örjan Backsell Signed-off-by: basse04 * Redesigned 20201204 by Örjan Backsell Signed-off-by: basse04 * Binding redesigned 20201010 by Örjan Backsell Signed-off-by: basse04 * Binding redesigned 20210112 by Örjan Backsell ' Signed-off-by: basse04 orjan.backsell@gmail.com Also-by: Christian Schneider <> Also-by: René Stakemeier <> Also-by: Christoph Weitkamp <> This is the upgraded version of Kostal Inverter extended with functionality for the Inverter type of Second generation PIKO 10-20 [kostalinverter] ' Signed-off-by: basse04 * Binding redesigned 20210114 by Örjan Backsell Signed-off-by: basse04 * Binding redesigned 20210119 by Örjan Backsell Signed-off-by: basse04 * Binding (KostalInverterFactory.java)redesigned 20210119 by Örjan Backsell Signed-off-by: basse04 * Binding redesigned 20210125 by Örjan Backsell * Binding redesigned 20200923 by Örjan Backsell (KostalInverterFactory.java) Signed-off-by: basse04 * Binding redesigned 20210125 by Örjan Backsell Signed-off-by: basse04 --- .../README.md | 195 +++++++- .../doc/kostalpiko10_20.jpg | Bin 0 -> 39756 bytes .../internal/KostalInverterFactory.java | 10 +- .../SecondGenerationBindingConstants.java | 100 ++++ .../SecondGenerationChannelConfiguration.java | 185 ++++++++ .../SecondGenerationConfigurationHandler.java | 161 +++++++ .../SecondGenerationDxsEntries.java | 35 ++ ...econdGenerationDxsEntriesContainerDTO.java | 26 ++ .../SecondGenerationHandler.java | 401 ++++++++++++++++ .../SecondGenerationInverterConfig.java | 36 ++ .../OH-INF/config/SecondGeneration.xml | 31 ++ .../main/resources/OH-INF/thing/Channels.xml | 438 +++++++++++++++++- .../main/resources/OH-INF/thing/PIKO1020.xml | 79 ++++ 13 files changed, 1693 insertions(+), 4 deletions(-) create mode 100644 bundles/org.openhab.binding.kostalinverter/doc/kostalpiko10_20.jpg create mode 100644 bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationBindingConstants.java create mode 100644 bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationChannelConfiguration.java create mode 100644 bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationConfigurationHandler.java create mode 100644 bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationDxsEntries.java create mode 100644 bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationDxsEntriesContainerDTO.java create mode 100644 bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationHandler.java create mode 100644 bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationInverterConfig.java create mode 100644 bundles/org.openhab.binding.kostalinverter/src/main/resources/OH-INF/config/SecondGeneration.xml create mode 100644 bundles/org.openhab.binding.kostalinverter/src/main/resources/OH-INF/thing/PIKO1020.xml diff --git a/bundles/org.openhab.binding.kostalinverter/README.md b/bundles/org.openhab.binding.kostalinverter/README.md index 9a3d8ec7459..b595afc5f9e 100644 --- a/bundles/org.openhab.binding.kostalinverter/README.md +++ b/bundles/org.openhab.binding.kostalinverter/README.md @@ -4,13 +4,19 @@ Scrapes the web interface of the inverter for the metrics of the supported chann ![Kostal Pico](doc/kostalpico.jpg) +![Kostal Piko 10-20](doc/kostalpiko10_20.jpg) + ![Kostal PLENTICORE / PIKI IQ](doc/plenticore.jpg) ## Supported Things ### First generation devices (PIKO) -Tested with Kostal Inverter PIKO but might work with other inverters from kostal too. +Tested with Kostal Inverter PIKO but might work with other inverters from Kostal too. + +### Second generation devices (PIKO 10-20, PIKO NEW GENERATION) + +Tested with Kostal Inverter PIKO 10-20, PIKO NEW GENERATION. ### Third generation devices (PIKO IQ / PLENTICORE plus) @@ -59,6 +65,78 @@ None - l3Voltage - l3Power +### Second generation devices (PIKO 10-20, PIKO NEW GENERATION) + +| Channel Type ID | Item Type | Description | Read Write | +|------------------------------------------|--------------------------|----------------------------------------------------------------------------------|:----------:| +| device-local-grid-output-power | Number:Power | Current output power to the grid | R | +| statistic-yield-day-second-gen | Number:Energy | Total produced power today | R | +| statistic-yield-total-second-gen | Number:Energy | Total produced power | R | +| device-local-operating-status | Number:Dimensionless | Current operating status, 0 = Standby, 3 = WO-IDLE | R | +| device-local-grid-voltage-l1 | Number:ElectricPotential | Current output voltage to the grid, L1 | R | +| device-local-grid-current-l1 | Number:ElectricCurrent | Current output current to the grid, L1 | R | +| device-local-grid-power-l1 | Number:Power | Current output power to the grid, L1 | R | +| device-local-grid-voltage-l2 | Number:ElectricPotential | Current output voltage to the grid, L2 | R | +| device-local-grid-current-l2 | Number:ElectricCurrent | Current output current to the grid, L2 | R | +| device-local-grid-power-l2 | Number:Power | Current output power to the grid, L2 | R | +| device-local-grid-voltage-l3 | Number:ElectricPotential | Current output voltage to the grid, L3 | R | +| device-local-grid-current-l3 | Number:ElectricCurrent | Current output current to the grid, L3 | R | +| device-local-grid-power-l3 | Number:Power | Current output power to the grid, L3 | R | +| device-local-dc-power-pv | Number:Power | Current power from all solar panels | R | +| device-local-dc1-voltage | Number:ElectricPotential | Current voltage from solar panels, Dc1 | R | +| device-local-dc1-current | Number:ElectricCurrent | Current current from solar panels, Dc1 | R | +| device-local-dc1-power | Number:Power | Current power from solar panels, Dc1 | R | +| device-local-dc2-voltage | Number:ElectricPotential | Current voltage from solar panels, Dc2 | R | +| device-local-dc2-current | Number:ElectricCurrent | Current current from solar panels, Dc2 | R | +| device-local-dc2-power | Number:Power | Current power from solar panels, Dc2 | R | +| device-local-dc3-voltage | Number:ElectricPotential | Current voltage from solar panels, Dc3 | R | +| device-local-dc3-current | Number:ElectricCurrent | Current current from solar panels, Dc3 | R | +| device-local-dc3-power | Number:Power | Current power from solar panels, Dc3 | R | +| device-local-akt-home-consumption-solar | Number:Power | Current consumption from solar panels | R | +| device-local-akt-home-consumption-bat | Number:Power | Current consumption from battery | R | +| device-local-akt-home-consumption-grid | Number:Power | Current consumption from grid | R | +| device-local-phase-sel-home-consump-l1 | Number:Power | Current home consumption, L1 | R | +| device-local-phase-sel-home-consump-l2 | Number:Power | Current home consumption, L2 | R | +| device-local-phase-sel-home-consump-l3 | Number:Power | Current home consumption, L3 | R | +| device-local-grid-freq | Number:Frequency | Current frequency on grid | R | +| device-local-grid-cos-phi | Number:Angle | Current power factor on grid | R | +| statistic-home-consumption-day | Number:Energy | Total home consumption today | R | +| statistic-own-consumption-day | Number:Energy | Total own consumption today | R | +| statistic-own-cons-rate-day | Number:Dimensionless | Total own consumption rate today | R | +| statistic-autonomy-degree-day | Number:Dimensionless | Total autonomy degree today | R | +| statistic-home-consumption-total | Number:Energy | Total home consumption | R | +| statistic-own-consumption-total | Number:Energy | Total own consumption | R | +| statistic-operating-time-total | Number:Time | Total operating time | R | +| device-local-current | Number:ElectricCurrent | Current | R | +| device-local-current-dir | Number:Dimensionless | Current direction of loading/unloading the battery | R | +| device-local-charge-cycles | Number:Dimensionless | Total number of charge cycles | R | +| device-local-battery-temperature | Number:Temperature | Battery current temperature | R | +| device-local-loginterval | Number:Time | Value for loginterval | R | +| device-local-s0-inpulse-cnt | Number:Dimensionless | S0-pulse counter | R | +| statistic-own-cons-rate-total | Number:Dimensionless | Total own comsumption rate | R | +| statistic-autonomy-degree-total | Number:Dimensionless | Total autonomy degree | R | +| device-local-battery-voltage | Number:ElectricPotential | Battery current voltage | R | +| device-local-bat-state-of-charge | Number:Dimensionless | Battery current charge state | R | +| device-local-self-consumption | Number:Power | Current self consumption | R | +| device-local-battery-usage-consumption | Number:Power | Battery usage consumption | R | +| device-local-smart-battery-control | Switch | Smart battery control | R | +| device-local-shadow-management | Number:Dimensionless | Shadow management | R | +| device-local-external-module-control | Number:Dimensionless | External module control | R | + +The following Channels are writeable + +| Channel Type ID | Item Type | Description | Read Write | +|------------------------------------------|--------------------------|----------------------------------------------------------------------------------|:----------:| +| device-local-battery-usage-consumption-set| String | Battery usage consumption level for power-consumption from battery, value = 100 (W) | W | +| device-local-battery-usage-strategy-set | String | Battery usage strategy, Value = 1 = Automatic, Value = 2 = Automatic economical | W | +| device-local-smart-battery-control-set | Switch | Smart battery control, Value = OFF / ON | W | +| device-local-battery-charge-time-from-set| String | Battery charge time from, Value = 00:00 | W | +| device-local-battery-charge-time-to-set | String | Battery charge time to, Value = 23:59 | W | +| device-local-max-depth-of-discharge-set | String | Max depth of discharge (SoC), Value = 10 | W | +| device-local-shadow-management-set | String | Shadow management, Value = 0 = No shadow management enabled, Value = 1 = Shadow management enabled for DC-Input String 1, Value = 2 = Shadow management enabled for DC-Input String 2, Value = 3 = Shadow management enabled for DC-Input String 1 and 2 | W | +| device-local-external-module-control-set | String | External module control, Value = 0 = Not Activated, Value = 1 = Activated | W | + + ### Third generation devices (PIKO IQ / PLENTICORE plus) | Channel Type ID | Item Type | Description | Read Write | @@ -148,6 +226,31 @@ If the thing goes online then the connection to the web interface is successful. In case it is offline you should see an error message. You optionally can define a `userName` and a `password` parameter if the access to the webinterface is protected and a desired `refreshInterval` (the time interval between updates, default 60 seconds). + +### Second generation devices (PIKO 10-20, PIKO NEW GENERATION) + +Second generation inverters require 5 mandatory parameters: + +| Parameter | Description | Type | Unit | Default value | Example value | +|--------------------------|--------------------------------------------------------|---------|---------|---------------|---------------| +| url | Host name or IP address of your device | Text | --- | --- | 192.168.0.2 | +| username | Username for your inverter | Text | --- | --- | myUsername | +| password | Password for your inverter | Text | --- | --- | myPassword | +| refreshInterval | Pollingintervall of your inverter | Integer | Seconds | 60 | 60 | +| hasBattery | Type of PIKO 10-20 inverter, with or without battery | boolean | --- | -- | false/true | + +demo.things + +``` + +Thing kostalinverter:piko1020:mypiko1020 [ url="http://'inverter-ip'", username="'myUsername'", password="'myPassword'", refreshInterval=60, hasBattery=false] + +``` + +You can define which type of PIKO10-20 inverter you will connect to with parameter hasBattery. + + + ### Third generation devices (PIKO IQ / PLENTICORE plus) All third generation inverters require to define 3 mandatory configuration parameters: @@ -182,11 +285,76 @@ Number:Energy SolarTotalEnergy "Solar total energy [%.3f %unit%]" { cha String SolarStatus "Solar status [%s]" { channel="kostalinverter:kostalinverter:inverter:status" } ``` + +### Second generation devices (PIKO NEW GENERATION) + +demo.items: + +``` +Number:Power GridOutputPower "PV Output Power" { channel="kostalinverter:piko1020:mypiko1020:gridOutputPower" } +Number:Energy YieldDaySecondGen "PV Output Power Day" { channel="kostalinverter:piko1020:mypiko1020:yieldDaySecondGen" } +Number:Energy YieldTotalSecondGen "PV Output Power Total" { channel="kostalinverter:piko1020:mypiko1020:yieldTotalSecondgen" } +Number:Dimensionless OperatingStatus "Operating Status" { channel="kostalinverter:piko1020:mypiko1020:operatingStatus" } +Number:ElectricPotential GridVoltageL1 "Grid Voltage L1" { channel="kostalinverter:piko1020:mypiko1020:gridVoltageL1" } +Number:ElectricCurrent GridCurrentL1 "Grid Current L1" { channel="kostalinverter:piko1020:mypiko1020:gridCurrentL1" } +Number:Power GridPowerL1 "Grid Power L1" { channel="kostalinverter:piko1020:mypiko1020:gridPowerL1" } +Number:ElectricPotential GridVoltageL2 "Grid Voltage L2" { channel="kostalinverter:piko1020:mypiko1020:gridVoltageL2" } +Number:ElectricCurrent GridCurrentL2 "Grid Current L2" { channel="kostalinverter:piko1020:mypiko1020:gridCurrentL2" } +Number:Power GridPowerL2 "Grid Power L2" { channel="kostalinverter:piko1020:mypiko1020:gridPowerL2" } +Number:ElectricPotential GridVoltageL3 "Grid Voltage L3" { channel="kostalinverter:piko1020:mypiko1020:gridVoltageL3" } +Number:ElectricCurrent GridCurrentL3 "Grid Current L3" { channel="kostalinverter:piko1020:mypiko1020:gridCurrentL3" } +Number:Power GridPowerL3 "Grid Power L3" { channel="kostalinverter:piko1020:mypiko1020:gridPowerL3" } +Number:Power DcPvPower "DC Power Pv" { channel="kostalinverter:piko1020:mypiko1020:dcPowerPV" } +Number:ElectricPotential Dc1Voltage "DC1 Voltage" { channel="kostalinverter:piko1020:mypiko1020:dc1Voltage" } +Number:ElectricCurrent Dc1Current "DC1 Current" { channel="kostalinverter:piko1020:mypiko1020:dc1Current" } +Number:Power Dc1Power "DC1 Power" { channel="kostalinverter:piko1020:mypiko1020:dc1Power" } +Number:ElectricPotential Dc2Voltage "DC2 Voltage" { channel="kostalinverter:piko1020:mypiko1020:dc2Voltage" } +Number:ElectricCurrent Dc2Current "DC2 Current" { channel="kostalinverter:piko1020:mypiko1020:dc2Current" } +Number:Power Dc2Power "DC2 Power" { channel="kostalinverter:piko1020:mypiko1020:dc2Power" } +Number:ElectricPotential Dc3Voltage "DC3 Voltage" { channel="kostalinverter:piko1020:mypiko1020:dc3Voltage" } +Number:ElectricCurrent Dc3Current "DC3 Current" { channel="kostalinverter:piko1020:mypiko1020:dc3Current" } +Number:Power Dc3Power "DC3 Power" { channel="kostalinverter:piko1020:mypiko1020:dc3Power" } +Number:Power AktHomeConsumptionSolar "Akt Home Consumption Solar" { channel="kostalinverter:piko1020:mypiko1020:aktHomeConsumptionSolar" } +Number:Power AktHomeConsumptionBat "Akt Home Consumption Battery" { channel="kostalinverter:piko1020:mypiko1020:aktHomeConsumptionBat" } +Number:Power AktHomeConsumptionGrid "Akt Home Consumption Grid" { channel="kostalinverter:piko1020:mypiko1020:aktHomeConsumptionGrid" } +Number:Power PhaseSelHomeConsumpL1 "Phase Sel Home Consump L1" { channel="kostalinverter:piko1020:mypiko1020:phaseSelHomeConsumpL1" } +Number:Power PhaseSelHomeConsumpL2 "Phase Sel Home Consump L2" { channel="kostalinverter:piko1020:mypiko1020:phaseSelHomeConsumpL2" } +Number:Power PhaseSelHomeConsumpL3 "Phase Sel Home Consump L3" { channel="kostalinverter:piko1020:mypiko1020:phaseSelHomeConsumpL3" } +Number:Frequency GridFreq "Grid Freq" { channel="kostalinverter:piko1020:mypiko1020:gridFreq" } +Number:Angle GridCosPhi "Grid Cos Phi" { channel="kostalinverter:piko1020:mypiko1020:gridCosPhi" } +Number:Energy HomeConsumptionDay "Home Consumption Day" { channel="kostalinverter:piko1020:mypiko1020:homeConsumptionDay" } +Number:Energy OwnConsumptionDay "Own Consumption Day" { channel="kostalinverter:piko1020:mypiko1020:ownConsumptionDay" } +Number:Dimensionless OwnConsRateDay "Own Cons Rate Day { channel="kostalinverter:piko1020:mypiko1020:ownConsRateDay" } +Number:Dimensionless AutonomyDegreeDay "Autonomy Degree Day" { channel="kostalinverter:piko1020:mypiko1020:autonomyDegreeDay" } +Number:Energy HomeConsumptionTotal "Home Consumption Total" { channel="kostalinverter:piko1020:mypiko1020:homeConsumptionTotal" } +Number:Energy OwnConsumptionTotal "Own Consumption Total" { channel="kostalinverter:piko1020:mypiko1020:ownConsumptionTotal" } +Number:Time OperatingTimeTotal "Operating Time Total" { channel="kostalinverter:piko1020:mypiko1020:operatingTimeTotal" } +Number:ElectricCurrent Current "Current" { channel="kostalinverter:piko1020:mypiko1020:current" } +Number:Dimensionless CurrentDir "Current Dir" { channel="kostalinverter:piko1020:mypiko1020:currentDir" } +Number:Dimensionless ChargeCycles "Charge Cycles" { channel="kostalinverter:piko1020:mypiko1020:chargeCycles" } +Number:Temperature BatteryTemperature "BatteryTemperature" { channel="kostalinverter:piko1020:mypiko1020:batteryTemperature" } +Number:Time Loginterval "Log Interval" { channel="kostalinverter:piko1020:mypiko1020:loginterval" } +Number:Dimensionless S0InPulseCnt "S0 InPulse Cnt" { channel="kostalinverter:piko1020:mypiko1020:s0InPulseCnt" } +Number:Dimensionless OwnConsRateTotal "Own Cons Rate Total" { channel="kostalinverter:piko1020:mypiko1020:ownConsRateTotal" } +Number:Dimensionless AutonomyDegreeTotal "Autonomy Degree Total" { channel="kostalinverter:piko1020:mypiko1020:autonomyDegreeTotal" } +Number:ElectricPotential BatteryVoltage "Battery Voltage" { channel="kostalinverter:piko1020:mypiko1020:batteryVoltage" } +Number:Dimensionless BatStateOfCharge "Bat State Of Charge" { channel="kostalinverter:piko1020:mypiko1020:batStateOfCharge" } +Number:Power SelfConsumption "Self Consumption" { channel="kostalinverter:piko1020:mypiko1020:selfConsumption" } +Number:Dimensionless BatteryUsageConsumption "Battery Usage Consumption" { channel="kostalinverter:piko1020:mypiko1020:batteryUsageConsumption" } +Switch SmartBatteryControl "Smart Battery Control" { channel="kostalinverter:piko1020:mypiko1020:smartBatteryControl" } +Number:Dimensionless MaxDepthOfDischarge "Max Depth Of Discharge" { channel="kostalinverter:piko1020:mypiko1020:maxDepthOfDischarge" } +Number:Dimensionless ShadowManagement "Shadow Management" { channel="kostalinverter:piko1020:mypiko1020:shadowManagement" } +Number:Dimensionless ExternalModuleControl "External Module Control" { channel="kostalinverter:piko1020:mypiko1020:externalModuleControl" } + + +``` + ### Third generation devices (PIKO IQ / PLENTICORE plus) demo.items: ``` + Number:Energy MyPlentiCore100WithBattery_DEVICE_LOCAL_DC_POWER { channel="kostalinverter:PLENTICOREPLUS100WITHBATTERY:MyPlentiCore100WithBattery:deviceLocalDCPower"} Number:Energy MyPlentiCore100WithBattery_DEVICE_LOCAL_HOMECONSUMPTION_FROM_BATTERY { channel="kostalinverter:PLENTICOREPLUS100WITHBATTERY:MyPlentiCore100WithBattery:deviceLocalHomeconsumptionFromBattery"} Number:Energy MyPlentiCore100WithBattery_DEVICE_LOCAL_HOMECONSUMPTION_FROM_GRID { channel="kostalinverter:PLENTICOREPLUS100WITHBATTERY:MyPlentiCore100WithBattery:deviceLocalHomeconsumptionFromGrid"} @@ -256,3 +424,28 @@ Number:Energy MyPlentiCore100WithBattery_STATISTIC_YIELD_TOTAL Number:Energy MyPlentiCore100WithBattery_STATISTIC_YIELD_YEAR { channel="kostalinverter:PLENTICOREPLUS100WITHBATTERY:MyPlentiCore100WithBattery:statisticYieldYear"} ``` + + +### Rules + +Second generation devices (PIKO 10-20, PIKO NEW GENERATION) + +``` + +Ex. Set Smart battery control OFF with cron trigger: + +triggers: + id: "1" + configuration: + cronExpression: 0 0/2 * * * ? * + type: timer.GenericCronTrigger +conditions: [] +actions: + inputs: {} + id: "2" + configuration: + type: application/vnd.openhab.dsl.rule + script: KOSTALPIKO1020_SmartBatteryControlSet.sendCommand("OFF") + type: script.ScriptAction + + diff --git a/bundles/org.openhab.binding.kostalinverter/doc/kostalpiko10_20.jpg b/bundles/org.openhab.binding.kostalinverter/doc/kostalpiko10_20.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f72fc15ae073caea21aaa25c41eed24b0f98b36b GIT binary patch literal 39756 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

eI-TGd}8^2~PYj(BD^ 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 bQNCtxv;P3?)H4k*&;Hb@M^cPpAH@IJveSw> literal 0 HcmV?d00001 diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/KostalInverterFactory.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/KostalInverterFactory.java index 79a00e2912f..0210a8ddcca 100644 --- a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/KostalInverterFactory.java +++ b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/KostalInverterFactory.java @@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.kostalinverter.internal.firstgeneration.WebscrapeHandler; +import org.openhab.binding.kostalinverter.internal.secondgeneration.SecondGenerationHandler; import org.openhab.binding.kostalinverter.internal.thirdgeneration.ThirdGenerationHandler; import org.openhab.binding.kostalinverter.internal.thirdgeneration.ThirdGenerationInverterTypes; import org.openhab.core.io.net.http.HttpClientFactory; @@ -36,6 +37,7 @@ import org.osgi.service.component.annotations.Reference; /** * @author Christian Schneider - Initial contribution (as WebscrapeHandlerFactory.java) * @author René Stakemeier - extension for the third generation of KOSTAL inverters + * @author Örjan Backsell - extension for the second generation of KOSTAL inverters */ @Component(service = ThingHandlerFactory.class, configurationPid = "binding.kostalinverter") @NonNullByDefault @@ -72,6 +74,8 @@ public class KostalInverterFactory extends BaseThingHandlerFactory { public static final ThingTypeUID FIRST_GENERATION_INVERTER = new ThingTypeUID("kostalinverter", "kostalinverter"); + public static final ThingTypeUID SECOND_GENERATION_INVERTER = new ThingTypeUID("kostalinverter", "piko1020"); + private final HttpClient httpClient; @Activate @@ -81,7 +85,7 @@ public class KostalInverterFactory extends BaseThingHandlerFactory { @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return thingTypeUID.equals(FIRST_GENERATION_INVERTER) + return thingTypeUID.equals(FIRST_GENERATION_INVERTER) || thingTypeUID.equals(SECOND_GENERATION_INVERTER) || SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.keySet().contains(thingTypeUID); } @@ -91,6 +95,10 @@ public class KostalInverterFactory extends BaseThingHandlerFactory { if (FIRST_GENERATION_INVERTER.equals(thing.getThingTypeUID())) { return new WebscrapeHandler(thing); } + // second generation + if (SECOND_GENERATION_INVERTER.equals(thing.getThingTypeUID())) { + return new SecondGenerationHandler(thing, httpClient); + } // third generation ThirdGenerationInverterTypes inverterType = SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS .get(thing.getThingTypeUID()); diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationBindingConstants.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationBindingConstants.java new file mode 100644 index 00000000000..d46f09e4e33 --- /dev/null +++ b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationBindingConstants.java @@ -0,0 +1,100 @@ +/** + * 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.kostalinverter.internal.secondgeneration; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link SecondGenerationBindingConstants} class defines channel constants, which are + * used in the second generation part of the binding. + * + * @author Örjan Backsell - Initial contribution Piko1020, Piko New Generation + */ + +@NonNullByDefault +public class SecondGenerationBindingConstants { + + private static final String BINDING_ID = "kostalinverter"; + + // List of all Thing Type UIDs + public static final ThingTypeUID SECOND_GENERATION_INVERTER = new ThingTypeUID(BINDING_ID, "piko1020"); + + // List of all Channel ids + public static final String CHANNEL_GRIDOUTPUTPOWER = "gridOutputPower"; + public static final String CHANNEL_YIELD_DAY_SECOND_GEN = "yieldDaySecondGen"; + public static final String CHANNEL_YIELD_TOTAL_SECOND_GEN = "yieldTotalSecondGen"; + public static final String CHANNEL_OPERATING_STATUS = "operatingStatus"; + public static final String CHANNEL_GRIDVOLTAGEL1 = "gridVoltageL1"; + public static final String CHANNEL_GRIDCURRENTL1 = "gridCurrentL1"; + public static final String CHANNEL_GRIDPOWERL1 = "gridPowerL1"; + public static final String CHANNEL_GRIDVOLTAGEL2 = "gridVoltageL2"; + public static final String CHANNEL_GRIDCURRENTL2 = "gridCurrentL2"; + public static final String CHANNEL_GRIDPOWERL2 = "gridPowerL2"; + public static final String CHANNEL_GRIDVOLTAGEL3 = "gridVoltageL3"; + public static final String CHANNEL_GRIDCURRENTL3 = "gridCurrentL3"; + public static final String CHANNEL_GRIDPOWERL3 = "gridPowerL3"; + public static final String CHANNEL_DCPOWERPV = "dcPowerPV"; + public static final String CHANNEL_DC1VOLTAGE = "dc1Voltage"; + public static final String CHANNEL_DC1CURRENT = "dc1Current"; + public static final String CHANNEL_DC1POWER = "dc1Power"; + public static final String CHANNEL_DC2VOLTAGE = "dc2Voltage"; + public static final String CHANNEL_DC2CURRENT = "dc2Current"; + public static final String CHANNEL_DC2POWER = "dc2Power"; + public static final String CHANNEL_DC3VOLTAGE = "dc3Voltage"; + public static final String CHANNEL_DC3CURRENT = "dc3Current"; + public static final String CHANNEL_DC3POWER = "dc3Power"; + + public static final String CHANNEL_AKTHOMECONSUMTIONSOLAR = "aktHomeConsumptionSolar"; + public static final String CHANNEL_AKTHOMECONSUMPTIONBAT = "aktHomeConsumptionBat"; + public static final String CHANNEL_AKTHOMECONSUMPTIONGRID = "aktHomeConsumptionGrid"; + public static final String CHANNEL_PHASESELHOMECONSUMPL1 = "phaseSelHomeConsumpL1"; + public static final String CHANNEL_PHASESELHOMECONSUMPL2 = "phaseSelHomeConsumpL2"; + public static final String CHANNEL_PHASESELHOMECONSUMPL3 = "phaseSelHomeConsumpL3"; + public static final String CHANNEL_GRIDFREQ = "gridFreq"; + public static final String CHANNEL_GRIDCOSPHI = "gridCosPhi"; + public static final String CHANNEL_HOMECONSUMPTION_DAY = "homeConsumptionDay"; + public static final String CHANNEL_OWNCONSUMPTION_DAY = "ownConsumptionDay"; + public static final String CHANNEL_OWNCONSRATE_DAY = "ownConsRateDay"; + public static final String CHANNEL_AUTONOMYDEGREE_DAY = "autonomyDegreeDay"; + public static final String CHANNEL_HOMECONSUMPTION_TOTAL = "homeConsumptionTotal"; + public static final String CHANNEL_OWNCONSUMPTION_TOTAL = "ownConsumptionTotal"; + public static final String CHANNEL_OPERATINGTIME_TOTAL = "operatingTimeTotal"; + public static final String CHANNEL_CURRENT = "current"; + public static final String CHANNEL_CURRENTDIR = "currentDir"; + public static final String CHANNEL_CHARGECYCLES = "chargeCycles"; + public static final String CHANNEL_BATTERYTEMPERATURE = "batteryTemperature"; + public static final String CHANNEL_LOGINTERVAL = "loginterval"; + public static final String CHANNEL_S0INPULSECNT = "s0InPulseCnt"; + public static final String CHANNEL_OWNCONSRATE_TOTAL = "ownConsRateTotal"; + public static final String CHANNEL_AUTONOMYDEGREE_TOTAL = "autonomyDegreeTotal"; + + public static final String CHANNEL_BATTERYVOLTAGE = "batteryVoltage"; + public static final String CHANNEL_BATSTATEOFCHARGE = "batStateOfCharge"; + public static final String CHANNEL_SELFCONSUMPTION = "selfConsumption"; + + public static final String CHANNEL_BATTERYUSAGECONSUMPTION = "batteryUsageConsumption"; + public static final String CHANNEL_SMARTBATTERYCONTROL = "smartBatteryControl"; + public static final String CHANNEL_MAXDEPTHOFDISCHARGE = "maxDepthOfDischarge"; + public static final String CHANNEL_SHADOWMANAGEMENT = "shadowManagement"; + public static final String CHANNEL_EXTERNALMODULECONTROL = "externalModuleControl"; + + public static final String CHANNEL_BATTERYUSAGECONSUMPTIONSET = "batteryUsageConsumptionSet"; + public static final String CHANNEL_BATTERYUSAGESTRATEGYSET = "batteryUsageStrategySet"; + public static final String CHANNEL_SMARTBATTERYCONTROLSET = "smartBatteryControlSet"; + public static final String CHANNEL_BATTERYCHARGETIMEFROMSET = "batteryChargeTimeFromSet"; + public static final String CHANNEL_BATTERYCHARGETIMETOSET = "batteryChargeTimeToSet"; + public static final String CHANNEL_MAXDEPTHOFDISCHARGESET = "maxDepthOfDischargeSet"; + public static final String CHANNEL_SHADOWMANAGEMENTSET = "shadowManagementSet"; + public static final String CHANNEL_EXTERNALMODULECONTROLSET = "externalModuleControlSet"; +} diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationChannelConfiguration.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationChannelConfiguration.java new file mode 100644 index 00000000000..dde6f0935e7 --- /dev/null +++ b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationChannelConfiguration.java @@ -0,0 +1,185 @@ +/** + * 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.kostalinverter.internal.secondgeneration; + +import java.util.ArrayList; +import java.util.List; + +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; + +/** + * The {@link SecondGenerationChannelConfiguration} class defines methods, which set up channel configuration, + * used in the second generation part of the binding. + * + * + * @author Christian Schneider - Initial contribution + * @author Christoph Weitkamp - Incorporated new QuantityType (Units of Measurement) + * @author Örjan Backsell - Added channels for (Piko1020, Piko New Generation) + */ + +@NonNullByDefault +public class SecondGenerationChannelConfiguration { + public String id; + public String tag; + public int num; + public Unit unit; + public String dxsEntries; + + public SecondGenerationChannelConfiguration(String id, String tag, int num, Unit unit, String dxsEntries) { + this.id = id; + this.tag = tag; + this.num = num; + this.unit = unit; + this.dxsEntries = dxsEntries; + } + + public static List getChannelConfiguration() { + final List channelConfiguration = new ArrayList<>(); + + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_GRIDOUTPUTPOWER, "td", 4, Units.WATT, "67109120")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_YIELD_DAY_SECOND_GEN, "td", 7, Units.WATT_HOUR, "251658754")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_YIELD_TOTAL_SECOND_GEN, "td", 10, Units.KILOWATT_HOUR, + "251658753")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_OPERATING_STATUS, "td", 13, Units.ONE, "16780032")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_GRIDVOLTAGEL1, "td", 16, Units.VOLT, "67109378")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_GRIDCURRENTL1, "td", 19, Units.AMPERE, "67109377")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_GRIDPOWERL1, "td", 22, Units.WATT, "67109379")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_GRIDVOLTAGEL2, "td", 25, Units.VOLT, "67109634")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_GRIDCURRENTL2, "td", 28, Units.AMPERE, "67109633")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_GRIDPOWERL2, "td", 31, Units.WATT, "67109635")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_GRIDVOLTAGEL3, "td", 34, Units.VOLT, "67109890")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_GRIDCURRENTL3, "td", 37, Units.AMPERE, "67109889")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_GRIDPOWERL3, "td", 40, Units.WATT, "67109891")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_DCPOWERPV, "td", 43, Units.WATT, "33556736")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_DC1VOLTAGE, "td", 46, Units.VOLT, "33555202")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_DC1CURRENT, "td", 49, Units.AMPERE, "33555201")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_DC1POWER, "td", 52, Units.WATT, "33555203")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_DC2VOLTAGE, "td", 55, Units.VOLT, "33555458")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_DC2CURRENT, "td", 58, Units.AMPERE, "33555457")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_DC2POWER, "td", 61, Units.WATT, "33555459")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_DC3VOLTAGE, "td", 64, Units.VOLT, "33555714")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_DC3CURRENT, "td", 67, Units.AMPERE, "33555713")); + channelConfiguration.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_DC3POWER, "td", 70, Units.WATT, "33555715")); + + return channelConfiguration; + } + + public static List getChannelConfigurationExt() { + final List channelConfigurationExt = new ArrayList<>(); + + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_AKTHOMECONSUMTIONSOLAR, "td", 73, Units.WATT, "83886336")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_AKTHOMECONSUMPTIONBAT, "td", 76, Units.WATT, "83886592")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_AKTHOMECONSUMPTIONGRID, "td", 79, Units.WATT, "83886848")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_PHASESELHOMECONSUMPL1, "td", 82, Units.WATT, "83887106")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_PHASESELHOMECONSUMPL2, "td", 85, Units.WATT, "83887362")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_PHASESELHOMECONSUMPL3, "td", 88, Units.WATT, "83887618")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_GRIDFREQ, "td", 91, Units.HERTZ, "67110400")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_GRIDCOSPHI, "td", 94, Units.DEGREE_ANGLE, "67110656")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_HOMECONSUMPTION_DAY, "td", 97, Units.WATT_HOUR, "251659010")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_OWNCONSUMPTION_DAY, "td", 100, Units.WATT_HOUR, "251659266")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_OWNCONSRATE_DAY, "td", 103, Units.PERCENT, "251659278")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_AUTONOMYDEGREE_DAY, "td", 106, Units.PERCENT, "251659279")); + channelConfigurationExt.add( + new SecondGenerationChannelConfiguration(SecondGenerationBindingConstants.CHANNEL_HOMECONSUMPTION_TOTAL, + "td", 109, Units.WATT_HOUR, "251659009")); + channelConfigurationExt.add( + new SecondGenerationChannelConfiguration(SecondGenerationBindingConstants.CHANNEL_OWNCONSUMPTION_TOTAL, + "td", 112, Units.WATT_HOUR, "251659265")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_OPERATINGTIME_TOTAL, "td", 115, Units.HOUR, "251658496")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_CURRENT, "td", 118, Units.AMPERE, "33556238")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_CURRENTDIR, "td", 121, Units.ONE, "33556230")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_CHARGECYCLES, "td", 124, Units.ONE, "33556228")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_BATTERYTEMPERATURE, "td", 127, SIUnits.CELSIUS, "33556227")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_LOGINTERVAL, "td", 130, Units.MINUTE, "150995968")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_S0INPULSECNT, "td", 133, Units.ONE, "184549632")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_OWNCONSRATE_TOTAL, "td", 136, Units.PERCENT, "251659280")); + channelConfigurationExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_AUTONOMYDEGREE_TOTAL, "td", 139, Units.PERCENT, "251659281")); + + return channelConfigurationExt; + } + + public static List getChannelConfigurationExtExt() { + final List channelConfigurationExtExt = new ArrayList<>(); + + channelConfigurationExtExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_BATTERYVOLTAGE, "td", 142, Units.VOLT, "33556226")); + channelConfigurationExtExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_BATSTATEOFCHARGE, "td", 145, Units.PERCENT, "33556229")); + channelConfigurationExtExt.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_SELFCONSUMPTION, "td", 148, Units.WATT, "83888128")); + return channelConfigurationExtExt; + } + + public static List getChannelConfigurationConfigurable() { + final List channelConfigurationConfigurable = new ArrayList<>(); + channelConfigurationConfigurable.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_BATTERYUSAGECONSUMPTION, "td", 151, Units.WATT, "33556249")); + channelConfigurationConfigurable.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_SMARTBATTERYCONTROL, "td", 154, Units.ONE, "33556484")); + channelConfigurationConfigurable.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_MAXDEPTHOFDISCHARGE, "td", 157, Units.ONE, "33556247")); + channelConfigurationConfigurable.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_SHADOWMANAGEMENT, "td", 160, Units.ONE, "33556483")); + channelConfigurationConfigurable.add(new SecondGenerationChannelConfiguration( + SecondGenerationBindingConstants.CHANNEL_EXTERNALMODULECONTROL, "td", 163, Units.ONE, "33556482")); + return channelConfigurationConfigurable; + } +} diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationConfigurationHandler.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationConfigurationHandler.java new file mode 100644 index 00000000000..e5d064e80a9 --- /dev/null +++ b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationConfigurationHandler.java @@ -0,0 +1,161 @@ +/** + * 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.kostalinverter.internal.secondgeneration; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.http.HttpHeader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonArray; +import com.google.gson.JsonIOException; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +/** + * The {@link SecondGenerationConfigurationHandler} is responsible for configuration changes, + * regarded to second generation part of the binding. + * + * @author Örjan Backsell - Initial contribution Piko1020, Piko New Generation + */ +@NonNullByDefault +public class SecondGenerationConfigurationHandler { + + public static void executeConfigurationChanges(HttpClient httpClient, String url, String username, String password, + String dxsId, String value) + throws InterruptedException, ExecutionException, TimeoutException, NoSuchAlgorithmException { + String urlLogin = url + "/api/login.json?"; + String salt = ""; + String sessionId = ""; + + Logger logger = LoggerFactory.getLogger(SecondGenerationConfigurationHandler.class); + + String getAuthenticateResponse = httpClient.GET(urlLogin).getContentAsString(); + + try { + JsonObject getAuthenticateResponseJsonObject = (JsonObject) new JsonParser() + .parse(transformJsonResponse(getAuthenticateResponse)); + + sessionId = extractSessionId(getAuthenticateResponseJsonObject); + + JsonObject authenticateJsonObject = new JsonParser().parse(getAuthenticateResponse.toString()) + .getAsJsonObject(); + salt = authenticateJsonObject.get("salt").getAsString(); + + String saltedPassword = new StringBuilder(password).append(salt).toString(); + MessageDigest mDigest = MessageDigest.getInstance("SHA1"); + + byte[] mDigestedPassword = mDigest.digest(saltedPassword.getBytes(StandardCharsets.UTF_8)); + StringBuilder loginPostStringBuilder = new StringBuilder(); + for (int i = 0; i < mDigestedPassword.length; i++) { + loginPostStringBuilder.append(Integer.toString((mDigestedPassword[i] & 0xff) + 0x100, 16).substring(1)); + } + String saltedmDigestedPwd = Base64.getEncoder().encodeToString(mDigest.digest(saltedPassword.getBytes())); + + String loginPostJsonData = "{\"mode\":1,\"userId\":\"" + username + "\",\"pwh\":\"" + saltedmDigestedPwd + + "\"}"; + + Request loginPostJsonResponse = httpClient.POST(urlLogin + "?sessionId=" + sessionId); + loginPostJsonResponse.header(HttpHeader.CONTENT_TYPE, "application/json"); + loginPostJsonResponse.content(new StringContentProvider(loginPostJsonData)); + ContentResponse loginPostJsonDataContentResponse = loginPostJsonResponse.send(); + + String loginPostResponse = new String(loginPostJsonDataContentResponse.getContent(), + StandardCharsets.UTF_8); + + JsonObject loginPostJsonObject = (JsonObject) new JsonParser() + .parse(transformJsonResponse(loginPostResponse)); + + sessionId = extractSessionId(loginPostJsonObject); + + // Part for sending data to Inverter + String postJsonData = "{\"dxsEntries\":[{\"dxsId\":" + dxsId + ",\"value\":" + value + "}]}"; + + Request postJsonDataRequest = httpClient.POST(url + "/api/dxs.json?sessionId=" + sessionId); + postJsonDataRequest.header(HttpHeader.CONTENT_TYPE, "application/json"); + postJsonDataRequest.content(new StringContentProvider(postJsonData)); + postJsonDataRequest.send(); + } catch (JsonIOException getAuthenticateResponseException) { + logger.debug("Could not read the response: {}", getAuthenticateResponseException.getMessage()); + } + } + + static String transformJsonResponse(String jsonResponse) { + // Method transformJsonResponse converts response,due to missing [] in ContentResponse + // postJsonDataContentResponse. + + int sessionStartPosition = jsonResponse.indexOf("session"); + int statusStartPosition = jsonResponse.indexOf("status"); + + StringBuilder transformStringBuilder = new StringBuilder(); + + transformStringBuilder.append(jsonResponse); + + transformStringBuilder.insert(sessionStartPosition + 9, '['); + int roleIdStartPosition = jsonResponse.indexOf("roleId"); + transformStringBuilder.insert(roleIdStartPosition + 11, ']'); + + transformStringBuilder.insert(statusStartPosition + 10, '['); + int codeStartPosition = jsonResponse.indexOf("code"); + transformStringBuilder.insert(codeStartPosition + 11, ']'); + + String transformJsonObject = transformStringBuilder.toString(); + + return transformJsonObject; + } + + // Method extractSessionId extracts sessionId from JsonObject + static String extractSessionId(JsonObject extractJsonObjectSessionId) { + Logger sessionIdLogger = LoggerFactory.getLogger(SecondGenerationConfigurationHandler.class); + String extractSessionId = ""; + JsonArray extractJsonArraySessionId = extractJsonObjectSessionId.getAsJsonArray("session"); + + int size = extractJsonArraySessionId.size(); + if (size > 0) { + extractSessionId = extractJsonArraySessionId.get(size - 1).getAsJsonObject().get("sessionId").getAsString(); + } + if (extractSessionId == "0") { + sessionIdLogger.debug(" Login Post Json Reponse not OK! , inverter answered with sessionId like: {}", + extractSessionId); + } + return extractSessionId; + } + + // Method extractCode extracts code from JsonObject + static String extractCode(JsonObject extractJsonObjectCode) { + Logger codeLogger = LoggerFactory.getLogger(SecondGenerationConfigurationHandler.class); + String extractCode = ""; + JsonArray extractJsonArrayCode = extractJsonObjectCode.getAsJsonArray("status"); + + int size = extractJsonArrayCode.size(); + if (size > 0) { + extractCode = extractJsonArrayCode.get(size - 1).getAsJsonObject().get("code").getAsString(); + } + if (extractCode != "0") { + codeLogger.debug(" Login Post Json Reponse not OK! , inverter answered with status code like: {}", + extractCode); + } + return extractCode; + } +} diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationDxsEntries.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationDxsEntries.java new file mode 100644 index 00000000000..cbfc286f8c6 --- /dev/null +++ b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationDxsEntries.java @@ -0,0 +1,35 @@ +/** + * 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.kostalinverter.internal.secondgeneration; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link SecondGenerationDxsEntries} class defines methods, which are + * used in the second generation part of the binding. + * + * @author Örjan Backsell - Initial contribution Piko1020, Piko New Generation + */ +@NonNullByDefault +public class SecondGenerationDxsEntries { + private String dxsId = ""; + private String value = ""; + + public String getId() { + return dxsId; + } + + public String getName() { + return value; + } +} diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationDxsEntriesContainerDTO.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationDxsEntriesContainerDTO.java new file mode 100644 index 00000000000..bbcf812ce3f --- /dev/null +++ b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationDxsEntriesContainerDTO.java @@ -0,0 +1,26 @@ +/** + * 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.kostalinverter.internal.secondgeneration; + +import java.util.List; + +/** + * The {@link SecondGenerationDxsEntriesContainer} class defines an Container, which is + * used in the second generation part of the binding. + * + * @author Örjan Backsell - Initial contribution Piko1020, Piko New Generation + */ + +public class SecondGenerationDxsEntriesContainerDTO { + public List dxsEntries; +} diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationHandler.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationHandler.java new file mode 100644 index 00000000000..2c0f6561591 --- /dev/null +++ b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationHandler.java @@ -0,0 +1,401 @@ +/** + * 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.kostalinverter.internal.secondgeneration; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +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.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * The {@link SecondGenerationHandler} is responsible for handling commands, which are + * sent to one of the channels, and initiation and refreshing regarded to second generation part of the binding. + * + * + * @author Christian Schneider - Initial contribution + * @author Christoph Weitkamp - Incorporated new QuantityType (Units of Measurement) + * @author Örjan Backsell - Redesigned regarding Piko1020, Piko New Generation + */ +@NonNullByDefault +public class SecondGenerationHandler extends BaseThingHandler { + + private final Logger logger = LoggerFactory.getLogger(SecondGenerationHandler.class); + + private @Nullable ScheduledFuture secondGenerationPoller; + + private final HttpClient httpClient; + + private List channelConfigs = new ArrayList<>(); + private List channelConfigsExt = new ArrayList<>(); + private List channelConfigsExtExt = new ArrayList<>(); + private List channelConfigsConfigurable = new ArrayList<>(); + private List channelConfigsAll = new ArrayList<>(); + + private List channelPostsTemp = new ArrayList<>(); + private List channelPostsTempExt = new ArrayList<>(); + private List channelPostsTempExtExt = new ArrayList<>(); + private List channelPostsTempAll = new ArrayList<>(); + + private SecondGenerationInverterConfig inverterConfig = new SecondGenerationInverterConfig(); + private Gson gson = new GsonBuilder().setPrettyPrinting().create(); + + public SecondGenerationHandler(Thing thing, HttpClient httpClient) { + super(thing); + this.httpClient = httpClient; + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + String url = inverterConfig.url; + String username = inverterConfig.username; + String password = inverterConfig.password; + String valueConfiguration = ""; + String dxsEntriesConf = ""; + + if (inverterConfig.hasBattery) { + switch (channelUID.getId()) { + case SecondGenerationBindingConstants.CHANNEL_BATTERYUSAGECONSUMPTIONSET: + valueConfiguration = command.toString(); + dxsEntriesConf = "33556249"; + preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf, + valueConfiguration); + break; + case SecondGenerationBindingConstants.CHANNEL_BATTERYUSAGESTRATEGYSET: + valueConfiguration = command.toString(); + dxsEntriesConf = "83888896"; + preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf, + valueConfiguration); + break; + case SecondGenerationBindingConstants.CHANNEL_SMARTBATTERYCONTROLSET: + if (command.toString().equals("ON")) { + valueConfiguration = "true"; + } + if (command.toString().equals("OFF")) { + valueConfiguration = "false"; + } + dxsEntriesConf = "33556484"; + preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf, + valueConfiguration); + break; + case SecondGenerationBindingConstants.CHANNEL_BATTERYCHARGETIMEFROMSET: + valueConfiguration = command.toString(); + String valueConfigurationFromTransformed = String.valueOf(stringToSeconds(valueConfiguration)); + dxsEntriesConf = "33556239"; + preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf, + valueConfigurationFromTransformed); + break; + case SecondGenerationBindingConstants.CHANNEL_BATTERYCHARGETIMETOSET: + valueConfiguration = command.toString(); + String valueConfigurationToTransformed = String.valueOf(stringToSeconds(valueConfiguration)); + dxsEntriesConf = "33556240"; + preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf, + valueConfigurationToTransformed); + break; + case SecondGenerationBindingConstants.CHANNEL_MAXDEPTHOFDISCHARGESET: + valueConfiguration = command.toString(); + dxsEntriesConf = "33556247"; + preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf, + valueConfiguration); + break; + case SecondGenerationBindingConstants.CHANNEL_SHADOWMANAGEMENTSET: + valueConfiguration = command.toString(); + dxsEntriesConf = "33556483"; + preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf, + valueConfiguration); + break; + case SecondGenerationBindingConstants.CHANNEL_EXTERNALMODULECONTROLSET: + valueConfiguration = command.toString(); + dxsEntriesConf = "33556482"; + preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf, + valueConfiguration); + break; + } + } + } + + @Override + public void initialize() { + // Set channel configuration parameters + channelConfigs = SecondGenerationChannelConfiguration.getChannelConfiguration(); + channelConfigsExt = SecondGenerationChannelConfiguration.getChannelConfigurationExt(); + channelConfigsExtExt = SecondGenerationChannelConfiguration.getChannelConfigurationExtExt(); + channelConfigsConfigurable = SecondGenerationChannelConfiguration.getChannelConfigurationConfigurable(); + + // Set inverter configuration parameters + final SecondGenerationInverterConfig inverterConfig = getConfigAs(SecondGenerationInverterConfig.class); + this.inverterConfig = inverterConfig; + + // Temporary value during initializing + updateStatus(ThingStatus.UNKNOWN); + + // Start update as configured + secondGenerationPoller = scheduler.scheduleWithFixedDelay(() -> { + try { + refresh(); + updateStatus(ThingStatus.ONLINE); + } catch (RuntimeException scheduleWithFixedDelayException) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + scheduleWithFixedDelayException.getClass().getName() + ":" + + scheduleWithFixedDelayException.getMessage()); + } catch (InterruptedException interruptedException) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + interruptedException.getClass().getName() + ":" + interruptedException.getMessage()); + } catch (ExecutionException executionException) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + executionException.getClass().getName() + ":" + executionException.getMessage()); + } catch (TimeoutException timeoutException) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + timeoutException.getClass().getName() + ":" + timeoutException.getMessage()); + } + }, 0, SecondGenerationInverterConfig.REFRESHINTERVAL_SEC, TimeUnit.SECONDS); + } + + @Override + public void dispose() { + final ScheduledFuture secondGenerationLocalPoller = secondGenerationPoller; + + if (secondGenerationLocalPoller != null) { + secondGenerationLocalPoller.cancel(true); + secondGenerationPoller = null; + } + } + + private void refresh() throws InterruptedException, ExecutionException, TimeoutException { + // Build posts for dxsEntries part + String dxsEntriesCall = inverterConfig.url + "/api/dxs.json?dxsEntries=" + channelConfigs.get(0).dxsEntries; + for (int i = 1; i < channelConfigs.size(); i++) { + dxsEntriesCall += ("&dxsEntries=" + channelConfigs.get(i).dxsEntries); + } + String jsonDxsEntriesResponse = callURL(dxsEntriesCall); + SecondGenerationDxsEntriesContainerDTO dxsEntriesContainer = gson.fromJson(jsonDxsEntriesResponse, + SecondGenerationDxsEntriesContainerDTO.class); + + String[] channelPosts = new String[23]; + int channelPostsCounter = 0; + for (SecondGenerationDxsEntries dxsentries : dxsEntriesContainer.dxsEntries) { + channelPosts[channelPostsCounter] = dxsentries.getName(); + channelPostsCounter++; + } + channelPostsTemp = List.of(channelPosts); + + // Build posts for dxsEntriesExt part + String dxsEntriesCallExt = inverterConfig.url + "/api/dxs.json?dxsEntries=" + + channelConfigsExt.get(0).dxsEntries; + for (int i = 1; i < channelConfigs.size(); i++) { + dxsEntriesCallExt += ("&dxsEntries=" + channelConfigsExt.get(i).dxsEntries); + } + String jsonDxsEntriesResponseExt = callURL(dxsEntriesCallExt); + SecondGenerationDxsEntriesContainerDTO dxsEntriesContainerExt = gson.fromJson(jsonDxsEntriesResponseExt, + SecondGenerationDxsEntriesContainerDTO.class); + String[] channelPostsExt = new String[23]; + int channelPostsCounterExt = 0; + for (SecondGenerationDxsEntries dxsentriesExt : dxsEntriesContainerExt.dxsEntries) { + channelPostsExt[channelPostsCounterExt] = dxsentriesExt.getName(); + channelPostsCounterExt++; + } + channelPostsTempExt = List.of(channelPostsExt); + + // Build posts for dxsEntriesExtExt part + String dxsEntriesCallExtExt = inverterConfig.url + "/api/dxs.json?dxsEntries=" + + channelConfigsExtExt.get(0).dxsEntries; + for (int i = 1; i < channelConfigsExtExt.size(); i++) { + dxsEntriesCallExtExt += ("&dxsEntries=" + channelConfigsExtExt.get(i).dxsEntries); + } + String jsonDxsEntriesResponseExtExt = callURL(dxsEntriesCallExtExt); + SecondGenerationDxsEntriesContainerDTO dxsEntriesContainerExtExt = gson.fromJson(jsonDxsEntriesResponseExtExt, + SecondGenerationDxsEntriesContainerDTO.class); + String[] channelPostsExtExt = new String[3]; + int channelPostsCounterExtExt = 0; + for (SecondGenerationDxsEntries dxsentriesExtExt : dxsEntriesContainerExtExt.dxsEntries) { + channelPostsExtExt[channelPostsCounterExtExt] = dxsentriesExtExt.getName(); + channelPostsCounterExtExt++; + } + channelPostsTempExtExt = List.of(channelPostsExtExt); + + // Concatenate posts for all parts except configurable channels + channelPostsTempAll = combinePostsLists(channelPostsTemp, channelPostsTempExt, channelPostsTempExtExt); + String[] channelPostsTempAll1 = channelPostsTempAll.toArray(new String[0]); + + // Build posts for dxsEntriesConfigureable part + String dxsEntriesCallConfigurable = inverterConfig.url + "/api/dxs.json?dxsEntries=" + + channelConfigsConfigurable.get(0).dxsEntries; + for (int i = 1; i < channelConfigsConfigurable.size(); i++) { + dxsEntriesCallConfigurable += ("&dxsEntries=" + channelConfigsConfigurable.get(i).dxsEntries); + } + String jsonDxsEntriesResponseConfigurable = callURL(dxsEntriesCallConfigurable); + SecondGenerationDxsEntriesContainerDTO dxsEntriesContainerConfigurable = gson + .fromJson(jsonDxsEntriesResponseConfigurable, SecondGenerationDxsEntriesContainerDTO.class); + String[] channelPostsConfigurable = new String[5]; + int channelPostsCounterConfigurable = 0; + for (SecondGenerationDxsEntries dxsentriesConfigurable : dxsEntriesContainerConfigurable.dxsEntries) { + channelPostsConfigurable[channelPostsCounterConfigurable] = dxsentriesConfigurable.getName(); + channelPostsCounterConfigurable++; + } + + // Create and update actual values for non-configurable channels + if (!inverterConfig.hasBattery) { + channelConfigsAll = combineChannelConfigLists(channelConfigs, channelConfigsExt, channelConfigsExtExt); + int channelValuesCounterAll = 0; + for (SecondGenerationChannelConfiguration cConfig : channelConfigsAll) { + String channel = cConfig.id; + updateState(channel, getState(channelPostsTempAll1[channelValuesCounterAll], cConfig.unit)); + channelValuesCounterAll++; + } + } + // Create and update actual values for all channels + if (inverterConfig.hasBattery) { + // Part for updating non-configurable channels + channelConfigsAll = combineChannelConfigLists(channelConfigs, channelConfigsExt, channelConfigsExtExt); + // Update the non-configurable channels + int channelValuesCounterAll = 0; + for (SecondGenerationChannelConfiguration cConfig : channelConfigsAll) { + String channel = cConfig.id; + updateState(channel, getState(channelPostsTempAll1[channelValuesCounterAll], cConfig.unit)); + channelValuesCounterAll++; + } + + // Part for updating configurable channels + int channelValuesCounterConfigurable = 0; + for (SecondGenerationChannelConfiguration cConfig : channelConfigsConfigurable) { + String channel = cConfig.id; + String value = channelPostsConfigurable[channelValuesCounterConfigurable]; + int dxsEntriesCheckCounter = 3; + if (cConfig.dxsEntries.equals("33556484")) { + dxsEntriesCheckCounter = 1; + } + if (cConfig.dxsEntries.equals("33556482")) { + dxsEntriesCheckCounter = 2; + } + switch (dxsEntriesCheckCounter) { + case 1: + if (value.equals("false")) { + updateState(channel, OnOffType.OFF); + } + if (value.equals("true")) { + updateState(channel, OnOffType.ON); + } + channelValuesCounterConfigurable++; + break; + case 2: + if (value.equals("false")) { + State stateFalse = new StringType("0"); + updateState(channel, stateFalse); + } + if (value.equals("true")) { + State stateTrue = new StringType("1"); + updateState(channel, stateTrue); + } + channelValuesCounterConfigurable++; + break; + case 3: + State stateOther = getState(channelPostsConfigurable[channelValuesCounterConfigurable], + cConfig.unit); + updateState(channel, stateOther); + channelValuesCounterConfigurable++; + break; + } + } + } + } + + // Help method of handleCommand to with SecondGenerationConfigurationHandler.executeConfigurationChanges method send + // configuration changes. + private final void preSetExecuteConfigurationChanges(HttpClient httpClient, String url, String username, + String password, String dxsEntriesConf, String valueConfiguration) { + try { + SecondGenerationConfigurationHandler.executeConfigurationChanges(httpClient, url, username, password, + dxsEntriesConf, valueConfiguration); + } catch (Exception handleCommandException) { + logger.debug("Handle command for {} on channel {}: {}: {}: {}: {}", thing.getUID(), httpClient, url, + dxsEntriesConf, valueConfiguration, handleCommandException.getMessage()); + } + } + + // Method callURL connect to inverter for value scraping + private final String callURL(String dxsEntriesCall) + throws InterruptedException, ExecutionException, TimeoutException { + String jsonDxsResponse = httpClient.GET(dxsEntriesCall).getContentAsString(); + return jsonDxsResponse; + } + + // Method getState is used for non-configurable values + private State getState(String value, @Nullable Unit unit) { + if (unit == null) { + return new StringType(value); + } else { + try { + return new QuantityType<>(new BigDecimal(value), unit); + } catch (NumberFormatException getStateException) { + logger.debug("Error parsing value '{}: {}'", value, getStateException.getMessage()); + return UnDefType.UNDEF; + } + } + } + + // Method stringToSeconds transform given time in 00:16 syntax to seconds syntax + private static long stringToSeconds(String stringTime) { + long secondsMin = Long.parseLong(stringTime.substring(3, 5)) * 60; + long secondsHrs = Long.parseLong(stringTime.substring(0, 2)) * 3600; + return secondsMin + secondsHrs; + } + + // Method to concatenate channelConfigs Lists to one List + @SafeVarargs + private final List combineChannelConfigLists( + List... args) { + List combinedChannelConfigLists = new ArrayList<>(); + for (List list : args) { + for (SecondGenerationChannelConfiguration i : list) { + combinedChannelConfigLists.add(i); + } + } + return combinedChannelConfigLists; + } + + // Method to concatenate channelPosts Lists to one List + @SafeVarargs + private final List combinePostsLists(List... args) { + List combinedPostsLists = new ArrayList<>(); + for (List list : args) { + for (String i : list) { + combinedPostsLists.add(i); + } + } + return combinedPostsLists; + } +} diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationInverterConfig.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationInverterConfig.java new file mode 100644 index 00000000000..f7a0588f465 --- /dev/null +++ b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/secondgeneration/SecondGenerationInverterConfig.java @@ -0,0 +1,36 @@ +/** + * 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.kostalinverter.internal.secondgeneration; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link SecondGenerationInverterConfig} class defines constants, which are + * used in the second generation part of the binding. + * + * @author Christian Schneider - Initial contribution + * @author Örjan Backsell - Added parameters for configuration options Piko1020, Piko New Generation + * + */ + +@NonNullByDefault +public class SecondGenerationInverterConfig { + public static final long REFRESHINTERVAL_SEC = 60; + + public String url = ""; + public String username = ""; + public String password = ""; + public String dxsIdConf = ""; + public String valueConf = ""; + public boolean hasBattery; +} diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/resources/OH-INF/config/SecondGeneration.xml b/bundles/org.openhab.binding.kostalinverter/src/main/resources/OH-INF/config/SecondGeneration.xml new file mode 100644 index 00000000000..f60c39f1c31 --- /dev/null +++ b/bundles/org.openhab.binding.kostalinverter/src/main/resources/OH-INF/config/SecondGeneration.xml @@ -0,0 +1,31 @@ + + + + + + IP address of the inverter. + url + + + + The username to the inverter. + + + + The password to the inverter. + password + + + + Refresh Interval in seconds. + 60 + + + + Type of inverter, with/without battery. + + + diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/resources/OH-INF/thing/Channels.xml b/bundles/org.openhab.binding.kostalinverter/src/main/resources/OH-INF/thing/Channels.xml index 68b15b3ce69..304aa0ebc66 100644 --- a/bundles/org.openhab.binding.kostalinverter/src/main/resources/OH-INF/thing/Channels.xml +++ b/bundles/org.openhab.binding.kostalinverter/src/main/resources/OH-INF/thing/Channels.xml @@ -32,7 +32,7 @@ Number:Power - Current own comsumption + Current own consumption Energy @@ -46,7 +46,7 @@ Number:Power - Current total homeconsumption + Current total home consumption Energy @@ -484,4 +484,438 @@ Energy + + Number:Power + + Current output power to the grid + Energy + + + + Number:Energy + + Total produced power day + Energy + + + + Number:Energy + + Total produced power + Energy + + + + Number:Dimensionless + + Operating status + Energy + + + + Number:ElectricPotential + + Current output voltage to the grid, L1 + Energy + + + + Number:ElectricCurrent + + Current output amperage to the grid, L1 + Energy + + + + Number:Power + + Current output power to the grid, L1 + Energy + + + + Number:ElectricPotential + + Current output voltage to the grid, L2 + Energy + + + + Number:ElectricCurrent + + Current output amperage to the grid, L2 + Energy + + + + Number:Power + + Current output power to the grid, L2 + Energy + + + + Number:ElectricPotential + + Current output voltage to the grid, L3 + Energy + + + + Number:ElectricCurrent + + Current output amperage to the grid, L3 + Energy + + + + Number:Power + + Current output power to the grid, L3 + Energy + + + + Number:Power + + Current power from solar panels + Energy + + + + Number:ElectricPotential + + Current voltage from solar panels, DC1 + Energy + + + + Number:ElectricCurrent + + Current amperage from solar panels, DC1 + Energy + + + + Number:Power + + Current power from solar panels, DC1 + Energy + + + + Number:ElectricPotential + + Current voltage from solar panels, DC2 + Energy + + + + Number:ElectricCurrent + + Current amperage from solar panels, DC2 + Energy + + + + Number:Power + + Current power from solar panels, DC2 + Energy + + + + Number:ElectricPotential + + Current voltage from solar panels, DC3 + Energy + + + + Number:ElectricCurrent + + Current amperage from solar panels, DC3 + Energy + + + + Number:Power + + Current power from solar panels, DC3 + Energy + + + + Number:Power + + Current consumption from solar panels + Energy + + + + Number:Power + + Current consumption from battery + Energy + + + + Number:Power + + Current consumption from grid + Energy + + + + Number:Power + + Current home consumption, L1 + Energy + + + + Number:Power + + Current home consumption, L2 + Energy + + + + Number:Power + + Current home consumption, L3 + Energy + + + + Number:Frequency + + Current frequency on grid + Energy + + + + Number:Angle + + Current power factor on grid + Energy + + + + Number:Energy + + Total home consumption day + Energy + + + + Number:Energy + + Total own consumption day + Energy + + + + Number:Dimensionless + + Total own consumption rate day + Energy + + + + Number:Dimensionless + + Total autonomy degree day + Energy + + + + Number:Energy + + Total home consumption + Energy + + + + Number:Energy + + Total own consumptionl + Energy + + + + Number:Time + + Total operating time + Energy + + + + Number:ElectricCurrent + + Current + Energy + + + + Number:Dimensionless + + Current direction + Energy + + + + Number:Dimensionless + + Total number of charge cycles + Energy + + + + Number:Temperature + + Current battery temperature + Energy + + + + Number:Time + + Value for log interval + Energy + + + + Number:Dimensionless + + S0-pulse counter + Energy + + + + Number:Dimensionless + + Total own consumption rate + Energy + + + + Number:Dimensionless + + Total autonomy degree + Energy + + + + Number:ElectricalPotential + + Current battery voltage + Energy + + + + Number:Dimensionless + + Current battery charge state + Energy + + + + Number:Power + + Current self consumption + Energy + + + + Number:Power + + Battery usage consumption + Energy + + + + Switch + + Smart battery control + Energy + + + + Number:Dimensionless + + Max depth of discharge + Energy + + + + Number:Dimensionless + + Shadow management + Energy + + + + Number:Dimensionless + + External Module Control + Energy + + + + String + + Set battery usage consumption + Energy + + + + String + + Set battery usage strategy + Energy + + + + Switch + + Set smart battery control + Energy + + + + String + + Set battery charge time from + Energy + + + + String + + Set battery charge time to + Energy + + + + String + + Set max depth of discharge + Energy + + + + String + + Set shadow management + Energy + + + + String + + Set External Module Control + Energy + + diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/resources/OH-INF/thing/PIKO1020.xml b/bundles/org.openhab.binding.kostalinverter/src/main/resources/OH-INF/thing/PIKO1020.xml new file mode 100644 index 00000000000..662dfe65537 --- /dev/null +++ b/bundles/org.openhab.binding.kostalinverter/src/main/resources/OH-INF/thing/PIKO1020.xml @@ -0,0 +1,79 @@ + + + + + Bindings for the KOSTAL PIKO 10-20 + Inverter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + KOSTAL Solar Electric GmbH + + + +