From 7434a38cf18ddb5eb19f2efc9de9567c15324e53 Mon Sep 17 00:00:00 2001 From: Konstantin Polihronov Date: Wed, 14 Feb 2024 17:22:23 +0200 Subject: [PATCH] [solax] Cloud connection support (#16124) * Initial rearrangement of classes and cloud response in test Signed-off-by: Konstantin Polihronov --- bundles/org.openhab.binding.solax/README.md | 231 +++++++++++---- .../solax/internal/SolaxBindingConstants.java | 18 +- .../solax/internal/SolaxConfiguration.java | 1 + .../solax/internal/SolaxHandlerFactory.java | 23 +- .../connectivity/CloudHttpConnector.java | 59 ++++ .../connectivity/rawdata/RawDataBean.java | 2 + .../rawdata/cloud/CloudRawDataBean.java | 268 ++++++++++++++++++ .../connectivity/rawdata/cloud/Result.java | 248 ++++++++++++++++ .../{ => local}/LocalConnectRawDataBean.java | 5 +- .../exceptions/SolaxUpdateException.java | 38 +++ .../handlers/AbstractSolaxHandler.java | 137 +++++++++ .../internal/handlers/SolaxCloudHandler.java | 117 ++++++++ .../SolaxLocalAccessHandler.java | 118 ++------ .../solax/internal/model/InverterType.java | 8 +- .../model/cloud/CloudInverterData.java | 93 ++++++ .../CommonLocalInverterData.java} | 13 +- .../LocalInverterData.java} | 23 +- .../X1HybridG4InverterData.java | 16 +- .../X3HybridG4InverterData.java | 16 +- .../X3MicOrProG2InverterData.java | 6 +- .../{ => local}/parsers/RawDataParser.java | 8 +- .../parsers/X1HybridG4DataParser.java | 10 +- .../parsers/X3HybridG4DataParser.java | 10 +- .../parsers/X3MicOrProG2DataParser.java | 10 +- .../resources/OH-INF/i18n/solax.properties | 58 ++++ .../resources/OH-INF/thing/channel_types.xml | 23 ++ .../OH-INF/thing/cloudConnectInverter.xml | 111 ++++++++ .../solax/internal/cloud/TestCloudParser.java | 118 ++++++++ .../{ => local}/TestX1HybridG4Parser.java | 10 +- .../{ => local}/TestX3HybridG4Parser.java | 10 +- .../{ => local}/TestX3MicOrProG2Parser.java | 10 +- 31 files changed, 1590 insertions(+), 228 deletions(-) create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/CloudHttpConnector.java create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/cloud/CloudRawDataBean.java create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/cloud/Result.java rename bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/{ => local}/LocalConnectRawDataBean.java (94%) create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/exceptions/SolaxUpdateException.java create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/handlers/AbstractSolaxHandler.java create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/handlers/SolaxCloudHandler.java rename bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/{ => handlers}/SolaxLocalAccessHandler.java (74%) create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/cloud/CloudInverterData.java rename bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/{impl/CommonInverterData.java => local/CommonLocalInverterData.java} (80%) rename bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/{InverterData.java => local/LocalInverterData.java} (95%) rename bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/{impl => local}/X1HybridG4InverterData.java (91%) rename bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/{impl => local}/X3HybridG4InverterData.java (95%) rename bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/{impl => local}/X3MicOrProG2InverterData.java (93%) rename bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/{ => local}/parsers/RawDataParser.java (72%) rename bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/{ => local}/parsers/X1HybridG4DataParser.java (82%) rename bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/{ => local}/parsers/X3HybridG4DataParser.java (86%) rename bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/{ => local}/parsers/X3MicOrProG2DataParser.java (84%) create mode 100644 bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/cloudConnectInverter.xml create mode 100644 bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/cloud/TestCloudParser.java rename bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/{ => local}/TestX1HybridG4Parser.java (92%) rename bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/{ => local}/TestX3HybridG4Parser.java (93%) rename bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/{ => local}/TestX3MicOrProG2Parser.java (90%) diff --git a/bundles/org.openhab.binding.solax/README.md b/bundles/org.openhab.binding.solax/README.md index a9427a847e8..08b6cd89737 100644 --- a/bundles/org.openhab.binding.solax/README.md +++ b/bundles/org.openhab.binding.solax/README.md @@ -14,7 +14,8 @@ In case the parsed information that comes with the binding out of the box differ | Thing | Thing Type | Description | |------------------------|------------|-------------------------------------------------------------------------------------| -| local-connect-inverter | Thing | This is model representation of inverter with all the data available as a channels | +| local-connect-inverter | Thing | An inverter representation with all the data available as a channels (directly retrieved from the wi-fi module | +| cloud-connect-inverter | Thing | An inverter representation with all the data available as a channels (retrieved from the Solax cloud API) | Note: Channels may vary depending on the inverter type and the availability of information for parsing the raw data. If you're missing a channel this means that it's not supported for your inverter type. @@ -66,13 +67,13 @@ If you're missing a channel this means that it's not supported for your inverter ### Battery channels -| Channel | Type | Description | -|---------------------------|----------------------------|------------------------------------------------------------------------------------------------| -| battery-power | Number:Power | The power to / from battery (negative means power is pulled from battery and vice-versa) [W] | -| battery-current | Number:ElectricCurrent | The current to / from battery (negative means power is pulled from battery and vice-versa) [A] | -| battery-voltage | Number:ElectricPotential | The voltage of the battery [V] | -| battery-temperature | Number:Temperature | The temperature of the battery [C/F] | -| battery-state-of-charge | Number | The state of charge of the battery [%] | +| Channel | Type | Description | +|---------------------------|----------------------------|----------------------------------------------------------------------------------------------------| +| battery-power | Number:Power | The power to / from battery (negative means power is pulled from the battery and vice-versa) [W] | +| battery-current | Number:ElectricCurrent | The current to / from battery (negative means power is pulled from the battery and vice-versa) [A] | +| battery-voltage | Number:ElectricPotential | The voltage of the battery [V] | +| battery-temperature | Number:Temperature | The temperature of the battery [C/F] | +| battery-level | Number | The state of charge of the battery [%] | ### Grid related channels @@ -111,6 +112,40 @@ If you're missing a channel this means that it's not supported for your inverter | serialNumber | The serial number of the Wi-Fi module | | inverterType | Inverter Type (for example X1_HYBRID_G4) | + +### Cloud Connect Inverter Configuration + +| Parameter | Description | +|-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------| +| refreshInterval | Defines the refresh interval when the binding polls from from the Solax cloud (in seconds). Optional parameter(min=15, max=600). Default is 30 seconds. Be advised that the cloud API is limited to max 10 calls per minute and 10000 calls per day. | +| password | The registration number, shown in the Solax Cloud web portal. Mandatory parameter. | +| token | Token for accessing the Solax Cloud API. Can be obtained via Service -> API on the Solax cloud web portal. Mandatory parameter. | + +### Channels + +| Channel | Type | Description | +|---------------------------------|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------| +| inverter-output-power | Number:Power | The output power of the inverter [W] | +| pv1-power | Number:Power | The output power PV1 string [W] | +| pv2-power | Number:Power | The output power PV2 string [W] | +| pv3-power | Number:Power | The output power PV3 string [W] | +| pv4-power | Number:Power | The output power PV4 string [W] | +| pv-total-power | Number:Power | The output power of all the photovoltaic strings [W] | +| battery-power | Number:Power | The power to / from battery (negative means power is pulled from the battery and vice-versa) [W] | +| battery-level | Number | The state of charge of the battery [%] | +| feed-in-power | Number:Power | The power to / from grid (negative means power is pulled from the grid and vice-versa) [W] | +| total-feed-in-energy | Number:Energy | Total energy consumed from the electricity provider [kWh] | +| total-consumption | Number:Energy | Total energy consumed for the building [kWh] | +| today-energy | Number:Energy | Energy output from the inverter for the day [kWh] | +| total-energy | Number:Energy | Total energy output from the inverter [kWh] | +| raw-data | String | The raw data retrieved from inverter in JSON format. (Usable for channels not implemented. Can be consumed with the JSONpath transformation | +| inverter-status | String | The status of the inverter. (For the various status types, refer to the API documentation) | +| last-update-time | DateTime | Last time when a call has been made to the inverter | +| inverter-meter2-power | Number:Power | Inverter power on meter2 [W] | +| inverter-eps-power-r | Number:Power | Inverter AC EPS power R [W] | +| inverter-eps-power-s | Number:Power | Inverter AC EPS power S [W] | +| inverter-eps-power-t | Number:Power | Inverter AC EPS power T [W] | + ## Full Example Here are some file based examples. @@ -120,6 +155,7 @@ Here are some file based examples. ```java // The local connect inverter thing Thing solax:local-connect-inverter:localInverter [ refreshInterval=10, password="", hostname="" ] +Thing solax:cloud-connect-inverter:cloudInverter [ refresh=30, password="", token="" ] ``` ### Item Configuration @@ -128,17 +164,38 @@ Thing solax:local-connect-inverter:localInverter [ refreshInterval=10, password Group gSolaxInverter "Solax Inverter" (boilerRoom) Group solarPanels "Solar panels" (gSolaxInverter) -Number solaxPowerWest "West [%.0f W]" (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv1-power" } -Number solaxPowerEast "East [%.0f W]" (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv2-power" } +// Direct connect +Number solaxPowerWest "West Power [%d W]" (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv1-power" } +Number solaxPowerEast "East Power [%d W]" (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv2-power" } +Number solaxGenerationTotal "Total generаtion now [%.0f W]" (gsolax_inverter,EveryChangePersist,solarPanels) { channel="solax:local-connect-inverter:localInverter:pv-total-power" } +Number solaxVoltageWest "West Voltage [%.1f V]" (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv1-voltage" } +Number solaxVoltageEast "East Voltage [%.1f V]" (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv2-voltage" } +Number solaxCurrentWest "West Current [%.1f A]" (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv1-current" } +Number solaxCurrentEast "East Current [%.1f A]" (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv2-current" } Number solaxBatteryPower "Battery power [%.0f W]" (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-power" } -Number solaxBatterySoc "Battery SoC [%.0f %%]" (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-state-of-charge" } +Number solaxBatterySoc "Battery SoC [%.0f %%]" (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-level" } +Number solaxBatteryTemperature "Battery temperature [%d °C]" (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-temperature" } +Number solaxBatteryCurrent "Battery current [%.1f A]" (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-current" } +Number solaxBatteryVoltage "Battery voltage [%.1f V]" (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-voltage" } Number solaxFeedInPower "Feed-in power (CEZ) [%.0f W]" (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:feed-in-power" } +Number solaxCalculatedTotalFeedInPower "Calculated feed-in total power (CEZ) [%.0f KWh]" (gsolax_inverter,EveryChangePersist) +Number solaxCalculatedTotalFeedInPowerThisMonth "Calculated feed-in total power this month (CEZ) [%.0f KWh]" (gsolax_inverter,EveryChangePersist) Number solaxAcPower "Invertor output power [%.0f W]" (gsolax_inverter,EveryChangePersist){ channel="solax:local-connect-inverter:localInverter:inverter-output-power" } +Number solaxFrequency "Invertor frequency [%.2f Hz]" (gsolax_inverter,EveryChangePersist){ channel="solax:local-connect-inverter:localInverter:inverter-frequency" } +Number solaxVoltage "Invertor voltage [%.1f V]" (gsolax_inverter,EveryChangePersist){ channel="solax:local-connect-inverter:localInverter:inverter-voltage" } -String solaxInverterType "Inverter Type [%s]" (gsolax_inverter) { channel="solax:local-connect-inverter:localInverter:inverter-type"} -String solaxUploadTime "Last update time [%s]" (gsolax_inverter) { channel="solax:local-connect-inverter:localInverter:last-update-time" } -String solaxRawData "Raw data [%s]" (gsolax_inverter) { channel="solax:local-connect-inverter:localInverter:raw-data" } +String solaxLocalUploadTime "Local update time [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1tS]" (gsolax_inverter) { channel="solax:local-connect-inverter:localInverter:last-update-time" } +String solaxCloudUploadTime "Cloud update time [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1tS]" (gsolax_inverter) { channel="solax:cloud-connect-inverter:cloudInverter:last-update-time" } + +String solaxLocalRawData "Local raw data [%s]" (gsolax_inverter) { channel="solax:local-connect-inverter:localInverter:raw-data" } +String solaxCloudRawData "Cloud raw data [%s]" (gsolax_inverter) { channel="solax:cloud-connect-inverter:cloudInverter:raw-data" } + +// Cloud +Number solaxYieldToday "Yield today [%.0f kWh]" (gsolax_inverter){ channel="solax:cloud-connect-inverter:cloudInverter:today-energy" } +Number solaxYieldTotal "Yield total [%.0f kWh]" (gsolax_inverter) { channel="solax:cloud-connect-inverter:cloudInverter:total-energy" } +Number solaxFeedInEnergy "Total Feed-in (CEZ) Power [%.0f kWh]" (gsolax_inverter,EveryChangePersist) { channel="solax:cloud-connect-inverter:cloudInverter:total-feed-in-energy" } +String solaxInverterStatus "Inverter Status [%s]" (gsolax_inverter,EveryChangePersist) { channel="solax:cloud-connect-inverter:cloudInverter:inverter-status" } ``` ### Sitemap Configuration @@ -149,74 +206,146 @@ Frame label="Solar power strings" { Text item=solaxPowerEast icon="energy" valuecolor=[<=30="gray",<=300="red", <1500="orange", >=1500="green"] Text item=solaxPowerWest icon="energy" valuecolor=[<=30="gray",<=300="red", <1500="orange", >=1500="green"] Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"] - Chart item=solarPanels period=h refresh=600 visibility=[Chart_Period==0] - Chart item=solarPanels period=D refresh=3600 visibility=[Chart_Period==1] - Chart item=solarPanels period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] - Chart item=solarPanels period=M refresh=3600 visibility=[Chart_Period==3] - Chart item=solarPanels period=Y refresh=3600 visibility=[Chart_Period==4] + Chart item=solarPanels period=h refresh=600 visibility=[Chart_Period==0] + Chart item=solarPanels period=D refresh=3600 visibility=[Chart_Period==1] + Chart item=solarPanels period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] + Chart item=solarPanels period=M refresh=3600 visibility=[Chart_Period==3] + Chart item=solarPanels period=Y refresh=3600 visibility=[Chart_Period==4] } Text item=solaxPowerWest valuecolor=[<=30="gray",<=300="red", <1500="orange", >=1500="green"] { Text item=solaxPowerEast icon="energy" valuecolor=[<=30="gray",<=300="red", <1500="orange", >=1500="green"] Text item=solaxPowerWest icon="energy" valuecolor=[<=30="gray",<=300="red", <1500="orange", >=1500="green"] Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"] - Chart item=solarPanels period=h refresh=600 visibility=[Chart_Period==0] - Chart item=solarPanels period=D refresh=3600 visibility=[Chart_Period==1] - Chart item=solarPanels period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] - Chart item=solarPanels period=M refresh=3600 visibility=[Chart_Period==3] - Chart item=solarPanels period=Y refresh=3600 visibility=[Chart_Period==4] + Chart item=solarPanels period=h refresh=600 visibility=[Chart_Period==0] + Chart item=solarPanels period=D refresh=3600 visibility=[Chart_Period==1] + Chart item=solarPanels period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] + Chart item=solarPanels period=M refresh=3600 visibility=[Chart_Period==3] + Chart item=solarPanels period=Y refresh=3600 visibility=[Chart_Period==4] } + Text item=solaxVoltageEast valuecolor=[==0="gray",>0="green", >480="orange", >=500="red"] + Text item=solaxVoltageWest valuecolor=[==0="gray",>0="green", >480="orange", >=500="red"] + Text item=solaxCurrentEast valuecolor=[==0="gray",>0="green", >5="orange", >=10="red"] + Text item=solaxCurrentWest valuecolor=[==0="gray",>0="green", >5="orange", >=10="red"] Text item=solaxGenerationTotal valuecolor=[<=100="gray",<=500="red", <2000="orange", >=2000="green"] { Text item=solaxPowerEast icon="energy" valuecolor=[<=30="gray",<=300="red", <1500="orange", >=1500="green"] Text item=solaxPowerWest icon="energy" valuecolor=[<=30="gray",<=300="red", <1500="orange", >=1500="green"] Text item=solaxGenerationTotal icon="energy" valuecolor=[<=30="gray",<=300="red", <1500="orange", >=1500="green"] Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"] - Chart item=solarPanels period=h refresh=600 visibility=[Chart_Period==0] - Chart item=solarPanels period=D refresh=3600 visibility=[Chart_Period==1] - Chart item=solarPanels period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] - Chart item=solarPanels period=M refresh=3600 visibility=[Chart_Period==3] - Chart item=solarPanels period=Y refresh=3600 visibility=[Chart_Period==4] + Chart item=solarPanels period=h refresh=600 visibility=[Chart_Period==0] + Chart item=solarPanels period=D refresh=3600 visibility=[Chart_Period==1] + Chart item=solarPanels period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] + Chart item=solarPanels period=M refresh=3600 visibility=[Chart_Period==3] + Chart item=solarPanels period=Y refresh=3600 visibility=[Chart_Period==4] } } Frame label="Consumption" { Text item=solaxAcPower valuecolor=[<=30="gray", <800="green", <1500="orange", >=1500="red"] { Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"] Text item=solaxAcPower icon="energy" valuecolor=[<=30="gray", <800="green", <1500="orange", >=1500="red"] - Chart item=solaxAcPower period=h refresh=600 visibility=[Chart_Period==0] - Chart item=solaxAcPower period=D refresh=3600 visibility=[Chart_Period==1] - Chart item=solaxAcPower period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] - Chart item=solaxAcPower period=M refresh=3600 visibility=[Chart_Period==3] - Chart item=solaxAcPower period=Y refresh=3600 visibility=[Chart_Period==4] + Chart item=solaxAcPower period=h refresh=600 visibility=[Chart_Period==0] + Chart item=solaxAcPower period=D refresh=3600 visibility=[Chart_Period==1] + Chart item=solaxAcPower period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] + Chart item=solaxAcPower period=M refresh=3600 visibility=[Chart_Period==3] + Chart item=solaxAcPower period=Y refresh=3600 visibility=[Chart_Period==4] } Text item=solaxFeedInPower valuecolor=[<=30="gray", <800="green", <1500="orange", >=1500="red"] { Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"] Text item=solaxFeedInPower icon="energy" valuecolor=[<=30="gray", <800="green", <1500="orange", >=1500="red"] - Chart item=solaxFeedInPower period=h refresh=600 visibility=[Chart_Period==0] - Chart item=solaxFeedInPower period=D refresh=3600 visibility=[Chart_Period==1] - Chart item=solaxFeedInPower period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] - Chart item=solaxFeedInPower period=M refresh=3600 visibility=[Chart_Period==3] - Chart item=solaxFeedInPower period=Y refresh=3600 visibility=[Chart_Period==4] + Chart item=solaxFeedInPower period=h refresh=600 visibility=[Chart_Period==0] + Chart item=solaxFeedInPower period=D refresh=3600 visibility=[Chart_Period==1] + Chart item=solaxFeedInPower period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] + Chart item=solaxFeedInPower period=M refresh=3600 visibility=[Chart_Period==3] + Chart item=solaxFeedInPower period=Y refresh=3600 visibility=[Chart_Period==4] + } + Text item=solaxFrequency valuecolor=[<=30="gray", <800="green", <1500="orange", >=1500="red"] { + Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"] + Text item=solaxFrequency icon="energy" valuecolor=[<=30="gray", <800="green", <1500="orange", >=1500="red"] + Chart item=solaxFrequency period=h refresh=600 visibility=[Chart_Period==0] + Chart item=solaxFrequency period=D refresh=3600 visibility=[Chart_Period==1] + Chart item=solaxFrequency period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] + Chart item=solaxFrequency period=M refresh=3600 visibility=[Chart_Period==3] + Chart item=solaxFrequency period=Y refresh=3600 visibility=[Chart_Period==4] + } + Text item=solaxVoltage valuecolor=[<=30="gray", <800="green", <1500="orange", >=1500="red"] { + Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"] + Text item=solaxVoltage icon="energy" valuecolor=[<=30="gray", <800="green", <1500="orange", >=1500="red"] + Chart item=solaxVoltage period=h refresh=600 visibility=[Chart_Period==0] + Chart item=solaxVoltage period=D refresh=3600 visibility=[Chart_Period==1] + Chart item=solaxVoltage period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] + Chart item=solaxVoltage period=M refresh=3600 visibility=[Chart_Period==3] + Chart item=solaxVoltage period=Y refresh=3600 visibility=[Chart_Period==4] } } Frame label="Battery" { Text item=solaxBatteryPower valuecolor=[<=-500="red", <0="orange", ==0="gray", >0="green"] { Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"] Text item=solaxBatteryPower icon="energy" valuecolor=[<-800="red", <0="orange", ==0="gray", >=0="green"] - Chart item=solaxBatteryPower period=h refresh=600 visibility=[Chart_Period==0] - Chart item=solaxBatteryPower period=D refresh=3600 visibility=[Chart_Period==1] - Chart item=solaxBatteryPower period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] - Chart item=solaxBatteryPower period=M refresh=3600 visibility=[Chart_Period==3] - Chart item=solaxBatteryPower period=Y refresh=3600 visibility=[Chart_Period==4] + Chart item=solaxBatteryPower period=h refresh=600 visibility=[Chart_Period==0] + Chart item=solaxBatteryPower period=D refresh=3600 visibility=[Chart_Period==1] + Chart item=solaxBatteryPower period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] + Chart item=solaxBatteryPower period=M refresh=3600 visibility=[Chart_Period==3] + Chart item=solaxBatteryPower period=Y refresh=3600 visibility=[Chart_Period==4] + } + Text item=solaxBatteryCurrent valuecolor=[<=-5="red", <0="orange", ==0="gray", >0="green"] { + Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"] + Text item=solaxBatteryCurrent icon="energy" valuecolor=[<-800="red", <0="orange", ==0="gray", >=0="green"] + Chart item=solaxBatteryCurrent period=h refresh=600 visibility=[Chart_Period==0] + Chart item=solaxBatteryCurrent period=D refresh=3600 visibility=[Chart_Period==1] + Chart item=solaxBatteryCurrent period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] + Chart item=solaxBatteryCurrent period=M refresh=3600 visibility=[Chart_Period==3] + Chart item=solaxBatteryCurrent period=Y refresh=3600 visibility=[Chart_Period==4] + } + Text item=solaxBatteryVoltage valuecolor=[<=-500="red", <0="orange", ==0="gray", >0="green"] { + Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"] + Text item=solaxBatteryVoltage icon="energy" valuecolor=[<-800="red", <0="orange", ==0="gray", >=0="green"] + Chart item=solaxBatteryVoltage period=h refresh=600 visibility=[Chart_Period==0] + Chart item=solaxBatteryVoltage period=D refresh=3600 visibility=[Chart_Period==1] + Chart item=solaxBatteryVoltage period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] + Chart item=solaxBatteryVoltage period=M refresh=3600 visibility=[Chart_Period==3] + Chart item=solaxBatteryVoltage period=Y refresh=3600 visibility=[Chart_Period==4] } Text item=solaxBatterySoc valuecolor=[<=30="red", <50="orange", >=50="green"] { Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"] Text item=solaxBatterySoc valuecolor=[<=30="red", <50="orange", >=50="green"] - Chart item=solaxBatterySoc period=h refresh=600 visibility=[Chart_Period==0] - Chart item=solaxBatterySoc period=D refresh=3600 visibility=[Chart_Period==1] - Chart item=solaxBatterySoc period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] - Chart item=solaxBatterySoc period=M refresh=3600 visibility=[Chart_Period==3] - Chart item=solaxBatterySoc period=Y refresh=3600 visibility=[Chart_Period==4] + Chart item=solaxBatterySoc period=h refresh=600 visibility=[Chart_Period==0] + Chart item=solaxBatterySoc period=D refresh=3600 visibility=[Chart_Period==1] + Chart item=solaxBatterySoc period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] + Chart item=solaxBatterySoc period=M refresh=3600 visibility=[Chart_Period==3] + Chart item=solaxBatterySoc period=Y refresh=3600 visibility=[Chart_Period==4] + } + Text item=solaxBatteryTemperature valuecolor=[<=35="green", <45="orange", >=45="red"] { + Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"] + Text item=solaxBatteryTemperature valuecolor=[<=25="green", <40="orange", >=40="red"] + Chart item=solaxBatteryTemperature period=h refresh=600 visibility=[Chart_Period==0] + Chart item=solaxBatteryTemperature period=D refresh=3600 visibility=[Chart_Period==1] + Chart item=solaxBatteryTemperature period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] + Chart item=solaxBatteryTemperature period=M refresh=3600 visibility=[Chart_Period==3] + Chart item=solaxBatteryTemperature period=Y refresh=3600 visibility=[Chart_Period==4] } } +Frame label="Statistics" { + Text item=solaxYieldToday + Text item=solaxYieldTotal + Text item=solaxConsumeEnergy + Text item=solaxFeedInEnergy + Text item=solaxCalculatedTotalFeedInPower + Text item=solaxCalculatedTotalFeedInPowerThisMonth valuecolor=[<200="green", >=200="orange", >=300="red"] { + Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"] + Text item=solaxCalculatedTotalFeedInPowerThisMonth valuecolor=[<=30="red", <50="orange", >=50="green"] + Chart item=solaxCalculatedTotalFeedInPowerThisMonth period=h refresh=600 visibility=[Chart_Period==0] + Chart item=solaxCalculatedTotalFeedInPowerThisMonth period=D refresh=3600 visibility=[Chart_Period==1] + Chart item=solaxCalculatedTotalFeedInPowerThisMonth period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized] + Chart item=solaxCalculatedTotalFeedInPowerThisMonth period=M refresh=3600 visibility=[Chart_Period==3] + Chart item=solaxCalculatedTotalFeedInPowerThisMonth period=Y refresh=3600 visibility=[Chart_Period==4] + } +} +Frame label="General" { + Text item=solaxInverterStatus + Text item=solaxLocalUploadTime + Text item=solaxCloudUploadTime +} +Frame label="Raw data" { + Text item=solaxLocalRawData + Text item=solaxCloudRawData +} ``` - - diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java index 39d0b3ba6eb..4d2f18fb49c 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java @@ -26,14 +26,18 @@ import org.openhab.core.thing.ThingTypeUID; @NonNullByDefault public class SolaxBindingConstants { - protected static final String BINDING_ID = "solax"; + public static final String BINDING_ID = "solax"; private static final String THING_LOCAL_CONNECT_INVERTER_ID = "local-connect-inverter"; + private static final String THING_CLOUD_CONNECT_INVERTER_ID = "cloud-connect-inverter"; // List of all Thing Type UIDs public static final ThingTypeUID THING_TYPE_LOCAL_CONNECT_INVERTER = new ThingTypeUID(BINDING_ID, THING_LOCAL_CONNECT_INVERTER_ID); + public static final ThingTypeUID THING_TYPE_CLOUD_CONNECT_INVERTER = new ThingTypeUID(BINDING_ID, + THING_CLOUD_CONNECT_INVERTER_ID); - public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_LOCAL_CONNECT_INVERTER); + public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_LOCAL_CONNECT_INVERTER, + THING_TYPE_CLOUD_CONNECT_INVERTER); // List of properties public static final String PROPERTY_INVERTER_TYPE = "inverterType"; @@ -104,6 +108,14 @@ public class SolaxBindingConstants { public static final String CHANNEL_TODAY_FEED_IN_ENERGY = "today-feed-in-energy"; public static final String CHANNEL_TODAY_CONSUMPTION = "today-consumption"; + // Cloud specific channels + public static final String CHANNEL_INVERTER_PV3_POWER = "pv3-power"; + public static final String CHANNEL_INVERTER_PV4_POWER = "pv4-power"; + public static final String CHANNEL_INVERTER_OUTPUT_POWER_METER2 = "inverter-meter2-power"; + public static final String CHANNEL_INVERTER_EPS_POWER_R = "inverter-eps-power-r"; + public static final String CHANNEL_INVERTER_EPS_POWER_S = "inverter-eps-power-s"; + public static final String CHANNEL_INVERTER_EPS_POWER_T = "inverter-eps-power-t"; + // I18N Keys - protected static final String I18N_KEY_OFFLINE_COMMUNICATION_ERROR_JSON_CANNOT_BE_RETRIEVED = "@text/offline.communication-error.json-cannot-be-retrieved"; + public static final String I18N_KEY_OFFLINE_COMMUNICATION_ERROR_JSON_CANNOT_BE_RETRIEVED = "@text/offline.communication-error.json-cannot-be-retrieved"; } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxConfiguration.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxConfiguration.java index da3e94732f3..343b8009c02 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxConfiguration.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxConfiguration.java @@ -25,4 +25,5 @@ public class SolaxConfiguration { public String hostname = ""; public String password = ""; public int refreshInterval = 10; + public String token = ""; } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxHandlerFactory.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxHandlerFactory.java index 1e43245fa9a..c0c5a2b1035 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxHandlerFactory.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxHandlerFactory.java @@ -16,12 +16,19 @@ import static org.openhab.binding.solax.internal.SolaxBindingConstants.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.solax.internal.handlers.SolaxCloudHandler; +import org.openhab.binding.solax.internal.handlers.SolaxLocalAccessHandler; +import org.openhab.core.i18n.LocaleProvider; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.binding.BaseThingHandlerFactory; import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; /** * The {@link SolaxHandlerFactory} is responsible for creating things and thing @@ -33,6 +40,18 @@ import org.osgi.service.component.annotations.Component; @Component(configurationPid = "binding.solax", service = ThingHandlerFactory.class) public class SolaxHandlerFactory extends BaseThingHandlerFactory { + private TranslationProvider i18nProvider; + private TimeZoneProvider timeZoneProvider; + private LocaleProvider localeProvider; + + @Activate + public SolaxHandlerFactory(final @Reference TranslationProvider i18nProvider, + final @Reference TimeZoneProvider timeZoneProvider, final @Reference LocaleProvider localeProvider) { + this.i18nProvider = i18nProvider; + this.timeZoneProvider = timeZoneProvider; + this.localeProvider = localeProvider; + } + @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); @@ -42,7 +61,9 @@ public class SolaxHandlerFactory extends BaseThingHandlerFactory { protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (THING_TYPE_LOCAL_CONNECT_INVERTER.equals(thingTypeUID)) { - return new SolaxLocalAccessHandler(thing); + return new SolaxLocalAccessHandler(thing, i18nProvider, timeZoneProvider); + } else if (THING_TYPE_CLOUD_CONNECT_INVERTER.equals(thingTypeUID)) { + return new SolaxCloudHandler(thing, i18nProvider, timeZoneProvider, localeProvider); } return null; } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/CloudHttpConnector.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/CloudHttpConnector.java new file mode 100644 index 00000000000..c5b1cce463e --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/CloudHttpConnector.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.solax.internal.connectivity; + +import java.io.IOException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.http.HttpMethod; +import org.openhab.core.io.net.http.HttpUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link CloudHttpConnector} class uses HttpUtil to retrieve the raw JSON data from Inverter's Wi-Fi module. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class CloudHttpConnector implements SolaxConnector { + + private static final int HTTP_REQUEST_TIME_OUT = 5000; + + private static final String CONTENT_TYPE = "application/json; charset=utf-8"; + + private final Logger logger = LoggerFactory.getLogger(CloudHttpConnector.class); + + private static final String URI = """ + https://www.solaxcloud.com/proxyApp/proxy/api/getRealtimeInfo.do?tokenId={tokenId}&sn={serialNumber} + """; + + private String uri; + + public CloudHttpConnector(String tokenId, String serialNumber) { + this(URI, tokenId, serialNumber); + } + + public CloudHttpConnector(String uri, String tokenId, String serialNumber) { + this.uri = uri.replace("{tokenId}", tokenId).replace("{serialNumber}", serialNumber).trim(); + } + + @Override + public @Nullable String retrieveData() throws IOException { + logger.debug("About to retrieve data from Uri: {}", uri); + String result = HttpUtil.executeUrl(HttpMethod.GET.name(), uri, null, CONTENT_TYPE, HTTP_REQUEST_TIME_OUT); + logger.trace("Retrieved content = {}", result); + return result; + } +} diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/RawDataBean.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/RawDataBean.java index 094e79e1c74..7aa2530e56c 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/RawDataBean.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/RawDataBean.java @@ -25,4 +25,6 @@ import org.eclipse.jdt.annotation.Nullable; public interface RawDataBean { @Nullable String getRawData(); + + public void setRawData(String rawData); } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/cloud/CloudRawDataBean.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/cloud/CloudRawDataBean.java new file mode 100644 index 00000000000..9a772aa1533 --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/cloud/CloudRawDataBean.java @@ -0,0 +1,268 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.solax.internal.connectivity.rawdata.cloud; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.function.Supplier; + +import org.apache.directory.api.util.Strings; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.solax.internal.model.InverterType; +import org.openhab.binding.solax.internal.model.cloud.CloudInverterData; +import org.openhab.binding.solax.internal.util.GsonSupplier; + +import com.google.gson.Gson; + +/** + * The {@link CloudRawDataBean} is used as a storage for mapping the raw data collected from the cloud to this object. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class CloudRawDataBean implements CloudInverterData { + + public static final String QUERY_SUCCESS = "Query success!"; + public static final String ERROR = "error"; + + private static final DateTimeFormatter CUSTOM_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + private boolean success; + private @NonNullByDefault({}) String exception; + private @NonNullByDefault({}) Result result; + private int code; + private @NonNullByDefault({}) String rawData; + + // For JSON serialization / deserialization purposes + public CloudRawDataBean() { + } + + public CloudRawDataBean(boolean isSuccess) { + this.success = isSuccess; + this.exception = isSuccess ? QUERY_SUCCESS : ERROR; + } + + @Override + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public @Nullable String getException() { + return exception; + } + + public void setException(String exception) { + this.exception = exception; + } + + public @Nullable Result getResult() { + return result; + } + + public void setResult(Result result) { + this.result = result; + } + + @Override + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + // Inner Implementation / DTO of the CloudInverterData starts here + + @Override + public String getInverterSerialNumber() { + String inverterSN = result.getInverterSN(); + return inverterSN != null ? inverterSN : Strings.EMPTY_STRING; + } + + @Override + public String getWifiSerialNumber() { + String serialNumber = result.getSn(); + return serialNumber != null ? serialNumber : Strings.EMPTY_STRING; + } + + @Override + public String getOverallResult() { + // Why the ternary operator does not get taken into account by the JDT and the maven build? + String result = exception; + if (result == null) { + result = "Retrieved message from the cloud API is null. Something wrong happened."; + } + return result; + } + + @Override + public double getInverterOutputPower() { + return notNullResult(() -> result.getAcPower()); + } + + @Override + public double getYieldToday() { + return notNullResult(() -> result.getYieldToday()); + } + + @Override + public double getYieldTotal() { + return notNullResult(() -> result.getYieldTotal()); + } + + @Override + public double getFeedInPower() { + return notNullResult(() -> result.getFeedInPower()); + } + + @Override + public double getFeedInEnergy() { + return notNullResult(() -> result.getFeedInEnergy()); + } + + @Override + public double getConsumeEnergy() { + return notNullResult(() -> result.getConsumeEnergy()); + } + + @Override + public double getFeedInPowerM2() { + return notNullResult(() -> result.getFeedInPowerM2()); + } + + @Override + public double getBatteryLevel() { + return notNullResult(() -> result.getSoc()); + } + + @Override + public double getEPSPowerR() { + return notNullResult(() -> result.getPeps1()); + } + + @Override + public double getEPSPowerS() { + return notNullResult(() -> result.getPeps2()); + } + + @Override + public double getEPSPowerT() { + return notNullResult(() -> result.getPeps3()); + } + + @Override + public InverterType getInverterType() { + return InverterType.fromIndex(result.getInverterType()); + } + + @Override + public ZonedDateTime getUploadTime(ZoneId zoneId) { + String uploadTime = result.getUploadTime(); + if (uploadTime != null) { + return ZonedDateTime.of(LocalDateTime.parse(uploadTime, CUSTOM_DATE_FORMATTER), zoneId); + } + + return ZonedDateTime.of(LocalDateTime.MIN, zoneId); + } + + @Override + public double getBatteryPower() { + return notNullResult(() -> result.getBatPower()); + } + + @Override + public double getPowerPv1() { + return notNullResult(() -> result.getPowerDc1()); + } + + @Override + public double getPowerPv2() { + return notNullResult(() -> result.getPowerDc2()); + } + + @Override + public double getPowerPv3() { + return notNullResult(() -> result.getPowerDc3()); + } + + @Override + public double getPowerPv4() { + return notNullResult(() -> result.getPowerDc4()); + } + + @Override + public short getInverterWorkModeCode() { + return (short) result.getInverterStatus(); + } + + @Override + public int getBatteryStatus() { + return result.getBatStatus(); + } + + @Override + public double getPVTotalPower() { + return getPowerPv1() + getPowerPv2() + getPowerPv3() + getPowerPv4(); + } + + public static CloudRawDataBean fromJson(String json) { + if (json.isEmpty()) { + return new CloudRawDataBean(false); + } + + Gson gson = GsonSupplier.getInstance(); + CloudRawDataBean deserializedObject = gson.fromJson(json, CloudRawDataBean.class); + if (deserializedObject == null) { + return new CloudRawDataBean(false); + } + deserializedObject.setRawData(json); + return deserializedObject; + } + + public void setRawData(String json) { + this.rawData = json; + } + + @Override + public @Nullable String getRawData() { + return rawData; + } + + public boolean isError() { + String exception = getException(); + return ERROR.equals(exception); + } + + private double notNullResult(Supplier<@Nullable Double> supplier) { + Double returnValue = supplier.get(); + return returnValue != null ? returnValue : Integer.MIN_VALUE; + } + + public boolean isValid() { + return getResult() != null && getRawData() != null; + } + + @Override + public String toString() { + return "CloudRawDataBean [success=" + success + ", exception=" + exception + ", result=" + result + ", code=" + + code + ", rawData=" + rawData + "]"; + } +} diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/cloud/Result.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/cloud/Result.java new file mode 100644 index 00000000000..bf051b73b5e --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/cloud/Result.java @@ -0,0 +1,248 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.solax.internal.connectivity.rawdata.cloud; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link Result} is a sub-class used in the JSON response which provides the actual inverter data from the cloud. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class Result { + + private @Nullable String inverterSN; + private @Nullable String sn; + @SerializedName("acpower") + private @Nullable Double acPower; + @SerializedName("yieldtoday") + private @Nullable Double yieldToday; + @SerializedName("yieldtotal") + private @Nullable Double yieldTotal; + @SerializedName("feedinpower") + private @Nullable Double feedInPower; + @SerializedName("feedinenergy") + private @Nullable Double feedInEnergy; + @SerializedName("consumeenergy") + private @Nullable Double consumeEnergy; + @SerializedName("feedinpowerM2") + private @Nullable Double feedInPowerM2; + private @Nullable Double soc; + private @Nullable Double peps1; + private @Nullable Double peps2; + private @Nullable Double peps3; + private int inverterType; + private int inverterStatus; + private @Nullable String uploadTime; + private @Nullable Double batPower; + @SerializedName("powerdc1") + private @Nullable Double powerDc1; + @SerializedName("powerdc2") + private @Nullable Double powerDc2; + @SerializedName("powerdc3") + private @Nullable Double powerDc3; + @SerializedName("powerdc4") + private @Nullable Double powerDc4; + private int batStatus; + + public @Nullable String getInverterSN() { + return inverterSN; + } + + public void setInverterSN(String inverterSN) { + this.inverterSN = inverterSN; + } + + public @Nullable String getSn() { + return sn; + } + + public void setSn(String sn) { + this.sn = sn; + } + + public @Nullable Double getAcPower() { + return acPower; + } + + public void setAcPower(Double acPower) { + this.acPower = acPower; + } + + public @Nullable Double getYieldToday() { + return yieldToday; + } + + public void setYieldToday(Double yieldToday) { + this.yieldToday = yieldToday; + } + + public @Nullable Double getYieldTotal() { + return yieldTotal; + } + + public void setYieldTotal(Double yieldTotal) { + this.yieldTotal = yieldTotal; + } + + public @Nullable Double getFeedInPower() { + return feedInPower; + } + + public void setFeedInPower(Double feedInPower) { + this.feedInPower = feedInPower; + } + + public @Nullable Double getFeedInEnergy() { + return feedInEnergy; + } + + public void setFeedInEnergy(Double feedInEnergy) { + this.feedInEnergy = feedInEnergy; + } + + public @Nullable Double getConsumeEnergy() { + return consumeEnergy; + } + + public void setConsumeEnergy(Double consumeEnergy) { + this.consumeEnergy = consumeEnergy; + } + + public @Nullable Double getFeedInPowerM2() { + return feedInPowerM2; + } + + public void setFeedInPowerM2(Double feedInPowerM2) { + this.feedInPowerM2 = feedInPowerM2; + } + + public @Nullable Double getSoc() { + return soc; + } + + public void setSoc(Double soc) { + this.soc = soc; + } + + public @Nullable Double getPeps1() { + return peps1; + } + + public void setPeps1(Double peps1) { + this.peps1 = peps1; + } + + public @Nullable Double getPeps2() { + return peps2; + } + + public void setPeps2(Double peps2) { + this.peps2 = peps2; + } + + public @Nullable Double getPeps3() { + return peps3; + } + + public void setPeps3(Double peps3) { + this.peps3 = peps3; + } + + public int getInverterType() { + return inverterType; + } + + public void setInverterType(int inverterType) { + this.inverterType = inverterType; + } + + public int getInverterStatus() { + return inverterStatus; + } + + public void setInverterStatus(int inverterStatus) { + this.inverterStatus = inverterStatus; + } + + public @Nullable String getUploadTime() { + return uploadTime; + } + + public void setUploadTime(String uploadTime) { + this.uploadTime = uploadTime; + } + + public @Nullable Double getBatPower() { + return batPower; + } + + public void setBatPower(Double batPower) { + this.batPower = batPower; + } + + public @Nullable Double getPowerDc1() { + return powerDc1; + } + + public void setPowerDc1(Double powerDc1) { + this.powerDc1 = powerDc1; + } + + public @Nullable Double getPowerDc2() { + return powerDc2; + } + + public void setPowerDc2(Double powerDc2) { + this.powerDc2 = powerDc2; + } + + public @Nullable Double getPowerDc3() { + return powerDc3; + } + + public void setPowerDc3(Double powerDc3) { + this.powerDc3 = powerDc3; + } + + public @Nullable Double getPowerDc4() { + return powerDc4; + } + + public void setPowerDc4(Double powerDc4) { + this.powerDc4 = powerDc4; + } + + public int getBatStatus() { + return batStatus; + } + + public void setBatStatus(int batStatus) { + this.batStatus = batStatus; + } + + @Override + public String toString() { + return "Result [inverterSN=" + inverterSN + ", sn=" + sn + ", acPower=" + acPower + ", yieldToday=" + yieldToday + + ", yieldTotal=" + yieldTotal + ", feedInPower=" + feedInPower + ", feedInEnergy=" + feedInEnergy + + ", consumeEnergy=" + consumeEnergy + ", feedInPowerM2=" + feedInPowerM2 + ", soc=" + soc + ", peps1=" + + peps1 + ", peps2=" + peps2 + ", peps3=" + peps3 + ", inverterType=" + inverterType + + ", inverterStatus=" + inverterStatus + ", uploadTime=" + uploadTime + ", batPower=" + batPower + + ", powerDc1=" + powerDc1 + ", powerDc2=" + powerDc2 + ", powerDc3=" + powerDc3 + ", powerDc4=" + + powerDc4 + ", batStatus=" + batStatus + "]"; + } +} diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/LocalConnectRawDataBean.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/local/LocalConnectRawDataBean.java similarity index 94% rename from bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/LocalConnectRawDataBean.java rename to bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/local/LocalConnectRawDataBean.java index ee2293ced3a..876648493c6 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/LocalConnectRawDataBean.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/local/LocalConnectRawDataBean.java @@ -10,12 +10,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.solax.internal.connectivity.rawdata; +package org.openhab.binding.solax.internal.connectivity.rawdata.local; import java.util.Arrays; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.solax.internal.connectivity.rawdata.RawDataBean; import org.openhab.binding.solax.internal.util.GsonSupplier; import com.google.gson.Gson; @@ -23,7 +24,6 @@ import com.google.gson.annotations.SerializedName; /** * The {@link LocalConnectRawDataBean} collects the raw data and the specific implementation to return the parsed data. - * If there are differences between the inverters probably would be wise to split the parsing in seprate class(es) * * @author Konstantin Polihronov - Initial contribution */ @@ -90,6 +90,7 @@ public class LocalConnectRawDataBean implements RawDataBean { return rawData; } + @Override public void setRawData(String rawData) { this.rawData = rawData; } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/exceptions/SolaxUpdateException.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/exceptions/SolaxUpdateException.java new file mode 100644 index 00000000000..1c83d3650bf --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/exceptions/SolaxUpdateException.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.solax.internal.exceptions; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link SolaxUpdateException} exception thrown to the abstract class from the sub-classes if something goes wrong + * with the data update + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class SolaxUpdateException extends Exception { + + private static final long serialVersionUID = 1L; + private @Nullable Object[] args; + + public SolaxUpdateException(String message, @Nullable Object... args) { + super(message); + this.args = args; + } + + public @Nullable Object[] getArgs() { + return args; + } +} diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/handlers/AbstractSolaxHandler.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/handlers/AbstractSolaxHandler.java new file mode 100644 index 00000000000..037efa896bb --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/handlers/AbstractSolaxHandler.java @@ -0,0 +1,137 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.solax.internal.handlers; + +import java.io.IOException; +import java.time.ZoneId; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.solax.internal.SolaxBindingConstants; +import org.openhab.binding.solax.internal.SolaxConfiguration; +import org.openhab.binding.solax.internal.connectivity.SolaxConnector; +import org.openhab.binding.solax.internal.exceptions.SolaxUpdateException; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.i18n.TranslationProvider; +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.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link SolaxCloudHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public abstract class AbstractSolaxHandler extends BaseThingHandler { + + private final Logger logger = LoggerFactory.getLogger(AbstractSolaxHandler.class); + + private static final int INITIAL_SCHEDULE_DELAY_SECONDS = 0; + + private @NonNullByDefault({}) SolaxConnector connector; + + private @Nullable ScheduledFuture schedule; + + private final ReentrantLock retrieveDataCallLock = new ReentrantLock(); + + protected final TranslationProvider i18nProvider; + + protected final ZoneId timeZone; + + public AbstractSolaxHandler(Thing thing, TranslationProvider i18nProvider, TimeZoneProvider timeZoneProvider) { + super(thing); + this.i18nProvider = i18nProvider; + this.timeZone = timeZoneProvider.getTimeZone(); + } + + @Override + public void initialize() { + updateStatus(ThingStatus.UNKNOWN); + + SolaxConfiguration config = getConfigAs(SolaxConfiguration.class); + connector = createConnector(config); + int refreshInterval = config.refreshInterval; + TimeUnit timeUnit = TimeUnit.SECONDS; + + logger.debug("Scheduling regular interval retrieval every {} {}", refreshInterval, timeUnit); + schedule = scheduler.scheduleWithFixedDelay(this::retrieveData, INITIAL_SCHEDULE_DELAY_SECONDS, refreshInterval, + timeUnit); + } + + private void retrieveData() { + if (retrieveDataCallLock.tryLock()) { + try { + String rawJsonData = connector.retrieveData(); + logger.debug("Raw data retrieved = {}", rawJsonData); + + if (rawJsonData != null && !rawJsonData.isEmpty()) { + updateFromData(rawJsonData); + if (getThing().getStatus() != ThingStatus.ONLINE) { + updateStatus(ThingStatus.ONLINE); + } + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + SolaxBindingConstants.I18N_KEY_OFFLINE_COMMUNICATION_ERROR_JSON_CANNOT_BE_RETRIEVED); + } + } catch (IOException e) { + logger.debug("Exception received while attempting to retrieve data via HTTP", e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } catch (SolaxUpdateException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } finally { + retrieveDataCallLock.unlock(); + } + } else { + logger.debug("Unable to retrieve data because a request is already in progress."); + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (command instanceof RefreshType) { + scheduler.execute(this::retrieveData); + } else { + logger.debug("Binding {} only supports refresh command", SolaxBindingConstants.BINDING_ID); + } + } + + @Override + public void dispose() { + super.dispose(); + cancelSchedule(); + } + + protected void cancelSchedule() { + ScheduledFuture schedule = this.schedule; + if (schedule != null) { + logger.debug("Cancelling schedule {}", schedule); + schedule.cancel(true); + this.schedule = null; + } + } + + protected abstract SolaxConnector createConnector(SolaxConfiguration config); + + protected abstract void updateFromData(String rawJsonData) throws SolaxUpdateException; +} diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/handlers/SolaxCloudHandler.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/handlers/SolaxCloudHandler.java new file mode 100644 index 00000000000..5f9eb30d570 --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/handlers/SolaxCloudHandler.java @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.solax.internal.handlers; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.solax.internal.SolaxBindingConstants; +import org.openhab.binding.solax.internal.SolaxConfiguration; +import org.openhab.binding.solax.internal.connectivity.CloudHttpConnector; +import org.openhab.binding.solax.internal.connectivity.SolaxConnector; +import org.openhab.binding.solax.internal.connectivity.rawdata.cloud.CloudRawDataBean; +import org.openhab.binding.solax.internal.exceptions.SolaxUpdateException; +import org.openhab.binding.solax.internal.model.cloud.CloudInverterData; +import org.openhab.core.i18n.LocaleProvider; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.i18n.TranslationProvider; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Thing; + +/** + * The {@link SolaxCloudHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class SolaxCloudHandler extends AbstractSolaxHandler { + + public SolaxCloudHandler(Thing thing, TranslationProvider i18nProvider, TimeZoneProvider timeZoneProvider, + LocaleProvider localeProvider) { + super(thing, i18nProvider, timeZoneProvider); + } + + @Override + protected SolaxConnector createConnector(SolaxConfiguration config) { + return new CloudHttpConnector(config.token, config.password); + } + + @Override + protected void updateFromData(String rawJsonData) throws SolaxUpdateException { + CloudRawDataBean rawCloudBean = CloudRawDataBean.fromJson(rawJsonData); + if (!rawCloudBean.isValid()) { + throw new SolaxUpdateException("Deserialized JSON response is not valid. Bean = {}, rawJsonData = {}", + rawCloudBean, rawJsonData); + } + + if (!rawCloudBean.isSuccess() || rawCloudBean.isError()) { + throw new SolaxUpdateException( + "Connection to cloud was successful but the cloud API returned error. response = {}", rawCloudBean); + } + + updateProperties(rawCloudBean); + updateChannels(rawCloudBean); + } + + private void updateProperties(CloudInverterData cloudData) { + updateProperty(Thing.PROPERTY_SERIAL_NUMBER, cloudData.getInverterSerialNumber()); + updateProperty(SolaxBindingConstants.PROPERTY_INVERTER_TYPE, cloudData.getInverterType().name()); + } + + private void updateChannels(CloudInverterData inverterData) { + updateState(SolaxBindingConstants.CHANNEL_INVERTER_PV1_POWER, + new QuantityType<>(inverterData.getPowerPv1(), Units.WATT)); + updateState(SolaxBindingConstants.CHANNEL_INVERTER_PV2_POWER, + new QuantityType<>(inverterData.getPowerPv2(), Units.WATT)); + updateState(SolaxBindingConstants.CHANNEL_INVERTER_PV3_POWER, + new QuantityType<>(inverterData.getPowerPv3(), Units.WATT)); + updateState(SolaxBindingConstants.CHANNEL_INVERTER_PV4_POWER, + new QuantityType<>(inverterData.getPowerPv4(), Units.WATT)); + updateState(SolaxBindingConstants.CHANNEL_INVERTER_PV_TOTAL_POWER, + new QuantityType<>(inverterData.getPVTotalPower(), Units.WATT)); + + updateState(SolaxBindingConstants.CHANNEL_BATTERY_POWER, + new QuantityType<>(inverterData.getBatteryPower(), Units.WATT)); + updateState(SolaxBindingConstants.CHANNEL_BATTERY_STATE_OF_CHARGE, + new QuantityType<>(inverterData.getBatteryLevel(), Units.PERCENT)); + updateState(SolaxBindingConstants.CHANNEL_FEED_IN_POWER, + new QuantityType<>(inverterData.getFeedInPower(), Units.WATT)); + + updateState(SolaxBindingConstants.CHANNEL_TOTAL_FEED_IN_ENERGY, + new QuantityType<>(inverterData.getFeedInEnergy(), Units.KILOWATT_HOUR)); + updateState(SolaxBindingConstants.CHANNEL_TOTAL_CONSUMPTION, + new QuantityType<>(inverterData.getConsumeEnergy(), Units.KILOWATT_HOUR)); + updateState(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_METER2, + new QuantityType<>(inverterData.getFeedInPowerM2(), Units.WATT)); + updateState(SolaxBindingConstants.CHANNEL_INVERTER_EPS_POWER_R, + new QuantityType<>(inverterData.getEPSPowerR(), Units.WATT)); + updateState(SolaxBindingConstants.CHANNEL_INVERTER_EPS_POWER_S, + new QuantityType<>(inverterData.getEPSPowerS(), Units.WATT)); + updateState(SolaxBindingConstants.CHANNEL_INVERTER_EPS_POWER_T, + new QuantityType<>(inverterData.getEPSPowerT(), Units.WATT)); + + updateState(SolaxBindingConstants.CHANNEL_TODAY_ENERGY, + new QuantityType<>(inverterData.getYieldToday(), Units.KILOWATT_HOUR)); + updateState(SolaxBindingConstants.CHANNEL_TOTAL_ENERGY, + new QuantityType<>(inverterData.getYieldTotal(), Units.KILOWATT_HOUR)); + + updateState(SolaxBindingConstants.CHANNEL_INVERTER_WORKMODE, + new StringType(inverterData.getInverterWorkMode())); + + updateState(SolaxBindingConstants.CHANNEL_TIMESTAMP, new DateTimeType(inverterData.getUploadTime(timeZone))); + + updateState(SolaxBindingConstants.CHANNEL_RAW_DATA, new StringType(inverterData.getRawData())); + } +} diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/handlers/SolaxLocalAccessHandler.java similarity index 74% rename from bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java rename to bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/handlers/SolaxLocalAccessHandler.java index dd17e7bc27b..60f59e74eb0 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/handlers/SolaxLocalAccessHandler.java @@ -10,40 +10,36 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.solax.internal; +package org.openhab.binding.solax.internal.handlers; -import java.io.IOException; import java.time.ZonedDateTime; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; import javax.measure.Quantity; import javax.measure.Unit; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.solax.internal.SolaxBindingConstants; +import org.openhab.binding.solax.internal.SolaxConfiguration; import org.openhab.binding.solax.internal.connectivity.LocalHttpConnector; -import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; -import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.connectivity.SolaxConnector; +import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean; import org.openhab.binding.solax.internal.model.InverterType; -import org.openhab.binding.solax.internal.model.parsers.RawDataParser; +import org.openhab.binding.solax.internal.model.local.LocalInverterData; +import org.openhab.binding.solax.internal.model.local.parsers.RawDataParser; +import org.openhab.core.i18n.TimeZoneProvider; +import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; import org.openhab.core.library.unit.SIUnits; import org.openhab.core.library.unit.Units; import org.openhab.core.thing.Channel; -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.RefreshType; import org.openhab.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,64 +53,25 @@ import com.google.gson.JsonParseException; * @author Konstantin Polihronov - Initial contribution */ @NonNullByDefault -public class SolaxLocalAccessHandler extends BaseThingHandler { +public class SolaxLocalAccessHandler extends AbstractSolaxHandler { private final Logger logger = LoggerFactory.getLogger(SolaxLocalAccessHandler.class); - private static final int INITIAL_SCHEDULE_DELAY_SECONDS = 5; - - private @NonNullByDefault({}) LocalHttpConnector localHttpConnector; - - private @Nullable ScheduledFuture schedule; - private boolean alreadyRemovedUnsupportedChannels; private final Set unsupportedExistingChannels = new HashSet<>(); - private final ReentrantLock retrieveDataCallLock = new ReentrantLock(); - - public SolaxLocalAccessHandler(Thing thing) { - super(thing); + public SolaxLocalAccessHandler(Thing thing, TranslationProvider i18nProvider, TimeZoneProvider timeZoneProvider) { + super(thing, i18nProvider, timeZoneProvider); } @Override - public void initialize() { - updateStatus(ThingStatus.UNKNOWN); - - SolaxConfiguration config = getConfigAs(SolaxConfiguration.class); - localHttpConnector = new LocalHttpConnector(config.password, config.hostname); - int refreshInterval = config.refreshInterval; - TimeUnit timeUnit = TimeUnit.SECONDS; - - logger.debug("Scheduling regular interval retrieval every {} {}", refreshInterval, timeUnit); - schedule = scheduler.scheduleWithFixedDelay(this::retrieveData, INITIAL_SCHEDULE_DELAY_SECONDS, refreshInterval, - timeUnit); + protected SolaxConnector createConnector(SolaxConfiguration config) { + return new LocalHttpConnector(config.password, config.hostname); } - private void retrieveData() { - if (retrieveDataCallLock.tryLock()) { - try { - String rawJsonData = localHttpConnector.retrieveData(); - logger.debug("Raw data retrieved = {}", rawJsonData); - - if (rawJsonData != null && !rawJsonData.isEmpty()) { - updateFromData(rawJsonData); - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - SolaxBindingConstants.I18N_KEY_OFFLINE_COMMUNICATION_ERROR_JSON_CANNOT_BE_RETRIEVED); - } - } catch (IOException e) { - logger.debug("Exception received while attempting to retrieve data via HTTP", e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } finally { - retrieveDataCallLock.unlock(); - } - } else { - logger.debug("Unable to retrieve data because a request is already in progress."); - } - } - - private void updateFromData(String rawJsonData) { + @Override + protected void updateFromData(String rawJsonData) { try { LocalConnectRawDataBean rawDataBean = parseJson(rawJsonData); InverterType inverterType = calculateInverterType(rawDataBean); @@ -125,13 +82,9 @@ public class SolaxLocalAccessHandler extends BaseThingHandler { alreadyRemovedUnsupportedChannels = true; } - InverterData genericInverterData = parser.getData(rawDataBean); + LocalInverterData genericInverterData = parser.getData(rawDataBean); updateChannels(parser, genericInverterData); updateProperties(genericInverterData); - - if (getThing().getStatus() != ThingStatus.ONLINE) { - updateStatus(ThingStatus.ONLINE); - } } else { cancelSchedule(); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, @@ -144,9 +97,9 @@ public class SolaxLocalAccessHandler extends BaseThingHandler { } private LocalConnectRawDataBean parseJson(String rawJsonData) { - LocalConnectRawDataBean inverterParsedData = LocalConnectRawDataBean.fromJson(rawJsonData); - logger.debug("Received a new inverter JSON object. Data = {}", inverterParsedData.toString()); - return inverterParsedData; + LocalConnectRawDataBean fromJson = LocalConnectRawDataBean.fromJson(rawJsonData); + logger.debug("Received a new inverter JSON object. Data = {}", fromJson.toString()); + return fromJson; } private InverterType calculateInverterType(LocalConnectRawDataBean rawDataBean) { @@ -154,12 +107,12 @@ public class SolaxLocalAccessHandler extends BaseThingHandler { return InverterType.fromIndex(type); } - private void updateProperties(InverterData genericInverterData) { + private void updateProperties(LocalInverterData genericInverterData) { updateProperty(Thing.PROPERTY_SERIAL_NUMBER, genericInverterData.getWifiSerial()); updateProperty(SolaxBindingConstants.PROPERTY_INVERTER_TYPE, genericInverterData.getInverterType().name()); } - private void updateChannels(RawDataParser parser, InverterData inverterData) { + private void updateChannels(RawDataParser parser, LocalInverterData inverterData) { updateState(SolaxBindingConstants.CHANNEL_RAW_DATA, new StringType(inverterData.getRawData())); Set supportedChannels = parser.getSupportedChannels(); @@ -190,10 +143,6 @@ public class SolaxLocalAccessHandler extends BaseThingHandler { supportedChannels); updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_TEMPERATURE, inverterData.getBatteryTemperature(), SIUnits.CELSIUS, supportedChannels); - updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_TEMPERATURE1, inverterData.getInverterTemperature1(), - SIUnits.CELSIUS, supportedChannels); - updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_TEMPERATURE2, inverterData.getInverterTemperature2(), - SIUnits.CELSIUS, supportedChannels); updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_STATE_OF_CHARGE, inverterData.getBatteryLevel(), Units.PERCENT, supportedChannels); updateChannel(SolaxBindingConstants.CHANNEL_FEED_IN_POWER, inverterData.getFeedInPower(), Units.WATT, @@ -297,29 +246,6 @@ public class SolaxLocalAccessHandler extends BaseThingHandler { channelsToRemoveForLog); } - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - if (command instanceof RefreshType) { - scheduler.execute(this::retrieveData); - } else { - logger.debug("Binding {} only supports refresh command", SolaxBindingConstants.BINDING_ID); - } - } - - @Override - public void dispose() { - super.dispose(); - cancelSchedule(); - } - - private void cancelSchedule() { - ScheduledFuture schedule = this.schedule; - if (schedule != null) { - schedule.cancel(true); - this.schedule = null; - } - } - private > void updateChannel(String channelID, double value, Unit unit, Set supportedChannels) { if (supportedChannels.contains(channelID)) { diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java index 9d87543dfe7..e24f1346445 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java @@ -18,10 +18,10 @@ import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.solax.internal.model.parsers.RawDataParser; -import org.openhab.binding.solax.internal.model.parsers.X1HybridG4DataParser; -import org.openhab.binding.solax.internal.model.parsers.X3HybridG4DataParser; -import org.openhab.binding.solax.internal.model.parsers.X3MicOrProG2DataParser; +import org.openhab.binding.solax.internal.model.local.parsers.RawDataParser; +import org.openhab.binding.solax.internal.model.local.parsers.X1HybridG4DataParser; +import org.openhab.binding.solax.internal.model.local.parsers.X3HybridG4DataParser; +import org.openhab.binding.solax.internal.model.local.parsers.X3MicOrProG2DataParser; /** * The {@link InverterType} class is enum representing the different inverter types with a simple logic to convert from diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/cloud/CloudInverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/cloud/CloudInverterData.java new file mode 100644 index 00000000000..de207ffd9a6 --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/cloud/CloudInverterData.java @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.solax.internal.model.cloud; + +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.solax.internal.model.InverterType; + +/** + * The {@link CloudInverterData} Interface for the parsed inverter data in meaningful format. Currently the cloud + * responds with the same response for all type of inverters, so it's modeled in a single interface. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public interface CloudInverterData { + boolean isSuccess(); + + String getOverallResult(); + + int getCode(); + + String getInverterSerialNumber(); + + String getWifiSerialNumber(); + + double getInverterOutputPower(); + + double getYieldToday(); + + double getYieldTotal(); + + double getFeedInPower(); + + double getFeedInEnergy(); + + double getConsumeEnergy(); + + double getFeedInPowerM2(); + + double getEPSPowerR(); + + double getEPSPowerS(); + + double getEPSPowerT(); + + InverterType getInverterType(); + + double getBatteryLevel(); + + double getBatteryPower(); + + double getPowerPv1(); + + double getPowerPv2(); + + double getPowerPv3(); + + double getPowerPv4(); + + short getInverterWorkModeCode(); + + default String getInverterWorkMode() { + return String.valueOf(getInverterWorkModeCode()); + } + + /** + * Undocumented in the API so currently it's only an int probably the code of the battery status + * + * @return battery status code as int + */ + int getBatteryStatus(); + + @Nullable + String getRawData(); + + double getPVTotalPower(); + + ZonedDateTime getUploadTime(ZoneId zoneId); +} diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/CommonInverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/CommonLocalInverterData.java similarity index 80% rename from bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/CommonInverterData.java rename to bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/CommonLocalInverterData.java index b266dfac7c2..6d7f23f5ad6 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/CommonInverterData.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/CommonLocalInverterData.java @@ -10,30 +10,29 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.solax.internal.model.impl; +package org.openhab.binding.solax.internal.model.local; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; -import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean; import org.openhab.binding.solax.internal.model.InverterType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * The {@link CommonInverterData} is an abstract class that contains the common information, applicable for all + * The {@link CommonLocalInverterData} is an abstract class that contains the common information, applicable for all * inverters. * * @author Konstantin Polihronov - Initial contribution */ @NonNullByDefault -public abstract class CommonInverterData implements InverterData { +public abstract class CommonLocalInverterData implements LocalInverterData { - private final Logger logger = LoggerFactory.getLogger(CommonInverterData.class); + private final Logger logger = LoggerFactory.getLogger(CommonLocalInverterData.class); private LocalConnectRawDataBean data; - public CommonInverterData(LocalConnectRawDataBean data) { + public CommonLocalInverterData(LocalConnectRawDataBean data) { this.data = data; } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/LocalInverterData.java similarity index 95% rename from bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterData.java rename to bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/LocalInverterData.java index 429e3367819..106aaee772d 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterData.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/LocalInverterData.java @@ -10,18 +10,19 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.solax.internal.model; +package org.openhab.binding.solax.internal.model.local; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.solax.internal.model.InverterType; /** - * The {@link InverterData} Interface for the parsed inverter data in meaningful format + * The {@link LocalInverterData} Interface for the parsed inverter data in meaningful format * * @author Konstantin Polihronov - Initial contribution */ @NonNullByDefault -public interface InverterData { +public interface LocalInverterData { @Nullable String getWifiSerial(); @@ -90,14 +91,6 @@ public interface InverterData { return Short.MIN_VALUE; } - default short getInverterWorkModeCode() { - return Short.MIN_VALUE; - } - - default String getInverterWorkMode() { - return String.valueOf(getInverterWorkModeCode()); - } - default short getBatteryLevel() { return Short.MIN_VALUE; } @@ -222,6 +215,14 @@ public interface InverterData { return Short.MIN_VALUE; } + default short getInverterWorkModeCode() { + return Short.MIN_VALUE; + } + + default String getInverterWorkMode() { + return String.valueOf(getInverterWorkModeCode()); + } + default String toStringDetailed() { return "WifiSerial = " + getWifiSerial() + ", WifiVersion = " + getWifiVersion() + ", InverterType = " + getInverterType() + ", BatteryPower = " + getBatteryPower() + "W, Battery SoC = " + getBatteryLevel() diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X1HybridG4InverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/X1HybridG4InverterData.java similarity index 91% rename from bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X1HybridG4InverterData.java rename to bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/X1HybridG4InverterData.java index ead4e053024..b5364595c27 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X1HybridG4InverterData.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/X1HybridG4InverterData.java @@ -10,10 +10,10 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.solax.internal.model.impl; +package org.openhab.binding.solax.internal.model.local; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean; /** * The {@link X1HybridG4InverterData} is an implementation of the single phased inverter data interface for X1 Hybrid G4 @@ -22,7 +22,7 @@ import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDa * @author Konstantin Polihronov - Initial contribution */ @NonNullByDefault -public class X1HybridG4InverterData extends CommonInverterData { +public class X1HybridG4InverterData extends CommonLocalInverterData { public X1HybridG4InverterData(LocalConnectRawDataBean data) { super(data); @@ -83,11 +83,6 @@ public class X1HybridG4InverterData extends CommonInverterData { return getData(9); } - @Override - public short getInverterWorkModeCode() { - return getData(10); - } - @Override public double getBatteryVoltage() { return ((double) getData(14)) / 100; @@ -112,4 +107,9 @@ public class X1HybridG4InverterData extends CommonInverterData { public short getBatteryLevel() { return getData(18); } + + @Override + public short getInverterWorkModeCode() { + return getData(10); + } } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3HybridG4InverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/X3HybridG4InverterData.java similarity index 95% rename from bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3HybridG4InverterData.java rename to bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/X3HybridG4InverterData.java index 48ec4b707bd..b613fb4487c 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3HybridG4InverterData.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/X3HybridG4InverterData.java @@ -10,10 +10,10 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.solax.internal.model.impl; +package org.openhab.binding.solax.internal.model.local; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean; /** * The {@link X3HybridG4InverterData} is responsible for handling commands, which are @@ -22,7 +22,7 @@ import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDa * @author Konstantin Polihronov - Initial contribution */ @NonNullByDefault -public class X3HybridG4InverterData extends CommonInverterData { +public class X3HybridG4InverterData extends CommonLocalInverterData { public X3HybridG4InverterData(LocalConnectRawDataBean data) { super(data); @@ -125,11 +125,6 @@ public class X3HybridG4InverterData extends CommonInverterData { return ((double) getData(18)) / 100; } - @Override - public short getInverterWorkModeCode() { - return getData(19); - } - // Battery @Override @@ -225,4 +220,9 @@ public class X3HybridG4InverterData extends CommonInverterData { public double getTodayBatteryChargeEnergy() { return ((double) getData(79)) / 10; } + + @Override + public short getInverterWorkModeCode() { + return getData(19); + } } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3MicOrProG2InverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/X3MicOrProG2InverterData.java similarity index 93% rename from bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3MicOrProG2InverterData.java rename to bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/X3MicOrProG2InverterData.java index ddf6fb379cc..bd0e03c7a42 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3MicOrProG2InverterData.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/X3MicOrProG2InverterData.java @@ -10,10 +10,10 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.solax.internal.model.impl; +package org.openhab.binding.solax.internal.model.local; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean; /** * The {@link X3HybridG4InverterData} is responsible for handling commands, which are @@ -23,7 +23,7 @@ import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDa * (based on X1/X3 G4 parser from Konstantin Polihronov) */ @NonNullByDefault -public class X3MicOrProG2InverterData extends CommonInverterData { +public class X3MicOrProG2InverterData extends CommonLocalInverterData { public X3MicOrProG2InverterData(LocalConnectRawDataBean data) { super(data); diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/RawDataParser.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/RawDataParser.java similarity index 72% rename from bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/RawDataParser.java rename to bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/RawDataParser.java index 8fa85a20efd..d45cc088ebd 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/RawDataParser.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/RawDataParser.java @@ -10,13 +10,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.solax.internal.model.parsers; +package org.openhab.binding.solax.internal.model.local.parsers; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; -import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.model.local.LocalInverterData; /** * The {@link RawDataParser} declares generic parser implementation that parses raw data to generic inverter data which @@ -27,7 +27,7 @@ import org.openhab.binding.solax.internal.model.InverterData; @NonNullByDefault public interface RawDataParser { - InverterData getData(LocalConnectRawDataBean bean); + LocalInverterData getData(LocalConnectRawDataBean bean); Set getSupportedChannels(); } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X1HybridG4DataParser.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/X1HybridG4DataParser.java similarity index 82% rename from bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X1HybridG4DataParser.java rename to bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/X1HybridG4DataParser.java index 538e937231f..dc6ac43f264 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X1HybridG4DataParser.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/X1HybridG4DataParser.java @@ -10,16 +10,16 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.solax.internal.model.parsers; +package org.openhab.binding.solax.internal.model.local.parsers; import static org.openhab.binding.solax.internal.SolaxBindingConstants.*; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; -import org.openhab.binding.solax.internal.model.InverterData; -import org.openhab.binding.solax.internal.model.impl.X1HybridG4InverterData; +import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.model.local.LocalInverterData; +import org.openhab.binding.solax.internal.model.local.X1HybridG4InverterData; /** * The {@link SinglePhaseDataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the @@ -39,7 +39,7 @@ public class X1HybridG4DataParser implements RawDataParser { CHANNEL_INVERTER_OUTPUT_VOLTAGE, CHANNEL_INVERTER_OUTPUT_FREQUENCY, CHANNEL_INVERTER_WORKMODE); @Override - public InverterData getData(LocalConnectRawDataBean rawData) { + public LocalInverterData getData(LocalConnectRawDataBean rawData) { return new X1HybridG4InverterData(rawData); } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3HybridG4DataParser.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/X3HybridG4DataParser.java similarity index 86% rename from bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3HybridG4DataParser.java rename to bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/X3HybridG4DataParser.java index 7f0680aa489..781a5de752f 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3HybridG4DataParser.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/X3HybridG4DataParser.java @@ -10,16 +10,16 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.solax.internal.model.parsers; +package org.openhab.binding.solax.internal.model.local.parsers; import static org.openhab.binding.solax.internal.SolaxBindingConstants.*; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; -import org.openhab.binding.solax.internal.model.InverterData; -import org.openhab.binding.solax.internal.model.impl.X3HybridG4InverterData; +import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.model.local.LocalInverterData; +import org.openhab.binding.solax.internal.model.local.X3HybridG4InverterData; /** * The {@link X3HybridG4DataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the @@ -47,7 +47,7 @@ public class X3HybridG4DataParser implements RawDataParser { CHANNEL_TODAY_BATTERY_CHARGE_ENERGY, CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY, CHANNEL_INVERTER_WORKMODE); @Override - public InverterData getData(LocalConnectRawDataBean rawData) { + public LocalInverterData getData(LocalConnectRawDataBean rawData) { return new X3HybridG4InverterData(rawData); } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3MicOrProG2DataParser.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/X3MicOrProG2DataParser.java similarity index 84% rename from bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3MicOrProG2DataParser.java rename to bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/X3MicOrProG2DataParser.java index 6a1660b1b4f..4dec8566beb 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3MicOrProG2DataParser.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/X3MicOrProG2DataParser.java @@ -10,16 +10,16 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.solax.internal.model.parsers; +package org.openhab.binding.solax.internal.model.local.parsers; import static org.openhab.binding.solax.internal.SolaxBindingConstants.*; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; -import org.openhab.binding.solax.internal.model.InverterData; -import org.openhab.binding.solax.internal.model.impl.X3MicOrProG2InverterData; +import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.model.local.LocalInverterData; +import org.openhab.binding.solax.internal.model.local.X3MicOrProG2InverterData; /** * The {@link X3MicOrProG2DataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the @@ -44,7 +44,7 @@ public class X3MicOrProG2DataParser implements RawDataParser { CHANNEL_INVERTER_TEMPERATURE2, CHANNEL_INVERTER_WORKMODE, CHANNEL_RAW_DATA); @Override - public InverterData getData(LocalConnectRawDataBean rawData) { + public LocalInverterData getData(LocalConnectRawDataBean rawData) { return new X3MicOrProG2InverterData(rawData); } diff --git a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties index 7344060f394..e5f3ee6b0cd 100644 --- a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties +++ b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties @@ -5,6 +5,42 @@ addon.solax.description = This is the binding for Solax inverters. # thing types +thing-type.solax.cloud-connect-inverter.label = Cloud Connect Inverter +thing-type.solax.cloud-connect-inverter.description = The inverter representation that is retrieved from Solax cloud API +thing-type.solax.cloud-connect-inverter.channel.battery-level.label = Battery Level +thing-type.solax.cloud-connect-inverter.channel.battery-level.description = The battery state of charge in percent +thing-type.solax.cloud-connect-inverter.channel.battery-power.label = Battery Power +thing-type.solax.cloud-connect-inverter.channel.battery-power.description = Power to/from the battery +thing-type.solax.cloud-connect-inverter.channel.feed-in-power.label = Feed-In Power +thing-type.solax.cloud-connect-inverter.channel.feed-in-power.description = Power to/from the electricity network. +thing-type.solax.cloud-connect-inverter.channel.inverter-eps-power-r.label = EPS power R +thing-type.solax.cloud-connect-inverter.channel.inverter-eps-power-r.description = Inverter AC EPS power R +thing-type.solax.cloud-connect-inverter.channel.inverter-eps-power-s.label = EPS power S +thing-type.solax.cloud-connect-inverter.channel.inverter-eps-power-s.description = Inverter AC EPS power S +thing-type.solax.cloud-connect-inverter.channel.inverter-eps-power-t.label = EPS power T +thing-type.solax.cloud-connect-inverter.channel.inverter-eps-power-t.description = Inverter AC EPS power T +thing-type.solax.cloud-connect-inverter.channel.inverter-meter2-power.label = Meter2 Power +thing-type.solax.cloud-connect-inverter.channel.inverter-meter2-power.description = Inverter power on meter2. +thing-type.solax.cloud-connect-inverter.channel.inverter-output-power.label = Inverter Input/Output Power +thing-type.solax.cloud-connect-inverter.channel.inverter-output-power.description = Power to/from the inverter +thing-type.solax.cloud-connect-inverter.channel.pv-total-power.label = PV Total Power +thing-type.solax.cloud-connect-inverter.channel.pv-total-power.description = The sum of PV powers from all PV strings +thing-type.solax.cloud-connect-inverter.channel.pv1-power.label = PV 1 Power +thing-type.solax.cloud-connect-inverter.channel.pv1-power.description = Electric power of PV String 1 +thing-type.solax.cloud-connect-inverter.channel.pv2-power.label = PV 2 Power +thing-type.solax.cloud-connect-inverter.channel.pv2-power.description = Electric power of PV String 2 +thing-type.solax.cloud-connect-inverter.channel.pv3-power.label = PV 3 Power +thing-type.solax.cloud-connect-inverter.channel.pv3-power.description = Electric power of PV String 3 +thing-type.solax.cloud-connect-inverter.channel.pv4-power.label = PV 4 Power +thing-type.solax.cloud-connect-inverter.channel.pv4-power.description = Electric power of PV String 4 +thing-type.solax.cloud-connect-inverter.channel.today-energy.label = Yield today +thing-type.solax.cloud-connect-inverter.channel.today-energy.description = Inverter output energy for the day +thing-type.solax.cloud-connect-inverter.channel.total-consumption.label = Total Consumed Energy +thing-type.solax.cloud-connect-inverter.channel.total-consumption.description = Total Energy consumed from the electricity network. +thing-type.solax.cloud-connect-inverter.channel.total-energy.label = Yield total +thing-type.solax.cloud-connect-inverter.channel.total-energy.description = Total inverter output energy +thing-type.solax.cloud-connect-inverter.channel.total-feed-in-energy.label = Total Feed-In Energy +thing-type.solax.cloud-connect-inverter.channel.total-feed-in-energy.description = Total energy feed-in to the electricity network. thing-type.solax.local-connect-inverter.label = Local Connect Inverter thing-type.solax.local-connect-inverter.description = The inverter representation that supports local connections via HTTP thing-type.solax.local-connect-inverter.channel.battery-current.label = Battery Current @@ -100,6 +136,12 @@ thing-type.solax.local-connect-inverter.channel.total-pv-energy.description = To # thing types config +thing-type.config.solax.cloud-connect-inverter.password.label = Password +thing-type.config.solax.cloud-connect-inverter.password.description = Password for accessing cloud API (the serial number of the Wi-Fi module) +thing-type.config.solax.cloud-connect-inverter.refreshInterval.label = Refresh Interval +thing-type.config.solax.cloud-connect-inverter.refreshInterval.description = Refresh interval in seconds. (Cloud API is limited to max 10 calls per minute and 10000 times per day) +thing-type.config.solax.cloud-connect-inverter.token.label = Token +thing-type.config.solax.cloud-connect-inverter.token.description = Token to access the Solax cloud API thing-type.config.solax.local-connect-inverter.hostname.label = Network Address thing-type.config.solax.local-connect-inverter.hostname.description = IP address or the host name of the Wi-Fi module thing-type.config.solax.local-connect-inverter.password.label = Password @@ -113,6 +155,8 @@ channel-type.solax.battery-temperature.label = Battery Temperature channel-type.solax.battery-temperature.description = Battery Temperature channel-type.solax.frequency.label = Electric Frequency channel-type.solax.frequency.description = Frequency of the electricity to/from the inverter +channel-type.solax.inverter-status-type.label = Inverter Status +channel-type.solax.inverter-status-type.description = The status of the inverter. channel-type.solax.inverter-temperature.label = Inverter Temperature channel-type.solax.inverter-temperature.description = Inverter Temperature channel-type.solax.inverter-workmode.label = Inverter Workmode @@ -138,5 +182,19 @@ channel-type.solax.raw-data-type.description = The raw JSON data retrieved from # thing status descriptions +cloud.inverter.status.wait = Wait +cloud.inverter.status.check = Check +cloud.inverter.status.normal = Normal +cloud.inverter.status.fault = Fault +cloud.inverter.status.permanent-fault = Permanent Fault +cloud.inverter.status.update = Update +cloud.inverter.status.eps-check = EPS Check +cloud.inverter.status.self-test = Self Test +cloud.inverter.status.idle = Idle +cloud.inverter.status.standby = Standby +cloud.inverter.status.pv-wake-up-battery = PV Wake-up Battery +cloud.inverter.status.gen-check = Gen Check +cloud.inverter.status.gen-run = Gen Run +cloud.inverter.status.unknown = Unknown offline.communication-error.json-cannot-be-retrieved = JSON data could not be retrieved. offline.configuration-error.parser-not-implemented = Parser for inverter of type {0} is not implemented. diff --git a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/channel_types.xml b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/channel_types.xml index 472c9eea76d..2267461b2e7 100644 --- a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/channel_types.xml +++ b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/channel_types.xml @@ -57,6 +57,29 @@ + + String + + Inverter Workmode + + + + + + + + + + + + + + + + + + + DateTime diff --git a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/cloudConnectInverter.xml b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/cloudConnectInverter.xml new file mode 100644 index 00000000000..fb8a15ef1a5 --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/cloudConnectInverter.xml @@ -0,0 +1,111 @@ + + + + + + + The inverter representation that is retrieved from Solax cloud API + + + + + Power to/from the inverter + + + + Electric power of PV String 1 + + + + Electric power of PV String 2 + + + + Electric power of PV String 3 + + + + Electric power of PV String 4 + + + + The sum of PV powers from all PV strings + + + + The battery state of charge in percent + + + + Power to/from the battery + + + + Power to/from the electricity network. + + + + Total energy feed-in to the electricity network. + + + + Total Energy consumed from the electricity network. + + + + Inverter power on meter2. + + + + Inverter AC EPS power R + + + + Inverter AC EPS power S + + + + Inverter AC EPS power T + + + + + Inverter output energy for the day + + + + Total inverter output energy + + + + + + + Inverter Workmode + + + + + + + + + Refresh interval in seconds. (Cloud API is limited to max 10 calls per minute and 10000 times per day) + 30 + + + + Password for accessing cloud API (the serial number of the Wi-Fi module) + password + + + + Token to access the Solax cloud API + password + + + + diff --git a/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/cloud/TestCloudParser.java b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/cloud/TestCloudParser.java new file mode 100644 index 00000000000..7dfd5770ebb --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/cloud/TestCloudParser.java @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.solax.internal.cloud; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.solax.internal.connectivity.rawdata.cloud.CloudRawDataBean; +import org.openhab.binding.solax.internal.model.InverterType; + +/** + * The {@link TestCloudParser} Simple test that tests for proper parsing against a real data from the cloud + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class TestCloudParser { + private static final String CLOUD_RESPONSE_SUCCESS = """ + { + "success": true, + "exception": "Query success!", + "result": { + "inverterSN": "xxx", + "sn": "xxxx", + "acpower": 151.0, + "yieldtoday": 0.9, + "yieldtotal": 7339.5, + "feedinpower": -925.0, + "feedinenergy": 147.2, + "consumeenergy": 2536.4, + "feedinpowerM2": 0.0, + "soc": 27.0, + "peps1": 56.3, + "peps2": null, + "peps3": null, + "inverterType": "15", + "inverterStatus": "110", + "uploadTime": "2023-11-28 18:34:17", + "batPower": 1245.0, + "powerdc1": 55.0, + "powerdc2": 670.0, + "powerdc3": null, + "powerdc4": null, + "batStatus": "0" + }, + "code": 0 + } + """; + + private static final String CLOUD_RESPONSE_ERROR = """ + {"success":false,"exception":"error","result":null,"code":2001} + """; + + @Test + public void testPositiveScenario() throws IOException { + CloudRawDataBean bean = CloudRawDataBean.fromJson(CLOUD_RESPONSE_SUCCESS); + assertTrue(bean.isSuccess(), "Overall response success"); + assertEquals(bean.getOverallResult(), CloudRawDataBean.QUERY_SUCCESS, "Query success string response"); + + InverterType type = bean.getInverterType(); + assertEquals(InverterType.X1_HYBRID_G4, type, "Inverter type not recognized properly"); + + assertEquals(110, bean.getInverterWorkModeCode()); + assertEquals("110", bean.getInverterWorkMode()); + + assertEquals("xxx", bean.getInverterSerialNumber()); + assertEquals("xxxx", bean.getWifiSerialNumber()); + + assertEquals(151, bean.getInverterOutputPower(), "AC/Inverter output power"); + assertEquals(0.9, bean.getYieldToday(), "Yield today"); + assertEquals(7339.5, bean.getYieldTotal(), "Yield total"); + assertEquals(-925.0, bean.getFeedInPower(), "Feed-in power"); + assertEquals(147.2, bean.getFeedInEnergy(), "Feed-in energy"); + assertEquals(2536.4, bean.getConsumeEnergy(), "Consume energy"); + assertEquals(0, bean.getFeedInPowerM2(), "Feed in power M2"); + assertEquals(56.3, bean.getEPSPowerR(), "EPS power R"); + + assertEquals(1245, bean.getBatteryPower(), "Battery power"); + assertEquals(55, bean.getPowerPv1(), "PV1"); + assertEquals(670, bean.getPowerPv2(), "PV2"); + assertEquals(0, bean.getBatteryStatus(), "Battery status"); + assertEquals(0, bean.getCode(), "Return code"); + + ZoneId zoneId = ZoneId.of("CET"); + assertEquals(ZonedDateTime.of(LocalDateTime.of(2023, 11, 28, 18, 34, 17), zoneId), bean.getUploadTime(zoneId), + "Upload time"); + } + + @Test + public void testNegativeScenario() throws IOException { + CloudRawDataBean bean = CloudRawDataBean.fromJson(CLOUD_RESPONSE_ERROR); + assertFalse(bean.isSuccess(), "Overall response success"); + assertEquals(bean.getOverallResult(), CloudRawDataBean.ERROR, "Expected error as a response"); + } + + @Test + public void testEmptyResponse() throws IOException { + CloudRawDataBean bean = CloudRawDataBean.fromJson(""); + assertFalse(bean.isSuccess(), "Overall response success"); + assertEquals(bean.getOverallResult(), CloudRawDataBean.ERROR, "Expected error as a response"); + } +} diff --git a/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX1HybridG4Parser.java b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/local/TestX1HybridG4Parser.java similarity index 92% rename from bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX1HybridG4Parser.java rename to bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/local/TestX1HybridG4Parser.java index c8c8c3ac974..a2dcc3400a4 100644 --- a/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX1HybridG4Parser.java +++ b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/local/TestX1HybridG4Parser.java @@ -10,16 +10,16 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.solax.internal; +package org.openhab.binding.solax.internal.local; import static org.junit.jupiter.api.Assertions.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; -import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; -import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean; import org.openhab.binding.solax.internal.model.InverterType; -import org.openhab.binding.solax.internal.model.parsers.RawDataParser; +import org.openhab.binding.solax.internal.model.local.LocalInverterData; +import org.openhab.binding.solax.internal.model.local.parsers.RawDataParser; /** * The {@link TestX1HybridG4Parser} Simple test that tests for proper parsing against a real data from the inverter @@ -53,7 +53,7 @@ public class TestX1HybridG4Parser { RawDataParser parser = inverterType.getParser(); assertNotNull(parser); - InverterData data = parser.getData(bean); + LocalInverterData data = parser.getData(bean); assertEquals("SOME_SERIAL_NUMBER", data.getWifiSerial()); assertEquals("3.008.10", data.getWifiVersion()); diff --git a/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3HybridG4Parser.java b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/local/TestX3HybridG4Parser.java similarity index 93% rename from bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3HybridG4Parser.java rename to bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/local/TestX3HybridG4Parser.java index 8c02197c80f..951d725d1a8 100644 --- a/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3HybridG4Parser.java +++ b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/local/TestX3HybridG4Parser.java @@ -10,16 +10,16 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.solax.internal; +package org.openhab.binding.solax.internal.local; import static org.junit.jupiter.api.Assertions.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; -import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; -import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean; import org.openhab.binding.solax.internal.model.InverterType; -import org.openhab.binding.solax.internal.model.parsers.RawDataParser; +import org.openhab.binding.solax.internal.model.local.LocalInverterData; +import org.openhab.binding.solax.internal.model.local.parsers.RawDataParser; /** * The {@link TestX3HybridG4Parser} simple test that tests for proper parsing against a real data from the inverter @@ -60,7 +60,7 @@ public class TestX3HybridG4Parser { RawDataParser parser = inverterType.getParser(); assertNotNull(parser); - InverterData data = parser.getData(bean); + LocalInverterData data = parser.getData(bean); assertEquals("XYZ", data.getWifiSerial()); assertEquals("3.005.01", data.getWifiVersion()); diff --git a/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3MicOrProG2Parser.java b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/local/TestX3MicOrProG2Parser.java similarity index 90% rename from bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3MicOrProG2Parser.java rename to bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/local/TestX3MicOrProG2Parser.java index c3d353d7a94..29846be0de3 100644 --- a/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3MicOrProG2Parser.java +++ b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/local/TestX3MicOrProG2Parser.java @@ -10,16 +10,16 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.solax.internal; +package org.openhab.binding.solax.internal.local; import static org.junit.jupiter.api.Assertions.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; -import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; -import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean; import org.openhab.binding.solax.internal.model.InverterType; -import org.openhab.binding.solax.internal.model.parsers.RawDataParser; +import org.openhab.binding.solax.internal.model.local.LocalInverterData; +import org.openhab.binding.solax.internal.model.local.parsers.RawDataParser; /** * The {@link TestX3HybridG4Parser} simple test that tests for proper parsing against a real data from the inverter @@ -56,7 +56,7 @@ public class TestX3MicOrProG2Parser { RawDataParser parser = inverterType.getParser(); assertNotNull(parser); - InverterData data = parser.getData(bean); + LocalInverterData data = parser.getData(bean); assertEquals("XYZ", data.getWifiSerial()); assertEquals("3.003.02", data.getWifiVersion());