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 9a3d8ec74..b595afc5f 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 zcmb5V1z21^voO9`aadq+Ulw<3u_BARyA_ufic_3Y9E!UYEnc*gqQ%{53q^{%6)8@? z1O2`4d++`3_xzuKPIfX$CdtesnIz|A|IGYZ0)Z7|EjEsVTiuwo*3j+%a69W?y8wVeZjf01Si3uhG;}Jjz2??=qiAjhcB=`_Q z$bBUUNI)566m%36bO<&kHst@e`_l=+Lqq69)I&nR10mueAmJhW=>|~%tb!1c0Pt^z zfQX8O{0IdN$R)p*1IYYuxqt+ZQ2+b@VIct}!AM{b2w{BQe+v@ozWdwt*67TYwB}>T z*9=$QH69bG{?Y^+j^{6GEiDTNhpY_J2_Y)-8isZn;SCyrK@;|Ph-z#KH8gVCFnx>; zcaIN0$*T&`%_Y5GtWRe63@D|{?lqh^yuEwLt)@|lg22PlUzAT27*fv^S6Ebv5?GQa zrxyZ=kPAct6VbpL#7W{hM25$V><^BGnz(3NJzTbxpZ1g{dsj9+PWc0R@~wJ}_H{gl z3wSt$HrT~*z?>U1(o&N~gMFW9`Bz~%+oSInkqo}?vJ+j<2+%6L@Le~Yu;|dWlcplLdf&v2};3P#* zzEmx^P(2b|gBDE^d54OCBCq;Dj5Jm6gv4$4Ddo|k*YUQo@u}?WY?ZV7tK0f3y8>&I zcNDF^UwQVUh2)0N(8hyvHQ<3DGz@s2RAIa{O(_hW2!sd+N1^4|FNTT!r2B;3>U62g zQSVhD#Gmp`l4LRc#O|r_bnWd(h9|0O{ExcrfeKPj?zVFg7x-&A4G1C;5d=dV1VV&> zLV{_SG0eenuf@Y@iAaJ|E_in>{P^E7?F?-k{jA{g7U(^l9VtBXv}{mq{-mkEfO|T|@&y%o7KRgIL7t1Hnn*Nr7x|G$KTr*Jcq4HMEhwJr9+X*Ix+?wDN9F zW;B;3c~drdoXaN8suo!jtnS8gy*oEcGri6#;%4Fe&9 z#PKy~A>!h3ugy+$E2&;mUX{z{(Rpu<6n^xy)SPyh`1It`IC4`?8!-?A&;TN+o=HK1 zB^a2D=!A&luW3QF;Cz(VP{fru#go)BuNKdpwg5QC=~$|~-NDEBoyS{qb2lFh1ydj( zOj`J0iCS(PJA#59A|?h*T`e#kff+%AmaTih5&;b@E+`Zh?gsGSN%^B(H7~mPyA4P3 z#7|bbDTmED@|#i1O>XIWGzfxRE(DCAm79;uj3JJO2BHB4f`ggS#No77u?J;x@%r4_7<(r?tbWlY3JQ=U%=ku4zfRAdK2>HAqmQ0R}S!P@+1T1|$%S2+yUV zp+!TJ!h?1d@GQqZd9q9SbmpNmKRQ;P`QeNR`vfHSdthN|#oHpQF+oc~YSk#nK!*ea z6B$_n3`PWlUvo3RMT5)Am@xac0E6Lo|KgnBY3PUHX>aU|bD}>W*2hmndPBgFC<%;w zWE8}Z*U~f~W`qa{2@nmf92^mhh=d;(Ov9a++v0L*gL0pQIC>WLx(~Ilg8g5)Wes%hs-8kN} z&G(0@?u+X7=ORyZ4p|dMUBT))wBjfrpbo@95D2`RV~V-?%Xx~dLJA*@Cy(v`wJ_={ z8T2-KFGWL$4l{ZV=+h>?tH8j1H}v<|JB8=_;i3(Cj(4%Fyfu2#%n0I4fC)4R1UQ1n zbzju1bF~Fv_5~{x5&=U?trW!%D<{$N)Jx^Iup7S+%{-elm7)#q_T1sA6Cm5xtzS~{ z)EMPv`{0_2D;NWWW`KYx2MJ+GKnwyQM4YS^M{W4V1Xzr_4o)bKMb_%qRMWCsv9dmv z-h6!g;AFi%8LK|VsJ>#O-JOKPtjqe)wuyxE+Ad$9_F=Ee&LJjXC^2Icg46&zK`Vjt z0Y_lvBD$>@d%b6bXH_m?uwN>;D6JQN)rQjT>Fm#|(&rd31#@vp!fnN%i)bPDjSp!r zU!2;p`vncnk5QgYtR2gb=;ev$hJZk72vSVIFxLRNb6HTB;-yY4eNH8YuB7ETltU3B zUPn7yZ(X~#pE>Jk48TCa+$J$kkh)gCR=J<{FV1dF8eB8S4_RMs9bX<$gwoT(*`*PQ z;y`$cav>%7704lay^Ym$Vavx?zPgDdrQ>56tJcD^u4}Ul!;YC)JZ4!LRixwRHWL89 zkrDWaU=BtZtKb|0s&=IE_vC<%>8t83aaHSLC-zpiC}gAyEfJn+1c`$*=nZlM36Yc0 z{A|xYic;NjkZ15Z--Q*YE`O3+zs?p3$T{5iF*x~sv~?ph`!e^MO}MQ=RCvY1+3Qo@ zfoj2-hAw3sOdSn}x0t6>6(!6xs4I`-eC(av?mj4MXusi?M}v#oN|7O?mL9EXfF*wt zB`IM-o*wxlO(2T=+4Jce_p272==H08)%1lJ?lV2pxLH4J%6GD|nw!FAzx}&#z8A-) zT`Xjo_-$P$Zry4n_dLTh7ms%q;$>4ss4DN=-RAf`mMLeyAF9P`9QH}JeQv}3OhrW5 zy^_yx#HhEv<6XAn<1ga5wcVBxQiUMs_z?LJqn)?cSiz{3$fw!W&8tltnSrFGQ9A52 zT*LLY?}K|Ar_O6T*|>~s?u%iqqN&pstdS8!$blrBGFFk#_lXq@D z`qilnEUa&q!6Ls*8SM@k_3@7le=@GldxnBwnQVpMO%xtd$CTbgZYr70lhJj^^^3g9 zMrXKaWuPxbg#2Toz1Nt{FQ2EJbGv|6EtVf2d3`TBR4q6?p~&(;iwg|qPAy+EcW^wu_p!w)wA=g@WRW?^-+mD<}+ti-}dOHLdP zGg3);LinBRTEww+*Ykt#4_RH?k17_Pr(TP59R8M}E^*Z*z17Bu>o6u{CJc0OSV?zC z4MaBnQlz=%2|2&#;_}KTt(FVx7|OI?HqpwJ8)Ig_(r?9z`aaHy`~eMbhOuwoB43Fq z(>rUyrITQiNJVdZ+#A})Xsi7B+klmntl3Uyw*LIrzVC(C_iXmh5HxxKWTsexH;FLNZ5S4G|Sc4^E*d#|0c)O_2A>Nx8CK?;Ds=h zZNK~0q6KWxq&R3+g11jP~A87>N<2PM!?-+gLw!j2L-;D=KW&+XG)AJs-Ye)Eyb3Fc!;w zC%Nt1+xVjJ5lt|cNzN0(Z)DfLZLP;6`|m{Kzs~7G-~Ze?xs@@R{l@oN0scjs)?5vN zD2SyZ0v?Gbm*V`a*913mXv=H3AeOnOc1T#r-cJ1Qdb^>;Xu!UKKj| zDj#0XYC%YP^d!uz;s_YRhQ0d!XGl0hd%N#rqEwCQgWCN0cAyP18WOama zcsHi=Yt~ScdU>~R?-VyzlPVA2iN;R-DAWADzd4uL^20eU4^tc@oeS)n-qWM0yDc6;WtASYVM;PD`5FrS(aC*IPEp=c|9SnjYLx1&Aw6J}A6ss6%seA|& z{W>S%uxIonKsA2AMyoqF7cnUi5`r9<3!EdsXvuPNAHlh3;Cw_v8t4}(Fh0HWP;jeP zO5W7AybQ4X$nNYzDAJW@r`A0V+Dc!GGohe>z)&@i24Wy0&^QrDoYqVVO%DML9ac|= zuOl#zllWA3z&QYffzQl@fPf%}EMX=d2&PeFfH>rmw zU%OBOEggkEN~$74E2wi)4FlsnzqvLN6jp|;K&prcABU+yAug-!!_W7Y)5VkT7cgO6 zGbXhfQy@vu6bF|k^WeUrGc{+3PMwnmq3+rod8ld%RtDTQzWg zsCR5D{QO8g0;^UpT|bKA#f2Z0E??`gP+gy+QfY^qvsG11%wo!@<0yI49G{EQK$rXR zYJCU|o`6dygFT8#w~vF5XjUWV=%>!TsR*fhLPcr6ef{f72i?}UdNs^aD`q`Tdlt=~ z%IiPMsXb}%Z4%afhEk}IA5Y&$c9Bh`%5u(sdc4|HTTD3deaKW)*t=Qqnq+@xb8IYT zR5*s1;@4=PI1a_fE@;{4hMI)+Hb)0t}(?=`f8 zdsp93v4&Yt-d6ghs9`8H&xsmSmRIK|L|8$>8T4*MV;SR9U2$TJo>&f2=(`@x3o`f$ z+HYhErJuO2Hb=3`zZjxHL+DCD?&nUd>)%OUD(VpR>r11v^{yU2@oI^h?FwL<)5j4m zj$BRdBP=#$Csq12M9#wVkv&k~hO)=ThJ2@HXsTBcH?oc3sjtT-4;p?rvi!4CGS_oG ztMVuG`pL+3kj~{kr#-XwnyRu&BL(E8G&Tej{7yp|C}9$OnoR*QTn&s? zSO))s09as9HDVlK3B>VhL5PyTJ0pHDFsqS~k${&-|9W$T0GtO2Ajl|SyhlXbsDw1M zTzrpt`PE=N;u27LDLVKw;B68b@RA7u1@Y(wKNf=oF&=sg2O}2WdMHH=*17=HsSsJv z9AQYnHKlr!!)(;3i{f*rpeF_+u9?~a7V|ubW}{QFvzf|DXZApda3Y2$1Id9A_xy~- z=c7J1vzV=}?1@m}avV<`ii1W}4{_lJ^o4Pi!yr#z{7yUzw3<+TNw&Ncr=iD0(d;A! zT=izQonJ%BF?EP4*)t5e8q8iy&KA`vdL)q<)_V|^&HrRATtD)VJjQ)AmoBQUZPuBb zfS+*h{z4$c#|Ll#-aE*k6+aBdoHSinM9<7DufU?Ak&u-1uZa5J2q9Ccv;xjOU;$F1 zusz;lQ+#gFseb!$@68$&RxzuQVlG@JI%ry+wDmDQ*Za;)1qXa`1qYa!a$z(f*^40l z;tO)F+2V_+nl&#f+4yGVA6~Rg1hhQldH0?m@;ILmoQ^)O7rfSeO^}$Fm<)E^U}R)G zPAk!3_0l3@%=Sw-G;{XJmzIUSEcsoKDDcIgU8Ub&ylL6&>)S{85@AR?O$@zH1m3(> z!UdZ-SIuY3PnT%3lxi!KX=~&ks+ARKvoNb=Wt9}cOVt%Z;Q7fIky=0ljEoEqkdXm! zj2bR3E>?^{U~a|0py^6L+4zc?{PBoks*QSjlzseN-n;h~%EGjlk3Jjt*S|iaYqouk z*~%MjqMxe2KB4m%Rk_Fb$sNvJ=+(i?m7m6^^FRCAvc$g{ed3I%ahlCMa1AZ2j6K7; zp5@4n_kCkj{4)<}M{Kz78J*S4mhI;V3^Iw3OV64bed^E}w7n`CYlNLP=exX|YxajG zIosj5)0(co?`-ngYVMOMACeh!o=IFqU0(bFsg3!?WXIjSxtfArzeYVj{3k6mI}T{< z%>gp!eB0BJv!7kyYstM zI`imIas);viu4b8zW(pGfs5sr>y*TBy4N1umKF;kI2(ODD+>T=Kc_r3!&`H%Jj9N%U5ajB1>GSWCUbk0)flI*Kn_VZ zU&#u03IB3vO16K?=q?cZ!*1pe zsQ<~7E1T(eG|rRhUXE=tbUZ7AaFyUKmEd)Fo?3~Dipu@S;Tp*b0d>9nl$(Dh5Ie_l zuVf1cKtpWLg8qP>%0(|sIBk^%b@4xYk4MN3{sdpZv}BLVU)YapfX@~+;RJ=3jPyZi zDx?gUju!~4lW+e->ntG1b%oH_TVYW{%YcCoE|1;RI!LZlesZY;Fw z7~xuQxBsBPBrTaNP^e^~6(Yz}$9Ve>MLr>!mTiqe5ZjMuer5KN?vj3l%4hTGd>V|6 z_f2QM&yKwb>j>;?RFWL;HqRf8UrAme{QKnp6aFifqwS@=uA7`sc>nxO{13cOGkNxF zr^iw%JdN{32D2)pF*NErUn-_1u4AvUaYU=rE?M$_re&6_FPNU3wG#Flus6~`uE zbRh7*Jm?q+*SiqNG;GN|ely6))RKQ3^*;h!CXw8~iGKr;za$R<9u{jsr&04wwf9H; zCLFPoiP!`NR{XR#emr|h0&2O!NWm0!Pi3!T<-P5?e>TnlmXYjmW3ff_tKRV?8#HGf z8ZTFF%hQ>=3ssji;G(svLKosclBjobc@xc+ysn<_=jW(M`9n@Pid0gd3a-R2O zQp~$LCvQ-$htPIJkQhPyAOW9GQ%&fvLN_g`X(tGzj!h_|lgDS0?p<6Q;Q#IJ@rPeh z(u_%ZKpkXdtgMjn@@3N>(C^z<7W<*v`!4f3bdc}2(v%0S^jl5NjNc%80c%3gtuUP1 zo~M-Uaqms{dIair+DA(7aEJ%Y4~1JsZW@7-1Y~=xKC^S~5K}kK{DTZ<#>R&M^opd% z`{=h^8X9}(!v1o~_>iqt3r{c~`(L!4XOZ+J)~8UzF7~{Xqldg7qz+gOp@x#|W+~M} zdR7%Y-$y1+kcK$AIgCNX@t#T7ZdxLZtY~^sy-F4*tf<-^5|Qycpf6Q zOI(}_G*8|1w|N0grsv(HHwz!7j{7X^NeFKrr;jS_O?YW=561T%wYU+7tYUW7Os~sr zvY2BPgFbs;b$&b8YsV#``+m;+2c*~f6xD4iyyaJhhuHZoSTby}a!3=rM!V{v_&arv zfXi$VJal36N>aEJZxK9t6l~fh+({aMW#r3qM|z(neFMEC4M1xPHidI86CXuwnXk?; zh~oW|lAAiNb}_hWw;t#Mvb?UJ zj(3mmKHzTIL6u)(D|$e$0?u%@w}juP{{gk%OWVE^hJD*|*Glx^u3>xIOf>dGzE9o) z@wlfl?_uM^Y!{*6YR^AiEDQ2(_SRD^*e&_#CN=?Nk2Tr>#)3~-KtDh!z%($`6>FCK z2JMBe>`XxZU53r`yDpW!JBg)b=|$=#r=tX#8I5=@Z~>=Vhj1DCLR`##u;&ZjX0z-$ ztb=|V4@uz$(uE4)k6|8MK-9fUxE2ow$&7m(ua~}7csxLvH{=iK+5HT}CMfNUx?tkA z6Q!EK6MIubI)D%l)$+`}%dj50x)r9rM12#Y`v+v&!NZzY?n)BwnK^pmwdg23(dX58 zC`-Au+A{B%88Qrjm=otkd(VvQjn$TE4=X&mcw;<2;?pU!{w4L@OEA;c)sC{-)dVoAmbftPBPY2PCnz)}FLL>Tvrf*`Q7_ z-567*@)qR>8A0350bSeMhs!^WZ+tR$+cLM?GBdZ^-#Gt}GEM`&0zQbqKOeMh&hIJV zOMYc|C+zGNyN=@hmnUq~t7e}=#Kry zaJ#*z{U~$e!w|Qi4wQkIG)GELxhr?`g>_@0o)9fC)I`J<_0xqhZdBMQh9)msITbsP zS~xNP18R?&Ifm*v#+%{$n6-ON@NLXOj<_cHI%X1w+UNfS!5rX5_%i1I9oQLTY>AH; zBS&Sm(dmLCg?JpI`{d%GY`V5#z*2hj2V{w5FqsAH39}V|jbN#Ik1U~)js^C~tSyaRF29`Bk$YO2y*!Th*XM?c z!RAuYTd(-M%qN^2xMSro-B$K{=Au3kt8gTJ*4UQnAyKYDT;GN_rD7N{8!PPSYTR=^ zZB1*Wtv;rE(M)rR8g>oHsj}07{EbtNlJeTje}!rK*d%%L)-E<9yD z$kEa~_QE4AYOn&oce}}Y>+4Rx+1IU#kfyn!jJ0!Q_Imcc>MhVHu!5~Ct*sk9_cVy=Dgm7jKHPqdEl_ z=Zmtl{x=Wg0aUY?Z(JLrLYI6y$#_Fuk)QJzjS$Y_SX%J-E|QxcHTV#2ck0g${(t$T zqXuVeO`74%pmmY0b&)e;vyO^sR-ZTj zfTDndDH9%WFnxr8gaW+lxqtBmI0QUIBrpv(4?Y2xIx?-e+A|3V^x-TDoLt2asAh^` zj903s@3zDLfZ*A(LS^pLyQSa5ce~wJgnm)l#D}4^0biQ&SJ(ITuV(RU@w#g` zfAZJrF4mpZS~ozk&!1aneV=}AIr2i{ZhJrRqn}K@xAOO+1P`$wssclX=NIzAy3_X7 zb?yX5F0McgPt?UwS^41p4JrUp9grZbN&O}{gI3ItVaMjkrB44xc)5O4#D$=K#lB3% zNB}x3e|Ba3>OJj`iES#e`*_J&6m@WN#^|^Hn$yyG*ZBVRBl?ZHkF{)m!q^t`T@*6J ztq-NlBQ6N(bvzoRU%OarO>Hu>)P1axt2=!`f2dm?dt?RVjJ&>`T7Tjm;*27ekH-q` zjyEqqDgx4rKMR>syX}Aa;<(BVu~&MN!3~v_T^X#{cTmbHpjI2@>QdkHuJYEld2Vlc z#vn!`k@BBZr{CuRtDs1Sa{PwoFYQ}lbdF!{p?RnXGKyU?6`)KUt zX~gpwf!`%T>ON(PJvPr@?1x?mo-B6HJ+&W~(K2Xm7xI^cW z1d7a-{sD=RUhkf0!bku~(yNvdVFDHVFU(0_AY9z)w7hE1cqGKl2uaatsEr1@xl? zc?PIFH@&I*Px|bdsezI4mLfB;IYxb9qDQ7FwXD?4`*WwqPUZgiJG7#j zfFJM8U-1&yWo1h1Z>fE3i-c3JnJ#&PeJ1eKRG_DOA%^DDa#{kO8;p+S0Mi$SHck0| zKuBT!HoIlJb9tiJjtxF#oa$~)KV2VqAzOHHo@6*VqOGBxU&zK3URE76yOXbwH}l-+ zEHrnApCn-of6XF$7AqqME#}4qIMZj3$P%^BBr^hB?F<$3@{PHZ@$NkB7rJu00xDlH~{v4%mm%QX_ zf6*O(rT^hK9C^1dc}p*O)7M6A5(RS}@_nLr9{fWld(_=m;rX7kgjBr3A5KL@mmdlH z^>?hMWtla@C)@=jhB!2O^y`iPfNHTOuBBNEDa|h&81i2;uocqS*3imqztU*< zRv7nHY(V$6WV8I6u_x96qxSbtivH-14cFz~g6cF1A0+Gra^7Ly0^`-~(=vP8&L0qJ zsQ&{;FFOrezuw_u?h5_h2Jj!HS~2avoqDVM>#7`6Zs@kT|9}!GDyipGG(x))|LTBM z^g$bd4*t;#pawE>6`9bfctDZ&8Uj?BS8qYq9a^k^&q(N}Pl}@V-~sy&0MhCn(iHsA zrCT#Jw-oa-)Kv4>_olPkf|^K6n>5$IO6iXSSQ+C;nOp=Jp&PFO^Apu$#wF`BW40|Q zV6+yV3T~^y-IPym_>Glz-WNioTZNn?fX*g^!G*vq9Z>~{R|J*Wrr+MjfDPjeQ!ASA zgd7P958enxR2GCr6?vC65tJ+osci9Ico|dAjNu(83r8DYCd+rz25#T|r67B!1gsO{U=RqnvWtQG2(bHq_nW|H5M*v1 zFdj`JJ}nn7zl5_}d@TV~Tut4jhVPl#>x4WM2;AJYgH9s|cJ$8|kw;>P^@^gE7UZhc zS=}29+&sa9)Fu9T72@5+-3l;#teKJMS*!`*8NeEpu?@8iwWH(7LE?Mx0P=7AZwde( z|Kky*j_`>QXr2CoR0L*pIR&@|eq2&;Aw4Y+5eGsrEwEEYU`8_J*?IL!-)A8<{c%JA zd}jyQcmRV|iLHws;~A}dd`vvb^l9~ZGd0hP<%*`)%(OA{%pqXlbMQZolv2k`Nf^eg zR?>@asm*CvbHdQ6x$-PQ8k8Z{UlSOb{Ss*(Z?9&MIX@N;h2PyOs82no=F+BqsXhc% z!lCHUL)3D{Y9lu&nklp73fArR;r)A6^pgHqJ@b!BR-j z-MsWaoXR$aHKexATqc^mYkVhUa&EC9*Q-tWtiroqy29!&oa$eoh}i zCtcJ}oWGeDqVEty^g8uAb+5RRb`hkFl8j<4M*~m9u!^}WL0YM=OX-QmdDA{BIYO0hel#k@MAz^+L?q;0QTPD?_|< ziilVEhWF!dwbO58Yw02)|A3g@mGspz?r`g_O`Vba z=DU@7`c_Om)OU}Dd^Lt;CAk-@gcMI^u3}(cTvk9>|uW?H*y=sGYZU1M{d>ZIyzZ) zN!uHNS|r)SQf$^AC}*KeY1?GPmq|vz@K9$;x_?m)yfOa=LC!}sZA?-;$G`1vcgAfD zo0mMGJqKTh`*q`nzy9^eMxSUrXl_^WUEF0Y>P_FaL^(!Y7a>*y^;wE) zN^0x$WuncAGDiCu$4bQ#7XdT!&8^+D!jF=?)`{{74Y9Ts=cK=lWc*U`axA)SW`2=w zh{$-wf|axiE3I2NJ`56%YfpZ2*MXcKXAu>L$KM4L)^FLYi|w+#V}CFuLqctQ7PJ7z z=IYE5QujR>P7;{Zjf{*X-u+)a5Wk-zWW9*ogy@%7Q$f-uEWPN(e?aAgODX8As5GxI z_`a8fI&0M(zdHl|ERbM8?J*zc=o>P7FQ}$J*4-F1XStRgzV$)(Qe|s0`2@_4ls3mT9)V@*Dzl0xJVv(3 z)Ji}rR&A390vOX-0a%h}ellXZ)6l9C=>WCJ#_;m+zu+Pwxm&3XYW0hNGdvy0xd3uX z>815$n(chrGn3dfeYW6RLMk!j!>;CplaAKOj~ zL?rR>aB99M-_zeSQk**fxXu27)5PQJ(7BNZ+fXZgt@@CDKfhD_m+JwO0SI2(_p;Ic zAGG@q$k``2*9Tw+SR2#=z$QyoR$=$n&o2G$r<1{p>N8+jaNri3Q;lpvHI6S5+e4Rf zPxVAF$#ht;o(#Iv-jdW zjsEqfmz$)!01pq3fFupDO$2Cwd7GK$*Hj0GM}((!$ba*oHAe)Up;Vz=6`5#K7T*g# zi}PmBogs1pZ^<9&oTOqkLCs-ZB!2Cl8C6RmoPl+@NKg%ymM&rBv={%QHq8MliF^5e zOYT`d?~ap_)<&&FeEJe(uf96S^!PYO?id%C{xI9(p=4~xrBh!1I}Qk+=K5f0r0-|7 z3Dr~#GUnrDNoi3drhiCW?n$&BNW%RZ)h2L1VUW@Pha}{0Ev6h0oc2xt@+q!&hDP`T zgq#2&?eAqtDbaZ*+pxJ1Z2W7WIa5b~WFH{8uj`(K{+^@_AV~#C!1pB70Eyubj~ec8 zPOo_H=%_k>c|02r2oqWPK0)#uHM4+v^@e@l{(@VrQ+Jv3;~x;>|Evt)J#`3Z?_RtI zmj1W)SaFX&)$g0<&a~6~GC^P28va}6-#q?X3_vCS=J7#{|7fif&>A4d|Ik_pYD%o5 z@BMuMh}bwU-o=tcmre`5xN5D}0Nfy-*Z-<%5wdUtAN* z;{Uq3B1Rh(uPPDYMipd?Wr?KygZ3^S)mvWx`+S~0AdgPvS3nbe(1Bl1%RxXM@gD8e zd}I=P_f332pyaJ3^`yT#igshtPN8Ln*`4jpO#5hjfLd&{`j4dS%gs&daDN2PEisxK zq$AvJF|L4h>NuB$*j_=ZE|bJ$LG_8ps>=`-eFUs=qN^I$V_qxt9D_o zT3VyXq$o&o39tYBIAo8a1F?hS&HIs;kJ6a!lf=g_$Ru*F zXt8T%W72*`s&Q}d~r+Jm#DbB0P1t)OHrV2h=?z@)3U`mgbV(YCDbKU9DX^ zIrVzqv^xt6lEmFT;vjL!iXhsvlOmZd;P6AP<`W)&Qy4NH%Mv(xDucYS(MBha1zk$!WK&PL3>_4x!*}d*nPkGuv-D9$oZ32hR#$edSxR z^O>N4rOI3E$}>g?6Uf7)zTLkUVyK&c8LAIU)Q7!DV|d$&rT&c!GhCD+RCtgb<44#)j(~ao(il!KX`$w1M#}s4A+QgX{H}iP zf%Nw5-Wy(7%^(}~nZvOq__*BlM4bw&e*h7R-qLw_!F$<1AcFD_!A9BCj_j@dS|9O) zmGh%SKYdAl>vY06ouRBrKS6NAJR#HvNoce_&CIFqZ-4zuBGQe<4a>jZQcWu!>dAR_ zhsKpkQaEW3m)DYqB})BBP=y++)45rGbrPOr6~Reg*6OitT#EjU1a*+tgEbR~jxEJ6 zal<0M*}>9QGc1$jGO`ks@hz1Jq`t%Dz)(pIBn^}-L_SuP8dp;+H|LFQZejHqB>S@0 zoMyt<#C!pWzI`F2DQg3o>;sfi1BnDYS}GNv7^Nce{p3sM<+>A8^F#|1kt`?egulrb zj=(ahrV}gy{llw2HA{Vyr*OQ15$YUT!aHn&i4at9DgOX(2bK#7zM$X z;Q|;nQ&-%&#;&-j1dsgM_?yyG=rqx*lS(&+#-)gF=__HKhjNQGN-jlbmfmF6aWX%< z2*+?9dHV{nu*_s@g_m^NbQKY9HM$j73h7`A=pGqH2&uAsl-ae=knP&Bf6U9&`zb#r zp~_;q-&QF1c$>?Y95-~FMHw1rkV1TPHbUYAm2^IdUjUOGLml`eUC5_#z2qtT9b>GJ zE7wf&ZMTnC%`G z@G+S}gqO{xAU#WRLHQtff zr4PC^4l(2F`CTxq^QFUp6j`I4)Z{ZiX`8+FOyhcETK|Ce} z!EOg|*9mz=x*0t!8Zp0+@-i+QTMkUtINfGs^>Qu#ki+>kbFssy88cboO-wDYX{SmW zF?r0RUU}k+Eq=xuIf090ZPAg23Coj&n83MXlL2`X%9$j`A8Nh>p=mZylXc z&pomFNyl~sHHXOJ>CjSUN+eXNc6+`h0SA7{Lq%9xOsC)p`&MUab)&0w2AfJ7?d9>J zn-^l%M9c^kMdL4T?8^|b-g#*%32}^xPV1{1d5aMjm;F{<*pVZ`%|;-eHOh&aUJj#Ut zawcDeMMg|paX$84Ov`lVUB3UoRq@ulT>tR)8MfO3oAY-SoaeENV&2JDQl>xjfnBYb zRP43tf|yRk73w~9{nHcTOoxFIpO;P5oZ!zNw_>c`644*S3KSRmAYCexxiz%lB~3@t zOqVm@?ABv!XZ%4fm@OnZUa>l^dE&s{`$+h>xD~i;D@k5UvQ=iQi{Ch2$%8c?tiCiO zf0y_xm+tmjOk|eBz8!Q4B%DW7I=Zn70cUk^| z7n_^0Hz!9@g=@SNipbP(mmg*%xq?$_ky?^8xQxv+Y0!wQ*C8y`=9j$Vj(pZTgY zwuz(^XQAsWoMNO^f?~O7F_t))ikZ$Wz4#+1mem50^v+juvhccbpA`=?GbeROl0l8C zL=1BT74C$p#1!DWsovyAF-y>V%p>dj>^rffsai4_l{!AIiI3OEz&y#J&iZ9##f09{ zOCGAzg$G6dd@i*y{xxr6&!3^7YuS^bF_zEqRE1P2S61byeph zJH5U--dhu$N}MmhU5?ng7}wPqTAIlg-H zcu{hM7m4~a5A9Ar#ga#anxoQ?=nn9&DmqQnj?w6hXp2cJZHO`WtrR_5a8###J@zWY zxZ+(}gWssub8|1!l^3bEIUY>ybs}$b9*@zP%U)Uj+|GG4CTtT?*E_pmF+9t7MfF%j z>DA1JPt424h3jYw_qgm4;rA4#PHG!xSiSUdAu$@kS0zWU{5s`+V2;MW2EER~bRb#y z$^#2c!ElIB|@&TdZEpuSZTZGw5 z+!?V~8$Rz!Y=1T;)gAc9U(4>NzML1cy*<9D7i;_^OVCADDl{=wFH%C@BUf(aa~PK( zP5;wy$@y8s591fS8#0%#>1&YDz%x2b5mA55BB=L7eP&Xp%|XcEl*L$XfbbptxgfaJD9lr zB`Vd_{&|MIOTmtf!mI3Ffx>{!=FTSQ8xks}o!y$^!V78yc4d?tss6cL={GA07H__V zZ%71X3i&umO&G*k$E61^-cY?qC#~eZoacDbMOP}~pv|UrU0g-fD{}h1gTDwL2J5dv zj`(F?5bDr1epXe<-NT@s$ETFIWT8v&Bd)tdQM3Sg5)B#?CqZaofmMQimDkq$2)cIo zC4yT$r|(FJJE5bx0YBwbiiwZUTKa}$rN@(6 zRxhJ%HsJ86rEaLo@X_PnrusJ0i^>=)gBL;x_CjwHy|`c^@!rt*@pS1sN%dwO0fsvL z<&6G?M(dnilqPW?UA!c5*8z4aM2!_jF5m?9YQZzg|c z*meEtS`W6+$73*Z}$;7T!p{XHt(iPwjS0beEzX?uyr4w3j z*p+6C7>WwuYjIEPE92^;J^8@gxIpn6*~-z!c?`)iaX4`%$uP*aP^SLl(~GC2+se%2 zIYf`G(^n^3w!TpkH>c^bL!=Yv3Qzfk?4=?w%=w?zO9m)F z-Eohs-LXBZMh%tRZ1l<1r`_X8ciCxMaq{{4oZ0ZKC-CXZR8rwqBVWpB!{128y`#yF zE+G#ymuJj`jc1nv8~MUXW6IENk%8jMZP*0;Kn>3TzS6UlwYH)Gp9Fb6MHkBisamNX ze7PM4ZG3sU4YKJt7gBc?=`ulX?iq;;I{NttURZ+4;D}|0ycv{Rp0S%zC_!FiOhJ`L zo}oKY$x)RD*u63UA4d}9VHxtG!3nt;szo?*$AZHPb_KbmuxTGWMh4dqQP%gW#dL9* zI?^uk--BM}@&AS;*ugS%R#OG$apGVT!otXHO|X znH)C8W5=leBf)!8`dcZZi#*H`*h)W2%CH1&qTACp*HRUQ8FgWurp-q&)Jwg?zz44D z6J*As*nKp}Qyoae%%yJLF#aO6!(pxtn}8j<_@S5b0Nd_-IrqZfjGAzD7_i5e=Th2} z50c-zKkppKGjKuS9Wf)MCtKa9d4YZAkGd;+;(toJe5jO`Q6dEk?+F;93@f^_lvnHu zjn-9C+UTXamh94grtHpWQa32b*4O~ghn~g6eMTA1TFq8WS5Gy+Wo{@uZxr5~y0vwC z#kWCm>ISC@w3M6b7j<-G=4K1?L+w+Vq$kzYl7G!&Nz1T_9U->IrrKx{`)a*8(ZDAw z%vG6xUNfLR*MceHfPqyj_v^s4&{uRvMHipMdF9XCPX`WET}x6{-HzpV+!Gcy*2ODQ}*r~eG(GEiEc3q?4py7(l_?=UdH=o>>!4i)(92y z+|1m0?sK1!b)EZN6t#?_LmaIfZ5TsN&`C?7`c{QC>YRNdQ?1@~dSox1fd{ah7|NCM z0U_1r4fPchuqbV&F^UASpau-4|I2A~k_+41j;#^R7#Vjvukh4l`!f$~z~)y${{Aao zSw2V)xS*b2M;J^ym$cvDn)m~&no41VYe@I9o&0!gA+~YYgkY$LzTBi9zsWmagmH>d z6tcegQArcqnO-WQ?YHriDct6^k>!Z?ofS1=>zr01a~XI{5B0le{Y(fL4is87N2x~7 zY4sDm_zo*1?L2$P5sxVaBslJ%@ zrC(MRwmnwYALHtSjkR%-jZT9cx4rW`GH=PiBDu)D>9&K?>F_JF4!`XONVXk@!GBa` z{=?X*4C_5jP6)NZHQ(tURRMqIJTIhT_h?XyLaCjv#h%}eMup;zNu$EQiRnXU zvx*xn38gl<4nCOQD^~iPkmEBJt$M)~7hJLwoRGDjBi1QwT!JIPA*NGjDMCZ*peDrz6jJ1XA zX~v+;ajs9U?ztviKXN*{h#08frt7*f6NYjzt~X?wag`1-R{VWgcWnwYtFxd^W%TX- zx#pFL#&W*S_;|wED!bpN)u;8gWYieFB)0cfUKT}O>p2s^R@=Q*PDXVFplV=)G z2g5C{iU86#y(&eaEOm~%*RC>df{acj#Ff26#$d}ZosY=C zYfetP{qi5NdW-cg6Oi?zW0I5!ptS*4Qi)Q1Ge7N{_{L>XG45x1M=s?_(K)hkw6ZQ1 zA{sJs#@hm#c<1+>bv%hAUaXw}d({)fWbX2H23}aU%_{7f~yo{Xv+J`j3O3vauEb zq?`;RaZI!KKf=v+qNLxmOL=4s(DQ>YQa6L5GZiHFtI8A+=kOB?s@VIk6qxzGHH-kl}h z{Apo0!^8n0TCxAl_$O>B?h#Be<^3zw*1n(ZFK(Hyc}e`72=<*OX0rr>bNy?VF${#p z%cSk%f$uAPM!IYpK$lKF)Kh1-c)yt$aZ|NZjx#Th6w0T|iLYHA&Gx@mvoB>OQsDq5 z_$c2OsDB_ai9Jj#kPquqKGQ2c9I4{e$bwp3vt46U8Ylk)blr5^>a7Hjhj*k1=P^;aqUv>RNGO#=-bgcm(@MRK%1 ze|A|gOqd0rMPvk^s}dSZHTc7P&P|(|(gk=gPhhynADxITRgp8X=nzrt3wsAubp5^7 z>Az9_dQ0n&nJOecfy}FlsBJ0|YF{P1GkN+#cZ&A1`J7(Gunh==#+b4qMj%HbfHb+Q zL!QP^CaLbe1zwJO*V_~RAbZ2Fw$_PV_L1o^q)>Li#LS0+1YJrm2CxToW6jWTVNJY@ zSoaGeK&!<^ze@t}m0}IDA;7Rd{Qv-nr|D%i8lvXeK0Q0NTk<3J9_y*g&R;Q-lF~*c zKqw z;o~Y3PqlGESbuFQesBoA=Yf&gBCN4)o1!txsxf`({|ER7K$o36_54v4xNS>>%G9ez zlG*h2rvp>gpU~HS?%MJXE1o#0{KaNvSs`(MYRv448D?6xDRb~5dnP|>`jr_; zTKQxlqz#V!7L~WgjTjPx^Wj5EFUZQxiQjq5v^C4ovqs+l%_%#k#*Zp#vWyHR>ixlo z=O!mimMr%?e5B+WP% zv8lt4;UAaAeaJe!k^zcp@k+|qz|q_oXP&W)BQy;>f83CA^kG6zOxy>@bKSz7x3Q)P ztO?grT0dGhv~3Z7u0*zlas2}@?WybT!?LR+n3?cgsIXIis4Nq+{KY2qbTd`r97PJD zxtq}rQsNO5iD51q3q7zsp^|}s&!k%X)M*hI#T6gV?q{o`WjGkX;F@ zCx(f$aKKz^Qzr8J32IwYtbjtoC^_og~b`hjxI{2h;V^-C$^6{$3qB`@L##WW|8{r(aCl-l4-y@kJQ_rK^b>bl z`n)UfC$61eH8q$`ImkrTL{t(OWLJQ_XbSm00X^C5o$)#~t|XPsGK=`xWz(NLu`NYwfP8 z)Dj1AtyXx`F8GSADyl|<@@#g|f0tOCuThBe$u=Q)xjvIkO7)ycGVJf??|)AM?<2A-5$*8#vs!jEaOtK*9%Yf3mj0u)SI_!( zIxo^YUWwvzX|&w!C|f6`wjVXpeNd2?1V8!?ox;i*)cUc>4a{%puSV>GXbn(uZiy*xm(B-)nriBf_u; z-#ApyWf*Rsbcwi!M<)7IFyLw&=tsX2QO^HLV%$_iL&GN>6Q!_`HEV~#P4f9uV>8;h z`MDl(4*PkxC`=x>VbL~k#PR6b)?XXPtlrc34EMi~`J6BCjrtW_L?Lw~4(k6&0f_2f@DHKKwJtG1gzqE?)jh{VRd1r~u@ zaN@TZbvm>k2|>p%Q{qBx^KA}S4lEKwwj|iFyh${LisFpmcqMz*Emk^)PG$~xeLgN5 z4g|~Kbh`rIa|^l_kKCL0(x>iN!+d**w5Vfo(ieiG?x;&Cp3Ru>&s)*GY)nEvs-!&( zS|_`sC#@|#>X2a0eF$-2Dezit3)0j^Dz1kw# z=Nzhlq1z>J6IOB4pxh678-vK-!#k+e4dU@zkAs!Oli8p~FAWl0UJWdG(twn;^*&Hu zgj|geZa!IWMDUoOrLsmzu)QtGl;Y%g1nB0yns=P$ahm2i4K1)+1?`_PaUcJ36_dmc zOW;%E9SKI23sl~vO>u?+OElrb%K2N80Wo&BV`kbG8SlWf_;W@#;{dv>nAdM!2`f^t zdj|;uU~bwHaCr8xL|8eX;uc!5AcSIG*-GUVQd6ols<0-BrBMjXQ%K?DAVCqb`q4-e zB^1E7(<%@YbTb2tp0XY1rxtT!qpvKc*yv&^TfFOtV|e(hHm*sFKmiyF2w19oLiMPSptDhzkbj{LT!&# ze@!d2lc?xHZn=R$+-Mp$*32xYLct2g-PuZeni=PRVlINDv^FX$(19+Q*lo6_sV3xn zbqi>J1dRzC{F4>^YHYVyqPFuB87kYVG706*D~M#R_aR4Q(HdZnTC8Iy3@ zcncFw)539htrg9SJY4SLm}cZ3K<8R!F%0I`EACb zUS|m14eB}S9zQ~=2D%cZMdFu4N*l$nm?(u)hToB8KiLVCwCqAk-+gOvcFraP!U@^J zed>Mcaj^={$);{ksF&!}v+Yl#SEXLBavhh#eQ%d?uBG!HLp`q_w~lUP7LNV_sv9ew zxJN$1X;>r!_$Tq9AJN6dy@L+Ex_=bw0{^N0w{P|6gA9fB87T=d^G6OI70f8kK%22+~N;)^GZ3m?6I;|fY zryGs-dX3{=soPL^;s}ZrEETWH*C`xl(CE^uRLmXwbMs>3W+_R#|6-pf=wLtl^%9-) zWE2B~=A0bXF5N6+r8OtEOUSophG+8b!4Uay ze&e78uD0JBKZq%IO*VN@ZxpodA?tuF%{@0XYXoeXb=?t=ganz`PMOzb>jWrP_5!27l|#<&91cJ zL6NsrVpG0Q*DV}L`k%ima?ZJ6H%wFhm%^G_6MC7Xn|iKddyl6v(1QJ(e*aPVJWkR@ zYS#uIxS~z2qa6Bdg%mAHs5+lJwb?c8=SB`rtP@U83UD6DesMT+6ae;e*%Xx(GF-nAw23(IfU z)ogj_AnPV4{6%hxOo))EX2O)cAC36%2VXIDgsVgKB%TdU!T3)e}~X#SL{>=4&ilg%Tv4T z;4ITmTaoWSQpdf-G&oana_a8(>Qvrh~Pvg#N#0n~!aC>$O&mnX04nmHsPL@{!|`mwp#{7BR`{cdqC3^iL?J z{0b>(VTPbV&!-T7$Q?VDK*`E3n9Bltpk{R`nEZC;fuTL?$BW9B$8?n&+{4$5ix)1~ z`>I|omFsZ*n{>sS@V9+=*YH5U@)pg){EL*r9M`tW&~-&7U3Y*)`A>tCSb2dSjYAh) zdE)mVA4j)lOZD8uR3TyMPLXmpAyBx=;mprw&wqe7R5tUyN#=l&=60jtlfbvPv^w;YgfsqlQ){ugX#apbcX^lLio z#JnhKwrafYwY4B>l%6544+JgU>z2{Q@0|*M77%g&=5V`}38&A~V)Bfr?ZP-CYAf4P z;-!XJR2So;%XrG9wQG2yo zcshlCDRf5iZ=0y|l#BZXIE)m|fkV**vYA&#TzZq^{n~LEa-ajz9j#o1iTJ=f6(~8A~5l=qy)*z+jio;@fyvIvXJ)97u(Gs zZTs?dJO?;Js@!}jymJsWG&nQ@R=!sNrYI;)(uuaqqg!Xh(8z~SOw7ccG^iZD6&x+s zQ<9X?(z~`Dy1_gOOAHFDJl0xd4tgzby!ZzgSR1sL6Qr)-AZaO=xF@EDzldr1O~33c zLWtc#kfl}&Hwl&@Tu1yp&IHCUqkn*{O96Tgi{vw@;q-6G3SaD)tG_6_r>L>>IBB<` zq+)MyN-7SDY_}@$kQ|yH;3W9S3Tspp^8if5fiSYk2{u}FsZ zWZM}d7u9(EZ{mwUgWg4k_}dp6(<$$v9|ob|FAjyw0l_4_dE#qxNfXa0F(-_k!_DzA zI(>X4(wM_^=RxnuDGpUBw&(eC@x1vY@H@F%mVujin(4&9Q}2&rqcPjsK@q{)MsRkz|sAqqi7A4&fKa(%1Vl=YnAAmz`TYuj6BTT)c-?h+XJ|JUJ8 zqEAry)Sdtj4_ymbx6l+!g68}1`bpxRgp$lH^?oZ+>>!KGHqiA)`6>+!n*0(U+g7n(GxcPi|@NEu> z_`DVOg+`owIMu^y9%g*6pTcGUTPx55dtP0xx>y-pwX3dYC#ai8bzb#Anl~DJ7=>3} z%}1UdypC2y1Lx@#Q)U0<5QIO{6*k}M$1VMH=o0A3~5*JjIy@1c6v{G)rr2=NE|o3wni;hIg3o!69=u( zBlJvR7Nlr##YKY{FJ26EC%J>UNmy4+$q#L3R{umvVu9$;2QWDuy5c6#nc@en?JM=H zU9tB#>8NHY$*Wshj-^-{tV~X%RBCUiD;Ifct#q^lfNd)F%GQ87%d@LGlu zSjS0P|4E_Ppu)5jmxyz@A6VOQ{#w@CKa1aD+sX5*fNP~4f1>Z)z}aLnO6n>wf}bWE zQAg7LjmJYcg!s?H>$aK6{yzYON~u#$wmjOj2QujrR2Y8r|XU_yXrCjHtxO;kYj#!15u-$0; z>tPU5x3?kzqqDX<*sZ!J&)tRD>xPwl@UqH#N)`o@%jrHz-pREOZ0R^dB;sI1>0-FR zYey<9EEDG8l~eq1hNk^3Zkw*(pWTheYbed%0FuG}Jj_m41nne;jptiLY}JA&iBh~v z2blmR-=`3f%RbpGW%b%HeI*wOVj7Hc0`Zqm1Pofi{(w(0GpD}S-_v2si`#4ScO~eW z(%2!hV55}bWS{s9Abem;c+WB|Tu6VmjP0!u( zyLONPs!dcNX3dB^;6~KE&#}-1Hk~u3s~6XaBZ8<*9C)6|@X8sF4t=#a8CQ4Cap~9w9(N-lr?9?%6>D6&{G&SBZVs4kK;N;sz)- zpGHE&8~8j_E>HJ&#BT1fX-Y47u0fIChF$xS5ekQq5P3|{flO_SP4R)AaheDKSAo}% z=i?sc@ce4N9CFv-1=g=%yPPGf79pp56Qqd%>)K?0R6bR*x5^)L4~LJFDU7&F7-*3EP#B$-_ki1n6Qv* zuo_BW>WKGO9MGxX$M`A*B<*R$Cp_UaSU>dDMk_q7_#Vd1O*=y|4c} zr|XGe+aUD#&PiyOTR8;gQDY2yw*cXd>@CW1oU&q8CLms@(ar;+-jR!*ISo5{>tBRS z5b&9_T0Z3-uH=soZ#(x!(o>hLC^QC z@;8;DjONd|C1m!%qlety&S|O1fJ&&hXM$}sWrKUe`&?$eagNpFF7<;A4gUUnbb~TqoD>N$m@ zcpZ^Hq<1J*XmLzjtiYkCWJUoBGtsx}4jKpl-hYa4e$4gh_qn_hrvHmzKGB;msaR=% zo2|dQuYKi@eZ1-@I5AoG?h@p4|ISfU0JGxT6-7NC3=uM6&^y^Ha79_jbxeKCYH!8= zg(ly{0JWCzwenpc%*vU&H#oJIdXKpk|H@U5{>QUiFxM6Flq*#B%LpWt%#zOaQ=|Db zrGi5ZqX2%Y)P^4o3vgL{rmqY-J8*SIXeQR}UOp*R`voIhTWv1<_jiYBCG}>zhqwI0 zRa4f%M&nphIs0^b%KLne5f2WnP-+=IHY(+&o(m3=F!qX5UtWP_1~!9gSD|$N({Jyg zeB)s@!Kq#o-*we1l6dk%6;(>c5cv1GxTW6}HMCW1yJFl?8j5AF#nh?)98qAlUw6W@ zusyTXf+6PNeI=mbZ*VB}iIX=9Ib?Xfz<@)QT>(b59+h!LikHty(;{a?ca%0^xCBZ2 zVUMXX6ZglqqO%FV{90pisJo;D?F>RY}= z^#YP+_})#yj&@xkglq;&p9vO!tEoY!xZ`hGg<`pzdi`y(e`)X}56YHBP5WzJMaP3S zuvnkfh?@3Eb#UY&SGb(_xYD(d{&&+c38W-nGOHOKIFK*$?=Q*u(^G1&xbv?cG@RyNFLQ%s{L z7sCUCLkAahT`KcnT*N$Pa45Oa&tC%5w~Aw5B7wOIhCnUw{(!R^C6#!wH0N%o$cKYA z6uwM;EVHeq!;Ljo|2@wQDoxB}>G7|`%TH1y#L###KeFq?$ z>X|V!6BwFJUzLEljae9zK&y$#Q~DPx-f^vUjQ6pI{ecqG?#7}j!rmKU+ig}ZK+*z6RCtUj+L z2YAr>r548pAmJ)E`i7*)N=HeE zSA^ee79uthI@vdn3a%ko#5vPdl7Lp(GFDDi?LB0Fsd`_FH-gKl?Bm#Nfi@}jtd7l| z-E0cZoKi1_NeTuBQY729`u@uM2S}U~!HaGE@;>nk(Y^s+bltQ!k<((YBH}$02SS2V z@B7H|mAXc)eaWN{^{w-y2F(Vz_-eji{k_AQ5V1pr{~h-!xWng{i4EF^CZ2usKlIDa zU3{D}ZcrlwV)Lc%U2ZHtSv%PkR?J>`8(Uis}n+5u{Q z+4xR^ae`-^V=>Q@jJLF5_JP{Iz_|&_bgXjbfSHEiExLPcLg1UXW*0;|b-!HLQn)CU z6gjSl>y!vIhphxBF|5Fl8=R%XG)_Xf;d~7R67!ZyO|0+-6@+@Jsf$2P`?_=)PRavt zfxygagE_rXw@c6+kIv@&t1Y&$uaLV(g^#h;I}o(3c!9@hfXZ&jYJ%n@x}a>SKKn1- zK5+&|vWW|P0X!GsJOzt^C8I@8=68YGT#MztNvVH#`2>2-)n{*^3PhgXtlTB1`*bFf zF!C`Y;LQCMlAjR57Z~xO0>m%{?jPI0S!SAHK$1x$nsr__@8kEgdO=qCfvHbXl?Ctf zc8nKiXn5@&$)bpiQ1EJ?oM0``Tf+BXKiTcBDUn=@wV(^Jo)AO}aGmjVj1Rz^%V6!8 znb)@NOY2dC^gegv^C!>B-^m8%fy7q<0XAG;N5S<2mb)I5!39erPqxnH4^dmV-Sy7| zBAyQnHUlnYOA7d~5{1ssrcWvw#c`sVhThtu0kacWD6j|J(F>#qEw8~%c5xqb5j6!4 zk|NE8hT$$-V%f{S9dq*~cFg`nlPd`(%+h?DW%T-)HedK?`*Tw_5Nv!Ho%%A+xaL>r znrK~E7tkImuqJpwZDZT-g;Y94bjHa;U1{jOFWly=Uy*&x1V*5X7rEi;bFsxp`ptDY z4dAvf=&H5F5&;U9M=)M+w>8s$7*xd0Tjds-xPFBHM_}Z87 z*GhzlsL2~){|PM22E%YnvDsfHyJIuS*knqqYGlgsiy`+osbAFjX_b)k zQK}hb>cL!t{A;VTonxeDzy$Yz-qd)Cd5{6FFB22&j}5$2x4xorm!GpRJY)Da!ImvG z*>}dkyD5a<9y)6O0k7eUql0+&m*2J%chHLj8JX`RORX5y&6+MH2yauSYG0TlQ!GP4 zZ(yM?_jI4{W57bLE4K4}V}}kbaCBAQw)!mIoBE!3^WX%DrZ=+YwGT{K#J$s9tWJ8o zlfBwkf-Zjzr+8EUdSU2(D^kdF^m;>bNBqSThE`*n9Pt=1xDmgB^W!mh|9gbNW?2)Y z;M;vHQsJ3Hvy}lScTP2|Nrcrmx)xy!l~aMfrrS31cpD+IWZykr7-qk7J&8E?C(hld zSg9>~-=6bv=QN8nJH59`ofX&bUq3Dtn|cK8v~N$2%~(1_c+{M+>u_LF6sEK2I<=F(|zSG z$4L4@W{_ol%Qgg80?Ak7qHL2hgH3tSk*vXe!3vC9d&-r?npz%SD77d<;g|18OeD1um0qN6BlMq~%%*}<5TZv9S23`%}8dluO5CJz^m@~UV!iW2KZddLpWgueSm3Nsebe~LQN z+pIwPd;sZ~3ho5Ad%%`6&9}SZ0>uewyR0%3Hhaklo1mdw346a|M(!C&d7}09C8s}E zxLhB9_~utUZeSIElb>=vLkURUFz;{|XIl4@riU9blU~;pNAr}U;5jE};2kR}ba2vx zS?nOrZSpOdK{Z0*BHM5%NLo^$6hC=(Lj+p6?7*Sj(&d{uHjQqBZ-Nux4}@X}mu9TJAGHK7N6* zk|z;sZe;RpNu%X!T2)b(j$vyEe%@9Ra&QPHT8E>fM`Mj2}D7gXP`> z22D<;NXpmp@DEDeU@!gNC)x8g_3X5PsAl_};fUlLi`k^WW~z?Y`yf%j1xj`tX}p5H z{1NXv-|eYDf2K&a4uty?pLAY!s+xgi!|CiOgr~pBk=Ya zyK%N$PpJCfCDg=h=Qj5kd!8ryCDAhN+TE_gME7V<} zBq_Zt%^%ao8$j5p+(Ai}Dp;eaPux;oAK`9!I6+LA`O*9{Qpc!}$IlJT!ttbpcgOi) zV}kR7yV`S{ci;7NM?0|~EBx!Px)SzH*s`Rxo2;~J?_rdb2mVqd#LGt^N*rAL=Z417 zHqYh_kzv&hBKroZS)gX6%)e`x)AI|wWLCOMP78tD&D-~RW_kAOrQ~@L;uBY#!{W!1 zf2Nb#pRS*P&y6j6DOWi*Jv;MDxy2-Ug%B6`33KH)P(|or)(|obT z8nQ?|pM-`p6S_gU=y6o9@7v2i1ldm(S1mI3qXjGyJ&X_2o-VvE3`i(O{i(=k+zjt$ z@ss^;$c%!v2`%5gOUp-2txPylI}g<`F!HR;MmPL z@Tdvs(RPD6x`9$cHnIF}-M9~2=aE(sYf#F6<)FucevD&aiDfCB@EPMd(Pm8Yf;cvK z(PSJfXVW5K6h5BQ+J#ss7S#wY`ptGMtlwe%HMIy#Z>3PTu?t_p`5z#j9mW@D=+ef1 z?O1?1HDWOPHP1nsgs&}5P+UDx`%WwIqq4*mA|-fn5(!fL`fl?lJQ`}@^~RS+#30Z! zy>?sTDcZ>)frAy3<0peuG?u2WNR*CQuKMsaG}W1j37wrc9r6#b8j;T9Mal0k9xXML zAWM>XluR}3-R%mXH2*r*S1I7ejrKC4WcVG)_79lLGCu|Dc;^f9L(!6^CwMWvm67dR zbH_-HY@REL-DHNux5?IYNLsW+A>tON1f0#nYmr&SD=~WK@V2}{dom)|_()TF8-d~g z2JF)Nh?Fe*6vg#xs$QgwD^j2SP|kHjdI7T!Go2oJO+5LI6)ho`ak%v8HrlNl#vNV)uONlmcx8UTg`i_u`6p-%>rQwmp=?v8g6UFIQh)rQ zQqW7$#O~{t;2e$#eW)h0D!{xgHJnfP>uUGQ$9So*t9Q6Xa2cxstkA>x=ems9%R-s= zi2nfGB;nEy{j&)trvv?6?7`o*8~dKWy!!1uKg{1%hF@*3KP^m!)Tpc${sTBzt^AQt z*&~;b+#9o&-0`=$D}CXtn+!Qne;7y+nIXgeWK2)bzI{5Ob1=0`-)_O5Goi#>`z22a zhOC51!w%QWT*s!%6K+{^|b{tn!~5we#?kr32u z?%@vhO?`-T=_^rUIivh&>9T$S?&^tJRwZ=xoY{We!@$ai_SPL zc8iv3`Zbm|kaSFPkA=2<=VAxm2KopoN0=zby7oARO(%Lxaka(PU>bt)pL|tTb8}w< z`{Aw-5KH6!TMuoaFf!+#QWsRW7eIEih8PNUqMTyaTl^eNxiUo2g5Tb}-BaqZ`flC( zOmkkKh=qV5%CAFAuiIT+ljRuA!#2j{2&F+%S6Bd|D7gfRM#bvE!*CgQL& zaCUUq+#@u)jYrhJ=9J>S`L(zP9o>-9FO!_bujfy*PbL&&_A`PrBBo0}NfQkiTd;dX zN2~^N!HAQq*NQdS4Oq(vy6R**lrh$kE3v&f>v~?q%J__B5LY>$l^N5@oug!T)JHQZ z_9unx>!M^XY4e&wrVPn%h|?_{l7v+PHn1%wHfivN-hC*tlVke#wM%B@sSJ*C)l3C)th`h;2s{T82}_0BLF%c)kNB2e{sDd%8coB= zL{XwLqr6J6rVB8OiMk*S#K~U{aSL*aex^81&FJ~5=O2IsPe4f+zN*pwVV#GjT*ukF z+|o>uKS5;qKoYw;g=i^7M>4Y;6n8otp;#q~7#fDJP>?7c%3;D+cIE=t`>54m_Wlr3 z_eb!2WL~{!($6(l2|t01Jg9yRlRsrAZckFl^T?M>%caGqq&)9u+b5e9l0+vMN1OK} zIcXY-P8IS)?oJ-JlpOtW@~59UB(nZ!Mnu28F?xznBw`faPjO)~4o7kYFZ#09C-l8K z9J>dEs3V@)4(w~Us$=Qsl*72bF)kF}m-1HYUJxWRS=_KkA`~sEdJ|=wB)YtbwM@v} zj7e4@67cxtLh1QCXWM3?N>NDdA)ABoDiALzz#iPz%>{lpo8E!;YJmP$k`{4p`s1*A zB)dEJ)*H_J4pi;)VDpBGU$3XalaDmiyVzS5@4Efo76(DTzIKtgT0Sttjv{-J&e)x8 zPKXkY*sZmY^|8GDpYkh>plS^o{h_?VUmKJNX-Nt%M`~Aiz{{R2QTwMp4mhviTq<3tyT`K+dmX9?dH{p5T#v>Egd zK6$#lZo3Nl`$lkAOtWm9Jdhsjw|;ztdNfNQXM;<9?hfR*v;EZsC$2JiKX$Z}=tRdv%Phi9pBBe1U>SX_`Kit&Tm@6)hQKj#EJgK~`6V1$d><>}_b z9Syc#n3xD2yD`LL)g?=N`=aTH?6z0z%xyf(Tc)wy*jEH0EciJ^-;^rb8}+DRF0~gr zI{OBo;~-+66l|kYYg?a;#NvHIP=`)yiaug{EpiI?JAy$-oJMg+oK;N zk6s7b&>7}hi|KrB(wjBzhY$^Q*kf*>+C~pYt?NBx9wotM*h?y=b-2kcMnxwm~Zq zu&vn@N~-Z+Jdd0vJ3&yfi*Qh#!>dhU&7d_$C%YvD=C2K7ytt+x{7Nx$m#OXS zrDxq*W7DDoO+rMzDb^kf>FEtpmQ-&qzRc{e&QqGeV_h`L(j^Xt+1=mH6YzMLY4_+$9I`wS6gDmk_m zIj(4~lE@ct(zA8{nio%k%=~3LI;?)mYuR%ORojp(dE^BDegN?=sbK2}%1DVX(qI#B zKKZ4-*#duq3)4Aen*d^kxpC`R&G7ddf%hqt>)ddn878kWw{b5`fj^k{3n*7oTx8| zX>skHK8ZSbJ|^W}wdPh`PkMj~pO0Sg-$m^X4rOunN7n@gRW3HqN>>X+LUIDa_HsJA z7d@$h(uK9pTTkAQZ3?XMW2fMGeTcT6Pv`TMYk11@XyYy}U-EEm7iBmMtgRwK_SM6} z=3n3JiR!X8*Z1mNKW0B7cd&cIXrPmh-$U;pHj^8_Ln>K?N;u=1aLICTY~!3WV(sAz zj`;_enMXSg)1wkknFl!Ja9rRlX!%l3L~(0vG`sQn=fOnHkL~sDNJ^oT#*QpHy3688 zmN$HxHqz{2Ks2$QDgNbUCbrtU3z!9M(z+q(+xaB4MK-m2Etp3dJu(09<5CplU>IpCSUAmzF^0Mi{Jy8KN4V6<~?TR*jA(UyU zMIsW0&*thv^)f!a9%P~n#$CKS4`AMC@?H%ut9*Ybq1P7pHFf$SfrNp18U1XQB{Vo9jjJmPY@`Y_!8<>jAk@7KBhKLOGbE$yhd$tv7N zvv1U_oj@yaJ+10t@>)4#d6&?bs~h(Rx5p80XzM!n8yPjJZ^Y+n^guoQvV}EdOVQ?1W2zXib@-nB6Drl!EwHz+oj{{TKLNrQml3Ggsv5J_~LwmueXEI0ap-0~uCfHk~Sf zl8$VZU@kBlOX_#o#bGVFLFNo2y=gNQuMj_LD1BbV9s*q)WC^n~qWg z$$UbElc0;$>#0hx%4!xu?BsXRae?3o58MZ^H_<%wO)+f^(a(@}G7 zV)E*gfY-UU<^V^r;5R6}+0?CGGqO{4g@6Zk*MLh=THM)2I$?LJVVtKDtTvs>D(Ys& zuDjHI{V=M}=5E%p9IbD-W3}d8R$KF^qUyaum``Z{<9EAYQPJjd!hFmH3ztck9co?` z+l))De-iHpEVOcKiK6N13yRCr7dZ>@2DM4%RuoSmGg`-%85%LeGeWhv7{(?=BaWuZ z{YpD}^A(r)ncBM4wI~{o0L45@1*R0N&xvOTxq+>7FWz+m=!<%r7^UZlVa{c>cg*D4 zI+)XT$3C8-%a2mkOds=i z5SeZO1DtUfiM_KD;o~t@42B2|9Nae60#utm^(5eb5iK9M(bPHWP*gA)h278-OIi09 zl|0nETa&11s^(gl#HOnwFzu?ARi9)O6=TdZfTzs0bb5;O?1!9w67A+I*WxO$*SNQ> zz&I%PJ2MYVOCihMe!Xt#{_24WK88gEZrUQI_e=5o}_mb1s!-+nIHa`D%i+j(L65k$J2exd09kllZGk+4u+-Pot z+*U$YPGVk1mfm4WcYYu>pOcAt^&Gc%oYFd#Prru<->-Pdb)w5LK&sm{Jt7#kE-YirLG#>n-;x zQ)B0IJ?qR4gCwWRZ&HM+)b9L24C|cA9&b{m3O*Tk6!jI&5DA5+GLCaz_do0r4ljK{ z_V4inW!t8|6AfHy25Eh{+*8T-P@`N{+2K!yI8q-329Lu7n?&;vy$^93D;h12oyxfu z`jvHVa}jwJ)lMs^XGt7#D)CaAW>uV?r7uOnOy}kRbmPpZXuokLuARW6&&+DYrM7ST zoM&6MbS?25o_mVeHV_;fu^Y|BI^xpuEq{9-I_gydbitD|^ezl7W*58OqhhE!mwVD) z9WJ8Wu%x*#JC{v*xn$dYn3l~Uys%im!4*+>?qP*TPN0dK@i2RZGpON=tW>IR#B4o( zF=#6(-0&R1Yj^GdSxYXC02~{RLHrk2o4!a$$v8HzUAJ+zYta4xtR`a zm8>c8Ec0Zz;ZDhDzI(ZL*F;_`7^W`#aU64Ka7%38aU+vEsYMS`jYW)#-*8CghAPT_ zr#gOSPG1pgE02gbgp>>UVNuBTK(CJzn?DgvTNSfDB8KbSal$(iFC$gdwe{jW7QB%| zm3+(I;AFvKl#7zLG6tLYh#4Vb$%PKeV5#jX} zRsfb!u6c$z=H>cx;ysKTVS5iyblNA>#KpeiXc1iESPEOFIBnuKcym!9y7dHaH=&3^ zWtAKLC6_-CLvO(@*`T~ijuY&c^AsC5!wP3h%vkpQ6A9jPS2P|T8271TUsEBcCmc&Y zrGc-^4l;}saf8dbK+iE;OnQr4Q@ibhvi;R-~s48EPX`Z2lK~1cIXcEg*hT}Xf+`t+t$>3xqv8&l zJWQ_b>RxnNsY`1SIaiEKH)JSMtF~FY-%(RHM^R1z_ZkdS`GPRJ;$C%r;^4{YaW0xs zdWyWd^BNqm$g69XT*EoOqgCU(inKPxFxGsrMbBD=y&OXfig(l+3em15vliq$k$can zl9&0510EZk^KTOb@2IM(;$C~1z^VoH7QOWa2R8JQ@Gg82?xlzCa8@+enMI=bm}&Qj zrCu-0zl!c#<%xJG^8syA_>CK&_bjKcc#G*CNZ8xetXp@Za{}2K654JuTz}Fmc5Kap2U!(IWgU_3k(ATWPChc_f zGIuY{`i`0W%Z69f!zibmM8gdnEKcpdrdijH<@mReYNd$0v}RQQ0FgoGx|B`87Y*NR z1w8d~`(L6I#=J&^{ldX`m*dz)oHs4CekHoMyP0LzQvP0HtsjWHw5f#4f-0)x>5dur zVix}Zh^ILvD|m)|_$BkA`D2zrb&Qd83iyu3JmMxAxj15Kab187JX}9P=tgpqq$0P^#-Z;ur!91Pq4*+(;{W z_{eyc(T|&{y5%rSqB;Ca-&U|s_r=9PJ#v_p5pGq-*E=qd4WqC z^vdj~_dHXrMHCHrjf#tYSRD7n=&Jlnbt(!`dwQ3Zs=Zr1m#JjVFP^2)Z~ICI1KGs1 z^(~h0KQi2;@J6p|_Y@npj0DE|+!5NZJ;m;yGL2>Xj2BDlQNXt@or^GYl4blR7st6= zmt!^y-*S!lnE-2xj+d#+Hb>J2nqBb~ytUCS6;*Xo&Dre40MSD=4_8^m3UMszM2`!m z;PpYTF6KhaMSn&2ZnD396V8Xr4*(>G=Dh6iwkLo>`@`U2cIo#Emxq#3f_P#)jm6k);x}xr zE+b>y=xS1IuPmcUZ~?&cEY03G8@HZ~GV^53R=#FRU3rUU&qSvJS95^dm_9OlgSO!0 zWo&zLAZjx}L;zQ8sWDlE!+kC&z6;_i;$&ws)UF`OJYIT@nrbL0B3Ag9b!@6rsI$Bg z94{%#73sc^C8vs-g{9;(SKnEMd3<0aob`iMm!!kpt~7#KdfrKZ%koMMjTn`Myh|+o z#T0}H;@A0zwTFsj9J89=xsz|WhyB4_zF@m|?hgL|lr2m?W~ToD40nF> zcHc6>ULNK}#{9(rdgNoweZeJO;b$M2YTMeu9A)kc{{X2;xj`WBA2IG^+B0WAb0;5} zMxA?>wnAHSpjNiMj%R8hlnt9br31(G)r{zFzwdn z;*$R83k&xZK@WE_FN<)~`6XTptg^~++QKi15n048xI*VXV4U-uL1lNFkJ1O3R~x&P zrpbOUfurmQ=N3FnZO5`){Y*qFC$dx&?h317r#2{WFAP*-28*lsB_Nq?T42!*#lYGn zF#AcAnDvrU+RfuV*VbXT>6BOLgPVRKlKu5932S5=u&yjS%(cuIvfw5-w9$1RYB z{i2jo3>tckcc094lk+@N-0lAWF;;%8WMfgxscBJnkb=^Bhsc+g!za zY$HUcEkcAtMKb0fU1KuiG57$V&mVJbJ;yXcizdo5HC6Kl9O4)$#IdzAF6l<$S9*@i zHDsr5_WMc&d4(>VcPN7VMWMx!sHtIbuHxk*sTSTyHLH#bf;F7Q`J2r}SDa0X=h;Ai zvon3fhK@6c6mcm)+D6Y20Miji7=ng0cQS+LiC`;bLz&XdYFs$PD_g%%E}#~#FpXC$ zW8o;Hj548UVl?U`K)Y%XM~akrr!{4i+)7Fw{w`}YZ253n&1k=4yJFoSeBD0 z^@9zzyugMf7;`g*pd89$BxTC$)TMm$GTvY#SGj=mDDxNTFa|CPou+x5O;%Tb)uK;h zWVLN1#b1fZH5y!UVklP5qc2kupcZS+rV9Y_`-$iC5}TFsnr+uC;W6|cXE|YIeV%4KUP#)`NKIhSvM;$Y`w#ofzV)XN0X1GeV)bHXEi zMvJEAYe)Vvr-Z%dI+s}DVv5{wCBrAFifUZ=6&YlzI)b2Es8P0G1tD+|O-1#$hs?Cq zv9?%CSw%7aHxX{Sul#=X$GG9`w7t8Rwj|qH&Ec>NgYJ2wV~Z3UN7|nU1ns!d zIH_Vsq>oc`>kyaWqdK(~8@u70uMlJ=WrN8lkdsOHqo}X8HF`m0LLq2Xyt;`)JWapx z#J~RlCsN^=dY6V0r4iJkJDkpDlW6L54M$)84!`xpz5f919Zio>{$K^sQ-T`*0QkC> f2PEX?QNCtxv;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 79a00e291..0210a8ddc 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 000000000..d46f09e4e --- /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 000000000..dde6f0935 --- /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 000000000..e5d064e80 --- /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 000000000..cbfc286f8 --- /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 000000000..bbcf812ce --- /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 000000000..2c0f65615 --- /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 000000000..f7a0588f4 --- /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 000000000..f60c39f1c --- /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 68b15b3ce..304aa0ebc 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 000000000..662dfe655 --- /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 + + + +