]> git.basschouten.com Git - openhab-addons.git/commitdiff
[solax] Cloud connection support (#16124)
authorKonstantin Polihronov <polychronov@gmail.com>
Wed, 14 Feb 2024 15:22:23 +0000 (17:22 +0200)
committerGitHub <noreply@github.com>
Wed, 14 Feb 2024 15:22:23 +0000 (16:22 +0100)
* Initial rearrangement of classes and cloud response in test

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
45 files changed:
bundles/org.openhab.binding.solax/README.md
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxConfiguration.java
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxHandlerFactory.java
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java [deleted file]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/CloudHttpConnector.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/LocalConnectRawDataBean.java [deleted file]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/RawDataBean.java
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/cloud/CloudRawDataBean.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/cloud/Result.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/local/LocalConnectRawDataBean.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/exceptions/SolaxUpdateException.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/handlers/AbstractSolaxHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/handlers/SolaxCloudHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/handlers/SolaxLocalAccessHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterData.java [deleted file]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/cloud/CloudInverterData.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/CommonInverterData.java [deleted file]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X1HybridG4InverterData.java [deleted file]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3HybridG4InverterData.java [deleted file]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3MicOrProG2InverterData.java [deleted file]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/CommonLocalInverterData.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/LocalInverterData.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/X1HybridG4InverterData.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/X3HybridG4InverterData.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/X3MicOrProG2InverterData.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/RawDataParser.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/X1HybridG4DataParser.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/X3HybridG4DataParser.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/X3MicOrProG2DataParser.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/RawDataParser.java [deleted file]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X1HybridG4DataParser.java [deleted file]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3HybridG4DataParser.java [deleted file]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3MicOrProG2DataParser.java [deleted file]
bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties
bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/channel_types.xml
bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/cloudConnectInverter.xml [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX1HybridG4Parser.java [deleted file]
bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3HybridG4Parser.java [deleted file]
bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3MicOrProG2Parser.java [deleted file]
bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/cloud/TestCloudParser.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/local/TestX1HybridG4Parser.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/local/TestX3HybridG4Parser.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/local/TestX3MicOrProG2Parser.java [new file with mode: 0644]

index a9427a847e822732535d8d068375ac5ed2be9b38..08b6cd8973791170463c84ceccb7d68c321648cd 100644 (file)
@@ -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="<SERIAL NUMBER OF THE WIFI MODULE>", hostname="<local IP/hostname in the network>" ] 
+Thing solax:cloud-connect-inverter:cloudInverter  [ refresh=30, password="<REG_NUMBER>", token="<TOKEN>" ] 
 ```
 
 ### Item Configuration
@@ -128,17 +164,38 @@ Thing solax:local-connect-inverter:localInverter  [ refreshInterval=10, password
 Group gSolaxInverter "Solax Inverter" <energy> (boilerRoom)
 Group solarPanels "Solar panels" <energy> (gSolaxInverter)
 
-Number solaxPowerWest "West [%.0f W]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv1-power" }
-Number solaxPowerEast "East [%.0f W]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv2-power" }
+// Direct connect
+Number solaxPowerWest "West Power [%d W]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv1-power" }
+Number solaxPowerEast "East Power [%d W]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv2-power" }
+Number solaxGenerationTotal "Total generаtion now [%.0f W]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels) { channel="solax:local-connect-inverter:localInverter:pv-total-power" }
+Number solaxVoltageWest "West Voltage [%.1f V]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv1-voltage" }
+Number solaxVoltageEast "East Voltage [%.1f V]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv2-voltage" }
+Number solaxCurrentWest "West Current [%.1f A]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv1-current" }
+Number solaxCurrentEast "East Current [%.1f A]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv2-current" }
 Number solaxBatteryPower "Battery power [%.0f W]" <energy> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-power" }
-Number solaxBatterySoc "Battery SoC [%.0f %%]" <batterylevel> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-state-of-charge" }
+Number solaxBatterySoc "Battery SoC [%.0f %%]" <batterylevel> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-level" }
+Number solaxBatteryTemperature "Battery temperature [%d Â°C]" <temperature> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-temperature" }
+Number solaxBatteryCurrent "Battery current [%.1f A]" <energy> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-current" }
+Number solaxBatteryVoltage "Battery voltage [%.1f V]" <energy> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-voltage" }
 
 Number solaxFeedInPower "Feed-in power (CEZ) [%.0f W]" <energy> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:feed-in-power" }
+Number solaxCalculatedTotalFeedInPower "Calculated feed-in total power (CEZ) [%.0f KWh]" <energy> (gsolax_inverter,EveryChangePersist)
+Number solaxCalculatedTotalFeedInPowerThisMonth "Calculated feed-in total power this month (CEZ) [%.0f KWh]" <energy> (gsolax_inverter,EveryChangePersist) 
 Number solaxAcPower "Invertor output power [%.0f W]" <energy> (gsolax_inverter,EveryChangePersist){ channel="solax:local-connect-inverter:localInverter:inverter-output-power" }
+Number solaxFrequency "Invertor frequency [%.2f Hz]" <energy> (gsolax_inverter,EveryChangePersist){ channel="solax:local-connect-inverter:localInverter:inverter-frequency" }
+Number solaxVoltage "Invertor voltage [%.1f V]" <energy> (gsolax_inverter,EveryChangePersist){ channel="solax:local-connect-inverter:localInverter:inverter-voltage" }
+
+String solaxLocalUploadTime "Local update time [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1tS]" <calendar> (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]" <calendar> (gsolax_inverter) { channel="solax:cloud-connect-inverter:cloudInverter:last-update-time" }
 
-String solaxInverterType "Inverter Type [%s]" <energy> (gsolax_inverter) { channel="solax:local-connect-inverter:localInverter:inverter-type"}
-String solaxUploadTime "Last update time [%s]" <calendar> (gsolax_inverter) { channel="solax:local-connect-inverter:localInverter:last-update-time" }
-String solaxRawData "Raw data [%s]" <data> (gsolax_inverter) { channel="solax:local-connect-inverter:localInverter:raw-data" }
+String solaxLocalRawData "Local raw data [%s]" <data> (gsolax_inverter) { channel="solax:local-connect-inverter:localInverter:raw-data" }
+String solaxCloudRawData "Cloud raw data [%s]" <data> (gsolax_inverter) { channel="solax:cloud-connect-inverter:cloudInverter:raw-data" }
+
+// Cloud
+Number solaxYieldToday "Yield today [%.0f kWh]" <energy> (gsolax_inverter){ channel="solax:cloud-connect-inverter:cloudInverter:today-energy" } 
+Number solaxYieldTotal "Yield total [%.0f kWh]" <energy> (gsolax_inverter) { channel="solax:cloud-connect-inverter:cloudInverter:total-energy" }
+Number solaxFeedInEnergy "Total Feed-in (CEZ) Power [%.0f kWh]" <energy> (gsolax_inverter,EveryChangePersist) { channel="solax:cloud-connect-inverter:cloudInverter:total-feed-in-energy" }
+String solaxInverterStatus "Inverter Status [%s]" <energy> (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
+}
 ```
-
-
index 39d0b3ba6eb67b72ff9fa9a1a7c550c382c2d6c3..4d2f18fb49cd8b80e684c1e9caa11113c0151ecd 100644 (file)
@@ -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<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_LOCAL_CONNECT_INVERTER);
+    public static final Set<ThingTypeUID> 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";
 }
index da3e94732f3a3b03df0e6125b2d4b82f24bef505..343b8009c02fdab92fc99d58e183f68e1559b27d 100644 (file)
@@ -25,4 +25,5 @@ public class SolaxConfiguration {
     public String hostname = "";
     public String password = "";
     public int refreshInterval = 10;
+    public String token = "";
 }
index 1e43245fa9a4d3447b7dec16a3ec3b4f1e062efc..c0c5a2b1035b48c97b3348300bfe428d306fbe10 100644 (file)
@@ -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/SolaxLocalAccessHandler.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java
deleted file mode 100644 (file)
index dd17e7b..0000000
+++ /dev/null
@@ -1,337 +0,0 @@
-/**
- * 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;
-
-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.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.model.InverterType;
-import org.openhab.binding.solax.internal.model.parsers.RawDataParser;
-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;
-
-import com.google.gson.JsonParseException;
-
-/**
- * The {@link SolaxLocalAccessHandler} is responsible for handling commands, which are
- * sent to one of the channels.
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public class SolaxLocalAccessHandler extends BaseThingHandler {
-
-    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<String> unsupportedExistingChannels = new HashSet<>();
-
-    private final ReentrantLock retrieveDataCallLock = new ReentrantLock();
-
-    public SolaxLocalAccessHandler(Thing thing) {
-        super(thing);
-    }
-
-    @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);
-    }
-
-    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) {
-        try {
-            LocalConnectRawDataBean rawDataBean = parseJson(rawJsonData);
-            InverterType inverterType = calculateInverterType(rawDataBean);
-            RawDataParser parser = inverterType.getParser();
-            if (parser != null) {
-                if (!alreadyRemovedUnsupportedChannels) {
-                    removeUnsupportedChannels(inverterType.getSupportedChannels());
-                    alreadyRemovedUnsupportedChannels = true;
-                }
-
-                InverterData 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,
-                        "@text/offline.configuration-error.parser-not-implemented [\"" + inverterType.name() + "\"]");
-            }
-        } catch (JsonParseException e) {
-            logger.debug("Unable to deserialize from JSON.", e);
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
-        }
-    }
-
-    private LocalConnectRawDataBean parseJson(String rawJsonData) {
-        LocalConnectRawDataBean inverterParsedData = LocalConnectRawDataBean.fromJson(rawJsonData);
-        logger.debug("Received a new inverter JSON object. Data = {}", inverterParsedData.toString());
-        return inverterParsedData;
-    }
-
-    private InverterType calculateInverterType(LocalConnectRawDataBean rawDataBean) {
-        int type = rawDataBean.getType();
-        return InverterType.fromIndex(type);
-    }
-
-    private void updateProperties(InverterData genericInverterData) {
-        updateProperty(Thing.PROPERTY_SERIAL_NUMBER, genericInverterData.getWifiSerial());
-        updateProperty(SolaxBindingConstants.PROPERTY_INVERTER_TYPE, genericInverterData.getInverterType().name());
-    }
-
-    private void updateChannels(RawDataParser parser, InverterData inverterData) {
-        updateState(SolaxBindingConstants.CHANNEL_RAW_DATA, new StringType(inverterData.getRawData()));
-
-        Set<String> supportedChannels = parser.getSupportedChannels();
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_POWER, inverterData.getPV1Power(), Units.WATT,
-                supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_CURRENT, inverterData.getPV1Current(), Units.AMPERE,
-                supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_VOLTAGE, inverterData.getPV1Voltage(), Units.VOLT,
-                supportedChannels);
-
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_POWER, inverterData.getPV2Power(), Units.WATT,
-                supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_CURRENT, inverterData.getPV2Current(), Units.AMPERE,
-                supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_VOLTAGE, inverterData.getPV2Voltage(), Units.VOLT,
-                supportedChannels);
-
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV_TOTAL_POWER, inverterData.getPVTotalPower(), Units.WATT,
-                supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV_TOTAL_CURRENT, inverterData.getPVTotalCurrent(),
-                Units.AMPERE, supportedChannels);
-
-        updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_POWER, inverterData.getBatteryPower(), Units.WATT,
-                supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_CURRENT, inverterData.getBatteryCurrent(), Units.AMPERE,
-                supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_VOLTAGE, inverterData.getBatteryVoltage(), Units.VOLT,
-                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,
-                supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_POWER_USAGE, inverterData.getPowerUsage(), Units.WATT,
-                supportedChannels);
-        updateState(SolaxBindingConstants.CHANNEL_INVERTER_WORKMODE,
-                new StringType(inverterData.getInverterWorkMode()));
-
-        // Totals
-        updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_ENERGY, inverterData.getTotalEnergy(), Units.KILOWATT_HOUR,
-                supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_BATTERY_DISCHARGE_ENERGY,
-                inverterData.getTotalBatteryDischargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_BATTERY_CHARGE_ENERGY,
-                inverterData.getTotalBatteryChargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_PV_ENERGY, inverterData.getTotalPVEnergy(),
-                Units.KILOWATT_HOUR, supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_FEED_IN_ENERGY, inverterData.getTotalFeedInEnergy(),
-                Units.KILOWATT_HOUR, supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_CONSUMPTION, inverterData.getTotalConsumption(),
-                Units.KILOWATT_HOUR, supportedChannels);
-
-        // Today's
-        updateChannel(SolaxBindingConstants.CHANNEL_TODAY_ENERGY, inverterData.getTodayEnergy(), Units.KILOWATT_HOUR,
-                supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY,
-                inverterData.getTodayBatteryDischargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_TODAY_BATTERY_CHARGE_ENERGY,
-                inverterData.getTodayBatteryChargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_TODAY_FEED_IN_ENERGY, inverterData.getTodayFeedInEnergy(),
-                Units.KILOWATT_HOUR, supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_TODAY_CONSUMPTION, inverterData.getTodayConsumption(),
-                Units.KILOWATT_HOUR, supportedChannels);
-
-        // Single phase specific channels
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER, inverterData.getInverterOutputPower(),
-                Units.WATT, supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT, inverterData.getInverterCurrent(),
-                Units.AMPERE, supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE, inverterData.getInverterVoltage(),
-                Units.VOLT, supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY, inverterData.getInverterFrequency(),
-                Units.HERTZ, supportedChannels);
-
-        // Three phase specific channels
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE1, inverterData.getOutputPowerPhase1(),
-                Units.WATT, supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE2, inverterData.getOutputPowerPhase2(),
-                Units.WATT, supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE3, inverterData.getOutputPowerPhase3(),
-                Units.WATT, supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_TOTAL_OUTPUT_POWER, inverterData.getTotalOutputPower(),
-                Units.WATT, supportedChannels);
-
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1, inverterData.getCurrentPhase1(),
-                Units.AMPERE, supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2, inverterData.getCurrentPhase2(),
-                Units.AMPERE, supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3, inverterData.getCurrentPhase3(),
-                Units.AMPERE, supportedChannels);
-
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1, inverterData.getVoltagePhase1(),
-                Units.VOLT, supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2, inverterData.getVoltagePhase2(),
-                Units.VOLT, supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3, inverterData.getVoltagePhase3(),
-                Units.VOLT, supportedChannels);
-
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1, inverterData.getFrequencyPhase1(),
-                Units.HERTZ, supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2, inverterData.getFrequencyPhase2(),
-                Units.HERTZ, supportedChannels);
-        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, inverterData.getFrequencyPhase3(),
-                Units.HERTZ, supportedChannels);
-
-        // Binding provided data
-        updateState(SolaxBindingConstants.CHANNEL_TIMESTAMP, new DateTimeType(ZonedDateTime.now()));
-    }
-
-    private void removeUnsupportedChannels(Set<String> supportedChannels) {
-        if (supportedChannels.isEmpty()) {
-            return;
-        }
-        List<Channel> channels = getThing().getChannels();
-        List<Channel> channelsToRemove = channels.stream()
-                .filter(channel -> !supportedChannels.contains(channel.getUID().getId())).toList();
-
-        if (!channelsToRemove.isEmpty()) {
-            if (logger.isDebugEnabled()) {
-                logRemovedChannels(channelsToRemove);
-            }
-            updateThing(editThing().withoutChannels(channelsToRemove).build());
-        }
-    }
-
-    private void logRemovedChannels(List<Channel> channelsToRemove) {
-        List<String> channelsToRemoveForLog = channelsToRemove.stream().map(channel -> channel.getUID().getId())
-                .toList();
-        logger.debug("Detected unsupported channels for the current inverter. Channels to be removed: {}",
-                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 <T extends Quantity<T>> void updateChannel(String channelID, double value, Unit<T> unit,
-            Set<String> supportedChannels) {
-        if (supportedChannels.contains(channelID)) {
-            if (value > Short.MIN_VALUE) {
-                updateState(channelID, new QuantityType<>(value, unit));
-            } else if (!unsupportedExistingChannels.contains(channelID)) {
-                updateState(channelID, UnDefType.UNDEF);
-                unsupportedExistingChannels.add(channelID);
-                logger.warn(
-                        "Channel {} is marked as supported, but its value is out of the defined range. Value = {}. This is unexpected behaviour. Please file a bug.",
-                        channelID, value);
-            }
-        }
-    }
-}
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 (file)
index 0000000..c5b1cce
--- /dev/null
@@ -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/LocalConnectRawDataBean.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/LocalConnectRawDataBean.java
deleted file mode 100644 (file)
index ee2293c..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-/**
- * 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;
-
-import java.util.Arrays;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.solax.internal.util.GsonSupplier;
-
-import com.google.gson.Gson;
-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
- */
-@NonNullByDefault
-public class LocalConnectRawDataBean implements RawDataBean {
-
-    private @Nullable String sn;
-    private @Nullable String ver;
-    private int type;
-    @SerializedName("Data")
-    private short @Nullable [] data;
-    @SerializedName("Information")
-    private String @Nullable [] information;
-    private @Nullable String rawData;
-
-    @Override
-    public String toString() {
-        return "LocalConnectRawDataBean [sn=" + sn + ", ver=" + ver + ", type=" + type + ", Information="
-                + Arrays.toString(information) + ", Data=" + Arrays.toString(data) + "]";
-    }
-
-    public @Nullable String getSn() {
-        return sn;
-    }
-
-    public void setSn(@Nullable String sn) {
-        this.sn = sn;
-    }
-
-    public @Nullable String getVer() {
-        return ver;
-    }
-
-    public void setVer(@Nullable String ver) {
-        this.ver = ver;
-    }
-
-    public int getType() {
-        return type;
-    }
-
-    public void setType(int type) {
-        this.type = type;
-    }
-
-    public short @Nullable [] getData() {
-        return data;
-    }
-
-    public void setData(short @Nullable [] data) {
-        this.data = data;
-    }
-
-    public String @Nullable [] getInformation() {
-        return information;
-    }
-
-    public void setInformation(String @Nullable [] information) {
-        this.information = information;
-    }
-
-    @Override
-    public @Nullable String getRawData() {
-        return rawData;
-    }
-
-    public void setRawData(String rawData) {
-        this.rawData = rawData;
-    }
-
-    public static LocalConnectRawDataBean fromJson(String json) {
-        if (json.isEmpty()) {
-            throw new IllegalArgumentException("JSON payload should not be empty");
-        }
-
-        Gson gson = GsonSupplier.getInstance();
-        LocalConnectRawDataBean deserializedObject = gson.fromJson(json, LocalConnectRawDataBean.class);
-        if (deserializedObject == null) {
-            throw new IllegalStateException("Unexpected null result when deserializing JSON");
-        }
-        deserializedObject.setRawData(json);
-        return deserializedObject;
-    }
-}
index 094e79e1c7439d174419e0716577ee5097539711..7aa2530e56cd0f39bb65a77fb8444249a44d564c 100644 (file)
@@ -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 (file)
index 0000000..9a772aa
--- /dev/null
@@ -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 (file)
index 0000000..bf051b7
--- /dev/null
@@ -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/local/LocalConnectRawDataBean.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/local/LocalConnectRawDataBean.java
new file mode 100644 (file)
index 0000000..8766484
--- /dev/null
@@ -0,0 +1,111 @@
+/**
+ * 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.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;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link LocalConnectRawDataBean} collects the raw data and the specific implementation to return the parsed data.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class LocalConnectRawDataBean implements RawDataBean {
+
+    private @Nullable String sn;
+    private @Nullable String ver;
+    private int type;
+    @SerializedName("Data")
+    private short @Nullable [] data;
+    @SerializedName("Information")
+    private String @Nullable [] information;
+    private @Nullable String rawData;
+
+    @Override
+    public String toString() {
+        return "LocalConnectRawDataBean [sn=" + sn + ", ver=" + ver + ", type=" + type + ", Information="
+                + Arrays.toString(information) + ", Data=" + Arrays.toString(data) + "]";
+    }
+
+    public @Nullable String getSn() {
+        return sn;
+    }
+
+    public void setSn(@Nullable String sn) {
+        this.sn = sn;
+    }
+
+    public @Nullable String getVer() {
+        return ver;
+    }
+
+    public void setVer(@Nullable String ver) {
+        this.ver = ver;
+    }
+
+    public int getType() {
+        return type;
+    }
+
+    public void setType(int type) {
+        this.type = type;
+    }
+
+    public short @Nullable [] getData() {
+        return data;
+    }
+
+    public void setData(short @Nullable [] data) {
+        this.data = data;
+    }
+
+    public String @Nullable [] getInformation() {
+        return information;
+    }
+
+    public void setInformation(String @Nullable [] information) {
+        this.information = information;
+    }
+
+    @Override
+    public @Nullable String getRawData() {
+        return rawData;
+    }
+
+    @Override
+    public void setRawData(String rawData) {
+        this.rawData = rawData;
+    }
+
+    public static LocalConnectRawDataBean fromJson(String json) {
+        if (json.isEmpty()) {
+            throw new IllegalArgumentException("JSON payload should not be empty");
+        }
+
+        Gson gson = GsonSupplier.getInstance();
+        LocalConnectRawDataBean deserializedObject = gson.fromJson(json, LocalConnectRawDataBean.class);
+        if (deserializedObject == null) {
+            throw new IllegalStateException("Unexpected null result when deserializing JSON");
+        }
+        deserializedObject.setRawData(json);
+        return deserializedObject;
+    }
+}
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 (file)
index 0000000..1c83d36
--- /dev/null
@@ -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 (file)
index 0000000..037efa8
--- /dev/null
@@ -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 (file)
index 0000000..5f9eb30
--- /dev/null
@@ -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/handlers/SolaxLocalAccessHandler.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/handlers/SolaxLocalAccessHandler.java
new file mode 100644 (file)
index 0000000..60f59e7
--- /dev/null
@@ -0,0 +1,263 @@
+/**
+ * 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.time.ZonedDateTime;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.measure.Quantity;
+import javax.measure.Unit;
+
+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.LocalHttpConnector;
+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.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.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonParseException;
+
+/**
+ * The {@link SolaxLocalAccessHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class SolaxLocalAccessHandler extends AbstractSolaxHandler {
+
+    private final Logger logger = LoggerFactory.getLogger(SolaxLocalAccessHandler.class);
+
+    private boolean alreadyRemovedUnsupportedChannels;
+
+    private final Set<String> unsupportedExistingChannels = new HashSet<>();
+
+    public SolaxLocalAccessHandler(Thing thing, TranslationProvider i18nProvider, TimeZoneProvider timeZoneProvider) {
+        super(thing, i18nProvider, timeZoneProvider);
+    }
+
+    @Override
+    protected SolaxConnector createConnector(SolaxConfiguration config) {
+        return new LocalHttpConnector(config.password, config.hostname);
+    }
+
+    @Override
+    protected void updateFromData(String rawJsonData) {
+        try {
+            LocalConnectRawDataBean rawDataBean = parseJson(rawJsonData);
+            InverterType inverterType = calculateInverterType(rawDataBean);
+            RawDataParser parser = inverterType.getParser();
+            if (parser != null) {
+                if (!alreadyRemovedUnsupportedChannels) {
+                    removeUnsupportedChannels(inverterType.getSupportedChannels());
+                    alreadyRemovedUnsupportedChannels = true;
+                }
+
+                LocalInverterData genericInverterData = parser.getData(rawDataBean);
+                updateChannels(parser, genericInverterData);
+                updateProperties(genericInverterData);
+            } else {
+                cancelSchedule();
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+                        "@text/offline.configuration-error.parser-not-implemented [\"" + inverterType.name() + "\"]");
+            }
+        } catch (JsonParseException e) {
+            logger.debug("Unable to deserialize from JSON.", e);
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+        }
+    }
+
+    private LocalConnectRawDataBean parseJson(String rawJsonData) {
+        LocalConnectRawDataBean fromJson = LocalConnectRawDataBean.fromJson(rawJsonData);
+        logger.debug("Received a new inverter JSON object. Data = {}", fromJson.toString());
+        return fromJson;
+    }
+
+    private InverterType calculateInverterType(LocalConnectRawDataBean rawDataBean) {
+        int type = rawDataBean.getType();
+        return InverterType.fromIndex(type);
+    }
+
+    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, LocalInverterData inverterData) {
+        updateState(SolaxBindingConstants.CHANNEL_RAW_DATA, new StringType(inverterData.getRawData()));
+
+        Set<String> supportedChannels = parser.getSupportedChannels();
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_POWER, inverterData.getPV1Power(), Units.WATT,
+                supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_CURRENT, inverterData.getPV1Current(), Units.AMPERE,
+                supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_VOLTAGE, inverterData.getPV1Voltage(), Units.VOLT,
+                supportedChannels);
+
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_POWER, inverterData.getPV2Power(), Units.WATT,
+                supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_CURRENT, inverterData.getPV2Current(), Units.AMPERE,
+                supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_VOLTAGE, inverterData.getPV2Voltage(), Units.VOLT,
+                supportedChannels);
+
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV_TOTAL_POWER, inverterData.getPVTotalPower(), Units.WATT,
+                supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV_TOTAL_CURRENT, inverterData.getPVTotalCurrent(),
+                Units.AMPERE, supportedChannels);
+
+        updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_POWER, inverterData.getBatteryPower(), Units.WATT,
+                supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_CURRENT, inverterData.getBatteryCurrent(), Units.AMPERE,
+                supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_VOLTAGE, inverterData.getBatteryVoltage(), Units.VOLT,
+                supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_TEMPERATURE, inverterData.getBatteryTemperature(),
+                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,
+                supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_POWER_USAGE, inverterData.getPowerUsage(), Units.WATT,
+                supportedChannels);
+        updateState(SolaxBindingConstants.CHANNEL_INVERTER_WORKMODE,
+                new StringType(inverterData.getInverterWorkMode()));
+
+        // Totals
+        updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_ENERGY, inverterData.getTotalEnergy(), Units.KILOWATT_HOUR,
+                supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_BATTERY_DISCHARGE_ENERGY,
+                inverterData.getTotalBatteryDischargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_BATTERY_CHARGE_ENERGY,
+                inverterData.getTotalBatteryChargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_PV_ENERGY, inverterData.getTotalPVEnergy(),
+                Units.KILOWATT_HOUR, supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_FEED_IN_ENERGY, inverterData.getTotalFeedInEnergy(),
+                Units.KILOWATT_HOUR, supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_CONSUMPTION, inverterData.getTotalConsumption(),
+                Units.KILOWATT_HOUR, supportedChannels);
+
+        // Today's
+        updateChannel(SolaxBindingConstants.CHANNEL_TODAY_ENERGY, inverterData.getTodayEnergy(), Units.KILOWATT_HOUR,
+                supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY,
+                inverterData.getTodayBatteryDischargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_TODAY_BATTERY_CHARGE_ENERGY,
+                inverterData.getTodayBatteryChargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_TODAY_FEED_IN_ENERGY, inverterData.getTodayFeedInEnergy(),
+                Units.KILOWATT_HOUR, supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_TODAY_CONSUMPTION, inverterData.getTodayConsumption(),
+                Units.KILOWATT_HOUR, supportedChannels);
+
+        // Single phase specific channels
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER, inverterData.getInverterOutputPower(),
+                Units.WATT, supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT, inverterData.getInverterCurrent(),
+                Units.AMPERE, supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE, inverterData.getInverterVoltage(),
+                Units.VOLT, supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY, inverterData.getInverterFrequency(),
+                Units.HERTZ, supportedChannels);
+
+        // Three phase specific channels
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE1, inverterData.getOutputPowerPhase1(),
+                Units.WATT, supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE2, inverterData.getOutputPowerPhase2(),
+                Units.WATT, supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE3, inverterData.getOutputPowerPhase3(),
+                Units.WATT, supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_TOTAL_OUTPUT_POWER, inverterData.getTotalOutputPower(),
+                Units.WATT, supportedChannels);
+
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1, inverterData.getCurrentPhase1(),
+                Units.AMPERE, supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2, inverterData.getCurrentPhase2(),
+                Units.AMPERE, supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3, inverterData.getCurrentPhase3(),
+                Units.AMPERE, supportedChannels);
+
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1, inverterData.getVoltagePhase1(),
+                Units.VOLT, supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2, inverterData.getVoltagePhase2(),
+                Units.VOLT, supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3, inverterData.getVoltagePhase3(),
+                Units.VOLT, supportedChannels);
+
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1, inverterData.getFrequencyPhase1(),
+                Units.HERTZ, supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2, inverterData.getFrequencyPhase2(),
+                Units.HERTZ, supportedChannels);
+        updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, inverterData.getFrequencyPhase3(),
+                Units.HERTZ, supportedChannels);
+
+        // Binding provided data
+        updateState(SolaxBindingConstants.CHANNEL_TIMESTAMP, new DateTimeType(ZonedDateTime.now()));
+    }
+
+    private void removeUnsupportedChannels(Set<String> supportedChannels) {
+        if (supportedChannels.isEmpty()) {
+            return;
+        }
+        List<Channel> channels = getThing().getChannels();
+        List<Channel> channelsToRemove = channels.stream()
+                .filter(channel -> !supportedChannels.contains(channel.getUID().getId())).toList();
+
+        if (!channelsToRemove.isEmpty()) {
+            if (logger.isDebugEnabled()) {
+                logRemovedChannels(channelsToRemove);
+            }
+            updateThing(editThing().withoutChannels(channelsToRemove).build());
+        }
+    }
+
+    private void logRemovedChannels(List<Channel> channelsToRemove) {
+        List<String> channelsToRemoveForLog = channelsToRemove.stream().map(channel -> channel.getUID().getId())
+                .toList();
+        logger.debug("Detected unsupported channels for the current inverter. Channels to be removed: {}",
+                channelsToRemoveForLog);
+    }
+
+    private <T extends Quantity<T>> void updateChannel(String channelID, double value, Unit<T> unit,
+            Set<String> supportedChannels) {
+        if (supportedChannels.contains(channelID)) {
+            if (value > Short.MIN_VALUE) {
+                updateState(channelID, new QuantityType<>(value, unit));
+            } else if (!unsupportedExistingChannels.contains(channelID)) {
+                updateState(channelID, UnDefType.UNDEF);
+                unsupportedExistingChannels.add(channelID);
+                logger.warn(
+                        "Channel {} is marked as supported, but its value is out of the defined range. Value = {}. This is unexpected behaviour. Please file a bug.",
+                        channelID, value);
+            }
+        }
+    }
+}
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/InverterData.java
deleted file mode 100644 (file)
index 429e336..0000000
+++ /dev/null
@@ -1,231 +0,0 @@
-/**
- * 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;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-
-/**
- * The {@link InverterData} Interface for the parsed inverter data in meaningful format
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public interface InverterData {
-
-    @Nullable
-    String getWifiSerial();
-
-    @Nullable
-    String getWifiVersion();
-
-    InverterType getInverterType();
-
-    @Nullable
-    String getRawData();
-
-    default double getPV1Voltage() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getPV1Current() {
-        return Short.MIN_VALUE;
-    }
-
-    default short getPV1Power() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getPV2Voltage() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getPV2Current() {
-        return Short.MIN_VALUE;
-    }
-
-    default short getPV2Power() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getPVTotalPower() {
-        return getPV1Power() + getPV2Power();
-    }
-
-    default double getPVTotalCurrent() {
-        return getPV1Current() + getPV2Current();
-    }
-
-    default double getBatteryVoltage() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getBatteryCurrent() {
-        return Short.MIN_VALUE;
-    }
-
-    default short getBatteryPower() {
-        return Short.MIN_VALUE;
-    }
-
-    default short getBatteryTemperature() {
-        return Short.MIN_VALUE;
-    }
-
-    default short getInverterTemperature1() {
-        return Short.MIN_VALUE;
-    }
-
-    default short getInverterTemperature2() {
-        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;
-    }
-
-    default short getFeedInPower() {
-        return Short.MIN_VALUE;
-    }
-
-    default short getPowerUsage() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getTotalEnergy() {
-        return Short.MIN_VALUE;
-    }
-
-    default short getTotalBatteryDischargeEnergy() {
-        return Short.MIN_VALUE;
-    }
-
-    default short getTotalBatteryChargeEnergy() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getTotalPVEnergy() {
-        return Short.MIN_VALUE;
-    }
-
-    default short getTotalFeedInEnergy() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getTotalConsumption() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getTodayEnergy() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getTodayFeedInEnergy() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getTodayConsumption() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getTodayBatteryDischargeEnergy() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getTodayBatteryChargeEnergy() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getInverterVoltage() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getInverterCurrent() {
-        return Short.MIN_VALUE;
-    }
-
-    default short getInverterOutputPower() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getInverterFrequency() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getVoltagePhase1() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getVoltagePhase2() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getVoltagePhase3() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getCurrentPhase1() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getCurrentPhase2() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getCurrentPhase3() {
-        return Short.MIN_VALUE;
-    }
-
-    default short getOutputPowerPhase1() {
-        return Short.MIN_VALUE;
-    }
-
-    default short getOutputPowerPhase2() {
-        return Short.MIN_VALUE;
-    }
-
-    default short getOutputPowerPhase3() {
-        return Short.MIN_VALUE;
-    }
-
-    default short getTotalOutputPower() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getFrequencyPhase1() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getFrequencyPhase2() {
-        return Short.MIN_VALUE;
-    }
-
-    default double getFrequencyPhase3() {
-        return Short.MIN_VALUE;
-    }
-
-    default String toStringDetailed() {
-        return "WifiSerial = " + getWifiSerial() + ", WifiVersion = " + getWifiVersion() + ", InverterType = "
-                + getInverterType() + ", BatteryPower = " + getBatteryPower() + "W, Battery SoC = " + getBatteryLevel()
-                + "%, FeedIn Power = " + getFeedInPower() + "W, Total PV Power = " + (getPV1Power() + getPV2Power())
-                + "W";
-    }
-}
index 9d87543dfe7213de2f1730b9ca9df8d5b3251511..e24f1346445f4b7cf199c634c844350e7311fe19 100644 (file)
@@ -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 (file)
index 0000000..de207ff
--- /dev/null
@@ -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/impl/CommonInverterData.java
deleted file mode 100644 (file)
index b266dfa..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * 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.impl;
-
-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.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
- * inverters.
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public abstract class CommonInverterData implements InverterData {
-
-    private final Logger logger = LoggerFactory.getLogger(CommonInverterData.class);
-
-    private LocalConnectRawDataBean data;
-
-    public CommonInverterData(LocalConnectRawDataBean data) {
-        this.data = data;
-    }
-
-    @Override
-    public @Nullable String getRawData() {
-        return data.getRawData();
-    }
-
-    @Override
-    public @Nullable String getWifiSerial() {
-        return data.getSn();
-    }
-
-    @Override
-    public @Nullable String getWifiVersion() {
-        return data.getVer();
-    }
-
-    @Override
-    public InverterType getInverterType() {
-        return InverterType.fromIndex(data.getType());
-    }
-
-    protected short getData(int index) {
-        try {
-            short[] dataArray = data.getData();
-            if (dataArray != null) {
-                return dataArray[index];
-            }
-        } catch (IndexOutOfBoundsException e) {
-            logger.debug("Tried to get data out of bounds of the raw data array.", e);
-        }
-        return 0;
-    }
-
-    public long packU16(int indexMajor, int indexMinor) {
-        short major = getData(indexMajor);
-        short minor = getData(indexMinor);
-        if (major == 0) {
-            return minor;
-        }
-
-        return Integer.toUnsignedLong(major << 16 | minor & 0xFFFF);
-    }
-}
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/impl/X1HybridG4InverterData.java
deleted file mode 100644 (file)
index ead4e05..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-/**
- * 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.impl;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
-
-/**
- * The {@link X1HybridG4InverterData} is an implementation of the single phased inverter data interface for X1 Hybrid G4
- * inverter.
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public class X1HybridG4InverterData extends CommonInverterData {
-
-    public X1HybridG4InverterData(LocalConnectRawDataBean data) {
-        super(data);
-    }
-
-    @Override
-    public double getInverterVoltage() {
-        return ((double) getData(0)) / 10;
-    }
-
-    @Override
-    public double getInverterCurrent() {
-        return ((double) getData(1)) / 10;
-    }
-
-    @Override
-    public short getInverterOutputPower() {
-        return getData(2);
-    }
-
-    @Override
-    public double getInverterFrequency() {
-        return ((double) getData(3)) / 100;
-    }
-
-    @Override
-    public short getFeedInPower() {
-        return getData(32);
-    }
-
-    @Override
-    public double getPV1Voltage() {
-        return ((double) getData(4)) / 10;
-    }
-
-    @Override
-    public double getPV2Voltage() {
-        return ((double) getData(5)) / 10;
-    }
-
-    @Override
-    public double getPV1Current() {
-        return ((double) getData(6)) / 10;
-    }
-
-    @Override
-    public double getPV2Current() {
-        return ((double) getData(7)) / 10;
-    }
-
-    @Override
-    public short getPV1Power() {
-        return getData(8);
-    }
-
-    @Override
-    public short getPV2Power() {
-        return getData(9);
-    }
-
-    @Override
-    public short getInverterWorkModeCode() {
-        return getData(10);
-    }
-
-    @Override
-    public double getBatteryVoltage() {
-        return ((double) getData(14)) / 100;
-    }
-
-    @Override
-    public double getBatteryCurrent() {
-        return ((double) getData(15)) / 100;
-    }
-
-    @Override
-    public short getBatteryPower() {
-        return getData(16);
-    }
-
-    @Override
-    public short getBatteryTemperature() {
-        return getData(17);
-    }
-
-    @Override
-    public short getBatteryLevel() {
-        return getData(18);
-    }
-}
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/impl/X3HybridG4InverterData.java
deleted file mode 100644 (file)
index 48ec4b7..0000000
+++ /dev/null
@@ -1,228 +0,0 @@
-/**
- * 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.impl;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
-
-/**
- * The {@link X3HybridG4InverterData} is responsible for handling commands, which are
- * sent to one of the channels.
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public class X3HybridG4InverterData extends CommonInverterData {
-
-    public X3HybridG4InverterData(LocalConnectRawDataBean data) {
-        super(data);
-    }
-
-    // Inverter data
-
-    @Override
-    public double getVoltagePhase1() {
-        return ((double) getData(0)) / 10;
-    }
-
-    @Override
-    public double getVoltagePhase2() {
-        return ((double) getData(1)) / 10;
-    }
-
-    @Override
-    public double getVoltagePhase3() {
-        return ((double) getData(2)) / 10;
-    }
-
-    @Override
-    public double getCurrentPhase1() {
-        return ((double) getData(3)) / 10;
-    }
-
-    @Override
-    public double getCurrentPhase2() {
-        return ((double) getData(4)) / 10;
-    }
-
-    @Override
-    public double getCurrentPhase3() {
-        return ((double) getData(5)) / 10;
-    }
-
-    @Override
-    public short getOutputPowerPhase1() {
-        return getData(6);
-    }
-
-    @Override
-    public short getOutputPowerPhase2() {
-        return getData(7);
-    }
-
-    @Override
-    public short getOutputPowerPhase3() {
-        return getData(8);
-    }
-
-    @Override
-    public short getTotalOutputPower() {
-        return getData(9);
-    }
-
-    @Override
-    public double getPV1Voltage() {
-        return ((double) getData(10)) / 10;
-    }
-
-    @Override
-    public double getPV2Voltage() {
-        return ((double) getData(11)) / 10;
-    }
-
-    @Override
-    public double getPV1Current() {
-        return ((double) getData(12)) / 10;
-    }
-
-    @Override
-    public double getPV2Current() {
-        return ((double) getData(13)) / 10;
-    }
-
-    @Override
-    public short getPV1Power() {
-        return getData(14);
-    }
-
-    @Override
-    public short getPV2Power() {
-        return getData(15);
-    }
-
-    @Override
-    public double getFrequencyPhase1() {
-        return ((double) getData(16)) / 100;
-    }
-
-    @Override
-    public double getFrequencyPhase2() {
-        return ((double) getData(17)) / 100;
-    }
-
-    @Override
-    public double getFrequencyPhase3() {
-        return ((double) getData(18)) / 100;
-    }
-
-    @Override
-    public short getInverterWorkModeCode() {
-        return getData(19);
-    }
-
-    // Battery
-
-    @Override
-    public double getBatteryVoltage() {
-        return ((double) getData(39)) / 100;
-    }
-
-    @Override
-    public double getBatteryCurrent() {
-        return ((double) getData(40)) / 100;
-    }
-
-    @Override
-    public short getBatteryPower() {
-        return getData(41);
-    }
-
-    @Override
-    public short getBatteryTemperature() {
-        return getData(105);
-    }
-
-    @Override
-    public short getBatteryLevel() {
-        return getData(103);
-    }
-
-    // Feed in power
-
-    @Override
-    public short getFeedInPower() {
-        return (short) (getData(34) - getData(35));
-    }
-
-    // Totals
-
-    @Override
-    public short getPowerUsage() {
-        return getData(47);
-    }
-
-    @Override
-    public double getTotalEnergy() {
-        return ((double) getData(68)) / 10;
-    }
-
-    @Override
-    public short getTotalBatteryDischargeEnergy() {
-        return getData(74);
-    }
-
-    @Override
-    public short getTotalBatteryChargeEnergy() {
-        return getData(76);
-    }
-
-    @Override
-    public double getTotalPVEnergy() {
-        return ((double) getData(80)) / 10;
-    }
-
-    @Override
-    public short getTotalFeedInEnergy() {
-        return getData(86);
-    }
-
-    @Override
-    public double getTotalConsumption() {
-        return ((double) getData(88)) / 10;
-    }
-
-    @Override
-    public double getTodayEnergy() {
-        return ((double) getData(82)) / 10;
-    }
-
-    @Override
-    public double getTodayFeedInEnergy() {
-        return ((double) getData(90)) / 100;
-    }
-
-    @Override
-    public double getTodayConsumption() {
-        return ((double) getData(92)) / 100;
-    }
-
-    @Override
-    public double getTodayBatteryDischargeEnergy() {
-        return ((double) getData(78)) / 10;
-    }
-
-    @Override
-    public double getTodayBatteryChargeEnergy() {
-        return ((double) getData(79)) / 10;
-    }
-}
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/impl/X3MicOrProG2InverterData.java
deleted file mode 100644 (file)
index ddf6fb3..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-/**
- * 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.impl;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
-
-/**
- * The {@link X3HybridG4InverterData} is responsible for handling commands, which are
- * sent to one of the channels.
- *
- * @author Henrik Tóth - Initial contribution
- *         (based on X1/X3 G4 parser from Konstantin Polihronov)
- */
-@NonNullByDefault
-public class X3MicOrProG2InverterData extends CommonInverterData {
-
-    public X3MicOrProG2InverterData(LocalConnectRawDataBean data) {
-        super(data);
-    }
-
-    // Inverter data
-
-    @Override
-    public double getVoltagePhase1() {
-        return ((double) getData(0)) / 10;
-    }
-
-    @Override
-    public double getVoltagePhase2() {
-        return ((double) getData(1)) / 10;
-    }
-
-    @Override
-    public double getVoltagePhase3() {
-        return ((double) getData(2)) / 10;
-    }
-
-    @Override
-    public double getCurrentPhase1() {
-        return ((double) getData(3)) / 10;
-    }
-
-    @Override
-    public double getCurrentPhase2() {
-        return ((double) getData(4)) / 10;
-    }
-
-    @Override
-    public double getCurrentPhase3() {
-        return ((double) getData(5)) / 10;
-    }
-
-    @Override
-    public short getOutputPowerPhase1() {
-        return getData(6);
-    }
-
-    @Override
-    public short getOutputPowerPhase2() {
-        return getData(7);
-    }
-
-    @Override
-    public short getOutputPowerPhase3() {
-        return getData(8);
-    }
-
-    @Override
-    public double getPV1Voltage() {
-        return ((double) getData(9)) / 10;
-    }
-
-    @Override
-    public double getPV2Voltage() {
-        return ((double) getData(10)) / 10;
-    }
-
-    @Override
-    public double getPV1Current() {
-        return ((double) getData(12)) / 10;
-    }
-
-    @Override
-    public double getPV2Current() {
-        return ((double) getData(13)) / 10;
-    }
-
-    @Override
-    public short getPV1Power() {
-        return getData(15);
-    }
-
-    @Override
-    public short getPV2Power() {
-        return getData(16);
-    }
-
-    @Override
-    public double getFrequencyPhase1() {
-        return ((double) getData(18)) / 100;
-    }
-
-    @Override
-    public double getFrequencyPhase2() {
-        return ((double) getData(19)) / 100;
-    }
-
-    @Override
-    public double getFrequencyPhase3() {
-        return ((double) getData(20)) / 100;
-    }
-
-    @Override
-    public short getInverterWorkModeCode() {
-        return getData(21);
-    }
-
-    @Override
-    public double getTotalEnergy() {
-        return ((double) getData(22)) / 10;
-    }
-
-    @Override
-    public double getTodayEnergy() {
-        return ((double) getData(24)) / 10;
-    }
-
-    @Override
-    public short getInverterTemperature1() {
-        return getData(26);
-    }
-
-    @Override
-    public short getInverterTemperature2() {
-        return getData(27);
-    }
-
-    @Override
-    public short getTotalOutputPower() {
-        return getData(78);
-    }
-}
diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/CommonLocalInverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/CommonLocalInverterData.java
new file mode 100644 (file)
index 0000000..6d7f23f
--- /dev/null
@@ -0,0 +1,80 @@
+/**
+ * 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.local;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+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 CommonLocalInverterData} is an abstract class that contains the common information, applicable for all
+ * inverters.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public abstract class CommonLocalInverterData implements LocalInverterData {
+
+    private final Logger logger = LoggerFactory.getLogger(CommonLocalInverterData.class);
+
+    private LocalConnectRawDataBean data;
+
+    public CommonLocalInverterData(LocalConnectRawDataBean data) {
+        this.data = data;
+    }
+
+    @Override
+    public @Nullable String getRawData() {
+        return data.getRawData();
+    }
+
+    @Override
+    public @Nullable String getWifiSerial() {
+        return data.getSn();
+    }
+
+    @Override
+    public @Nullable String getWifiVersion() {
+        return data.getVer();
+    }
+
+    @Override
+    public InverterType getInverterType() {
+        return InverterType.fromIndex(data.getType());
+    }
+
+    protected short getData(int index) {
+        try {
+            short[] dataArray = data.getData();
+            if (dataArray != null) {
+                return dataArray[index];
+            }
+        } catch (IndexOutOfBoundsException e) {
+            logger.debug("Tried to get data out of bounds of the raw data array.", e);
+        }
+        return 0;
+    }
+
+    public long packU16(int indexMajor, int indexMinor) {
+        short major = getData(indexMajor);
+        short minor = getData(indexMinor);
+        if (major == 0) {
+            return minor;
+        }
+
+        return Integer.toUnsignedLong(major << 16 | minor & 0xFFFF);
+    }
+}
diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/LocalInverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/LocalInverterData.java
new file mode 100644 (file)
index 0000000..106aaee
--- /dev/null
@@ -0,0 +1,232 @@
+/**
+ * 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.local;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solax.internal.model.InverterType;
+
+/**
+ * The {@link LocalInverterData} Interface for the parsed inverter data in meaningful format
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public interface LocalInverterData {
+
+    @Nullable
+    String getWifiSerial();
+
+    @Nullable
+    String getWifiVersion();
+
+    InverterType getInverterType();
+
+    @Nullable
+    String getRawData();
+
+    default double getPV1Voltage() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getPV1Current() {
+        return Short.MIN_VALUE;
+    }
+
+    default short getPV1Power() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getPV2Voltage() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getPV2Current() {
+        return Short.MIN_VALUE;
+    }
+
+    default short getPV2Power() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getPVTotalPower() {
+        return getPV1Power() + getPV2Power();
+    }
+
+    default double getPVTotalCurrent() {
+        return getPV1Current() + getPV2Current();
+    }
+
+    default double getBatteryVoltage() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getBatteryCurrent() {
+        return Short.MIN_VALUE;
+    }
+
+    default short getBatteryPower() {
+        return Short.MIN_VALUE;
+    }
+
+    default short getBatteryTemperature() {
+        return Short.MIN_VALUE;
+    }
+
+    default short getInverterTemperature1() {
+        return Short.MIN_VALUE;
+    }
+
+    default short getInverterTemperature2() {
+        return Short.MIN_VALUE;
+    }
+
+    default short getBatteryLevel() {
+        return Short.MIN_VALUE;
+    }
+
+    default short getFeedInPower() {
+        return Short.MIN_VALUE;
+    }
+
+    default short getPowerUsage() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getTotalEnergy() {
+        return Short.MIN_VALUE;
+    }
+
+    default short getTotalBatteryDischargeEnergy() {
+        return Short.MIN_VALUE;
+    }
+
+    default short getTotalBatteryChargeEnergy() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getTotalPVEnergy() {
+        return Short.MIN_VALUE;
+    }
+
+    default short getTotalFeedInEnergy() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getTotalConsumption() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getTodayEnergy() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getTodayFeedInEnergy() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getTodayConsumption() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getTodayBatteryDischargeEnergy() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getTodayBatteryChargeEnergy() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getInverterVoltage() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getInverterCurrent() {
+        return Short.MIN_VALUE;
+    }
+
+    default short getInverterOutputPower() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getInverterFrequency() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getVoltagePhase1() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getVoltagePhase2() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getVoltagePhase3() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getCurrentPhase1() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getCurrentPhase2() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getCurrentPhase3() {
+        return Short.MIN_VALUE;
+    }
+
+    default short getOutputPowerPhase1() {
+        return Short.MIN_VALUE;
+    }
+
+    default short getOutputPowerPhase2() {
+        return Short.MIN_VALUE;
+    }
+
+    default short getOutputPowerPhase3() {
+        return Short.MIN_VALUE;
+    }
+
+    default short getTotalOutputPower() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getFrequencyPhase1() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getFrequencyPhase2() {
+        return Short.MIN_VALUE;
+    }
+
+    default double getFrequencyPhase3() {
+        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()
+                + "%, FeedIn Power = " + getFeedInPower() + "W, Total PV Power = " + (getPV1Power() + getPV2Power())
+                + "W";
+    }
+}
diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/X1HybridG4InverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/X1HybridG4InverterData.java
new file mode 100644 (file)
index 0000000..b536459
--- /dev/null
@@ -0,0 +1,115 @@
+/**
+ * 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.local;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+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
+ * inverter.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class X1HybridG4InverterData extends CommonLocalInverterData {
+
+    public X1HybridG4InverterData(LocalConnectRawDataBean data) {
+        super(data);
+    }
+
+    @Override
+    public double getInverterVoltage() {
+        return ((double) getData(0)) / 10;
+    }
+
+    @Override
+    public double getInverterCurrent() {
+        return ((double) getData(1)) / 10;
+    }
+
+    @Override
+    public short getInverterOutputPower() {
+        return getData(2);
+    }
+
+    @Override
+    public double getInverterFrequency() {
+        return ((double) getData(3)) / 100;
+    }
+
+    @Override
+    public short getFeedInPower() {
+        return getData(32);
+    }
+
+    @Override
+    public double getPV1Voltage() {
+        return ((double) getData(4)) / 10;
+    }
+
+    @Override
+    public double getPV2Voltage() {
+        return ((double) getData(5)) / 10;
+    }
+
+    @Override
+    public double getPV1Current() {
+        return ((double) getData(6)) / 10;
+    }
+
+    @Override
+    public double getPV2Current() {
+        return ((double) getData(7)) / 10;
+    }
+
+    @Override
+    public short getPV1Power() {
+        return getData(8);
+    }
+
+    @Override
+    public short getPV2Power() {
+        return getData(9);
+    }
+
+    @Override
+    public double getBatteryVoltage() {
+        return ((double) getData(14)) / 100;
+    }
+
+    @Override
+    public double getBatteryCurrent() {
+        return ((double) getData(15)) / 100;
+    }
+
+    @Override
+    public short getBatteryPower() {
+        return getData(16);
+    }
+
+    @Override
+    public short getBatteryTemperature() {
+        return getData(17);
+    }
+
+    @Override
+    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/local/X3HybridG4InverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/X3HybridG4InverterData.java
new file mode 100644 (file)
index 0000000..b613fb4
--- /dev/null
@@ -0,0 +1,228 @@
+/**
+ * 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.local;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
+
+/**
+ * The {@link X3HybridG4InverterData} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class X3HybridG4InverterData extends CommonLocalInverterData {
+
+    public X3HybridG4InverterData(LocalConnectRawDataBean data) {
+        super(data);
+    }
+
+    // Inverter data
+
+    @Override
+    public double getVoltagePhase1() {
+        return ((double) getData(0)) / 10;
+    }
+
+    @Override
+    public double getVoltagePhase2() {
+        return ((double) getData(1)) / 10;
+    }
+
+    @Override
+    public double getVoltagePhase3() {
+        return ((double) getData(2)) / 10;
+    }
+
+    @Override
+    public double getCurrentPhase1() {
+        return ((double) getData(3)) / 10;
+    }
+
+    @Override
+    public double getCurrentPhase2() {
+        return ((double) getData(4)) / 10;
+    }
+
+    @Override
+    public double getCurrentPhase3() {
+        return ((double) getData(5)) / 10;
+    }
+
+    @Override
+    public short getOutputPowerPhase1() {
+        return getData(6);
+    }
+
+    @Override
+    public short getOutputPowerPhase2() {
+        return getData(7);
+    }
+
+    @Override
+    public short getOutputPowerPhase3() {
+        return getData(8);
+    }
+
+    @Override
+    public short getTotalOutputPower() {
+        return getData(9);
+    }
+
+    @Override
+    public double getPV1Voltage() {
+        return ((double) getData(10)) / 10;
+    }
+
+    @Override
+    public double getPV2Voltage() {
+        return ((double) getData(11)) / 10;
+    }
+
+    @Override
+    public double getPV1Current() {
+        return ((double) getData(12)) / 10;
+    }
+
+    @Override
+    public double getPV2Current() {
+        return ((double) getData(13)) / 10;
+    }
+
+    @Override
+    public short getPV1Power() {
+        return getData(14);
+    }
+
+    @Override
+    public short getPV2Power() {
+        return getData(15);
+    }
+
+    @Override
+    public double getFrequencyPhase1() {
+        return ((double) getData(16)) / 100;
+    }
+
+    @Override
+    public double getFrequencyPhase2() {
+        return ((double) getData(17)) / 100;
+    }
+
+    @Override
+    public double getFrequencyPhase3() {
+        return ((double) getData(18)) / 100;
+    }
+
+    // Battery
+
+    @Override
+    public double getBatteryVoltage() {
+        return ((double) getData(39)) / 100;
+    }
+
+    @Override
+    public double getBatteryCurrent() {
+        return ((double) getData(40)) / 100;
+    }
+
+    @Override
+    public short getBatteryPower() {
+        return getData(41);
+    }
+
+    @Override
+    public short getBatteryTemperature() {
+        return getData(105);
+    }
+
+    @Override
+    public short getBatteryLevel() {
+        return getData(103);
+    }
+
+    // Feed in power
+
+    @Override
+    public short getFeedInPower() {
+        return (short) (getData(34) - getData(35));
+    }
+
+    // Totals
+
+    @Override
+    public short getPowerUsage() {
+        return getData(47);
+    }
+
+    @Override
+    public double getTotalEnergy() {
+        return ((double) getData(68)) / 10;
+    }
+
+    @Override
+    public short getTotalBatteryDischargeEnergy() {
+        return getData(74);
+    }
+
+    @Override
+    public short getTotalBatteryChargeEnergy() {
+        return getData(76);
+    }
+
+    @Override
+    public double getTotalPVEnergy() {
+        return ((double) getData(80)) / 10;
+    }
+
+    @Override
+    public short getTotalFeedInEnergy() {
+        return getData(86);
+    }
+
+    @Override
+    public double getTotalConsumption() {
+        return ((double) getData(88)) / 10;
+    }
+
+    @Override
+    public double getTodayEnergy() {
+        return ((double) getData(82)) / 10;
+    }
+
+    @Override
+    public double getTodayFeedInEnergy() {
+        return ((double) getData(90)) / 100;
+    }
+
+    @Override
+    public double getTodayConsumption() {
+        return ((double) getData(92)) / 100;
+    }
+
+    @Override
+    public double getTodayBatteryDischargeEnergy() {
+        return ((double) getData(78)) / 10;
+    }
+
+    @Override
+    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/local/X3MicOrProG2InverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/X3MicOrProG2InverterData.java
new file mode 100644 (file)
index 0000000..bd0e03c
--- /dev/null
@@ -0,0 +1,153 @@
+/**
+ * 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.local;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
+
+/**
+ * The {@link X3HybridG4InverterData} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Henrik Tóth - Initial contribution
+ *         (based on X1/X3 G4 parser from Konstantin Polihronov)
+ */
+@NonNullByDefault
+public class X3MicOrProG2InverterData extends CommonLocalInverterData {
+
+    public X3MicOrProG2InverterData(LocalConnectRawDataBean data) {
+        super(data);
+    }
+
+    // Inverter data
+
+    @Override
+    public double getVoltagePhase1() {
+        return ((double) getData(0)) / 10;
+    }
+
+    @Override
+    public double getVoltagePhase2() {
+        return ((double) getData(1)) / 10;
+    }
+
+    @Override
+    public double getVoltagePhase3() {
+        return ((double) getData(2)) / 10;
+    }
+
+    @Override
+    public double getCurrentPhase1() {
+        return ((double) getData(3)) / 10;
+    }
+
+    @Override
+    public double getCurrentPhase2() {
+        return ((double) getData(4)) / 10;
+    }
+
+    @Override
+    public double getCurrentPhase3() {
+        return ((double) getData(5)) / 10;
+    }
+
+    @Override
+    public short getOutputPowerPhase1() {
+        return getData(6);
+    }
+
+    @Override
+    public short getOutputPowerPhase2() {
+        return getData(7);
+    }
+
+    @Override
+    public short getOutputPowerPhase3() {
+        return getData(8);
+    }
+
+    @Override
+    public double getPV1Voltage() {
+        return ((double) getData(9)) / 10;
+    }
+
+    @Override
+    public double getPV2Voltage() {
+        return ((double) getData(10)) / 10;
+    }
+
+    @Override
+    public double getPV1Current() {
+        return ((double) getData(12)) / 10;
+    }
+
+    @Override
+    public double getPV2Current() {
+        return ((double) getData(13)) / 10;
+    }
+
+    @Override
+    public short getPV1Power() {
+        return getData(15);
+    }
+
+    @Override
+    public short getPV2Power() {
+        return getData(16);
+    }
+
+    @Override
+    public double getFrequencyPhase1() {
+        return ((double) getData(18)) / 100;
+    }
+
+    @Override
+    public double getFrequencyPhase2() {
+        return ((double) getData(19)) / 100;
+    }
+
+    @Override
+    public double getFrequencyPhase3() {
+        return ((double) getData(20)) / 100;
+    }
+
+    @Override
+    public short getInverterWorkModeCode() {
+        return getData(21);
+    }
+
+    @Override
+    public double getTotalEnergy() {
+        return ((double) getData(22)) / 10;
+    }
+
+    @Override
+    public double getTodayEnergy() {
+        return ((double) getData(24)) / 10;
+    }
+
+    @Override
+    public short getInverterTemperature1() {
+        return getData(26);
+    }
+
+    @Override
+    public short getInverterTemperature2() {
+        return getData(27);
+    }
+
+    @Override
+    public short getTotalOutputPower() {
+        return getData(78);
+    }
+}
diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/RawDataParser.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/RawDataParser.java
new file mode 100644 (file)
index 0000000..d45cc08
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * 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.local.parsers;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+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
+ * is common for all inverters.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public interface RawDataParser {
+
+    LocalInverterData getData(LocalConnectRawDataBean bean);
+
+    Set<String> getSupportedChannels();
+}
diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/X1HybridG4DataParser.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/X1HybridG4DataParser.java
new file mode 100644 (file)
index 0000000..dc6ac43
--- /dev/null
@@ -0,0 +1,50 @@
+/**
+ * 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.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.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
+ * X1 Hybrid G4 inverter.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class X1HybridG4DataParser implements RawDataParser {
+
+    private static final Set<String> X1_HYBRID_G4_SUPPORTED_CHANNELS = Set.of(CHANNEL_INVERTER_PV1_POWER,
+            CHANNEL_INVERTER_PV1_VOLTAGE, CHANNEL_INVERTER_PV1_CURRENT, CHANNEL_INVERTER_PV2_POWER,
+            CHANNEL_INVERTER_PV2_VOLTAGE, CHANNEL_INVERTER_PV2_CURRENT, CHANNEL_INVERTER_PV_TOTAL_POWER,
+            CHANNEL_INVERTER_PV_TOTAL_CURRENT, CHANNEL_BATTERY_POWER, CHANNEL_BATTERY_VOLTAGE, CHANNEL_BATTERY_CURRENT,
+            CHANNEL_BATTERY_TEMPERATURE, CHANNEL_BATTERY_STATE_OF_CHARGE, CHANNEL_FEED_IN_POWER, CHANNEL_TIMESTAMP,
+            CHANNEL_RAW_DATA, CHANNEL_INVERTER_OUTPUT_POWER, CHANNEL_INVERTER_OUTPUT_CURRENT,
+            CHANNEL_INVERTER_OUTPUT_VOLTAGE, CHANNEL_INVERTER_OUTPUT_FREQUENCY, CHANNEL_INVERTER_WORKMODE);
+
+    @Override
+    public LocalInverterData getData(LocalConnectRawDataBean rawData) {
+        return new X1HybridG4InverterData(rawData);
+    }
+
+    @Override
+    public Set<String> getSupportedChannels() {
+        return X1_HYBRID_G4_SUPPORTED_CHANNELS;
+    }
+}
diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/X3HybridG4DataParser.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/X3HybridG4DataParser.java
new file mode 100644 (file)
index 0000000..781a5de
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * 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.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.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
+ * X3 Hybrid G4 inverter.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class X3HybridG4DataParser implements RawDataParser {
+
+    private static final Set<String> X3_HYBRID_G4_SUPPORTED_CHANNELS = Set.of(CHANNEL_INVERTER_PV1_POWER,
+            CHANNEL_INVERTER_PV1_VOLTAGE, CHANNEL_INVERTER_PV1_CURRENT, CHANNEL_INVERTER_PV2_POWER,
+            CHANNEL_INVERTER_PV2_VOLTAGE, CHANNEL_INVERTER_PV2_CURRENT, CHANNEL_INVERTER_PV_TOTAL_POWER,
+            CHANNEL_INVERTER_PV_TOTAL_CURRENT, CHANNEL_BATTERY_POWER, CHANNEL_BATTERY_VOLTAGE, CHANNEL_BATTERY_CURRENT,
+            CHANNEL_BATTERY_TEMPERATURE, CHANNEL_BATTERY_STATE_OF_CHARGE, CHANNEL_FEED_IN_POWER, CHANNEL_TIMESTAMP,
+            CHANNEL_RAW_DATA, CHANNEL_INVERTER_OUTPUT_POWER_PHASE1, CHANNEL_INVERTER_OUTPUT_POWER_PHASE2,
+            CHANNEL_INVERTER_OUTPUT_POWER_PHASE3, CHANNEL_INVERTER_TOTAL_OUTPUT_POWER,
+            CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1, CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2,
+            CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3, CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1,
+            CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2, CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3,
+            CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2,
+            CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, CHANNEL_POWER_USAGE, CHANNEL_TOTAL_ENERGY,
+            CHANNEL_TOTAL_BATTERY_CHARGE_ENERGY, CHANNEL_TOTAL_PV_ENERGY, CHANNEL_TOTAL_CONSUMPTION,
+            CHANNEL_TODAY_ENERGY, CHANNEL_TODAY_FEED_IN_ENERGY, CHANNEL_TODAY_CONSUMPTION,
+            CHANNEL_TODAY_BATTERY_CHARGE_ENERGY, CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY, CHANNEL_INVERTER_WORKMODE);
+
+    @Override
+    public LocalInverterData getData(LocalConnectRawDataBean rawData) {
+        return new X3HybridG4InverterData(rawData);
+    }
+
+    @Override
+    public Set<String> getSupportedChannels() {
+        return X3_HYBRID_G4_SUPPORTED_CHANNELS;
+    }
+}
diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/X3MicOrProG2DataParser.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/local/parsers/X3MicOrProG2DataParser.java
new file mode 100644 (file)
index 0000000..4dec856
--- /dev/null
@@ -0,0 +1,55 @@
+/**
+ * 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.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.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
+ * X3 Mic / Pro G2 inverter.
+ *
+ * @author Henrik Tóth - Initial contribution
+ *         (based on X1/X3 G4 parser from Konstantin Polihronov)
+ */
+@NonNullByDefault
+public class X3MicOrProG2DataParser implements RawDataParser {
+
+    private static final Set<String> X3_MIC_OR_PRO_G2_SUPPORTED_CHANNELS = Set.of(
+            CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2,
+            CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3, CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1,
+            CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2, CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3,
+            CHANNEL_INVERTER_OUTPUT_POWER_PHASE1, CHANNEL_INVERTER_OUTPUT_POWER_PHASE2,
+            CHANNEL_INVERTER_OUTPUT_POWER_PHASE3, CHANNEL_INVERTER_PV1_VOLTAGE, CHANNEL_INVERTER_PV2_VOLTAGE,
+            CHANNEL_INVERTER_PV1_CURRENT, CHANNEL_INVERTER_PV2_CURRENT, CHANNEL_INVERTER_PV1_POWER,
+            CHANNEL_INVERTER_PV2_POWER, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1,
+            CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, CHANNEL_TOTAL_ENERGY,
+            CHANNEL_TODAY_ENERGY, CHANNEL_INVERTER_TOTAL_OUTPUT_POWER, CHANNEL_INVERTER_TEMPERATURE1,
+            CHANNEL_INVERTER_TEMPERATURE2, CHANNEL_INVERTER_WORKMODE, CHANNEL_RAW_DATA);
+
+    @Override
+    public LocalInverterData getData(LocalConnectRawDataBean rawData) {
+        return new X3MicOrProG2InverterData(rawData);
+    }
+
+    @Override
+    public Set<String> getSupportedChannels() {
+        return X3_MIC_OR_PRO_G2_SUPPORTED_CHANNELS;
+    }
+}
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/parsers/RawDataParser.java
deleted file mode 100644 (file)
index 8fa85a2..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * 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.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;
-
-/**
- * The {@link RawDataParser} declares generic parser implementation that parses raw data to generic inverter data which
- * is common for all inverters.
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public interface RawDataParser {
-
-    InverterData getData(LocalConnectRawDataBean bean);
-
-    Set<String> 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/parsers/X1HybridG4DataParser.java
deleted file mode 100644 (file)
index 538e937..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * 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.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;
-
-/**
- * The {@link SinglePhaseDataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the
- * X1 Hybrid G4 inverter.
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public class X1HybridG4DataParser implements RawDataParser {
-
-    private static final Set<String> X1_HYBRID_G4_SUPPORTED_CHANNELS = Set.of(CHANNEL_INVERTER_PV1_POWER,
-            CHANNEL_INVERTER_PV1_VOLTAGE, CHANNEL_INVERTER_PV1_CURRENT, CHANNEL_INVERTER_PV2_POWER,
-            CHANNEL_INVERTER_PV2_VOLTAGE, CHANNEL_INVERTER_PV2_CURRENT, CHANNEL_INVERTER_PV_TOTAL_POWER,
-            CHANNEL_INVERTER_PV_TOTAL_CURRENT, CHANNEL_BATTERY_POWER, CHANNEL_BATTERY_VOLTAGE, CHANNEL_BATTERY_CURRENT,
-            CHANNEL_BATTERY_TEMPERATURE, CHANNEL_BATTERY_STATE_OF_CHARGE, CHANNEL_FEED_IN_POWER, CHANNEL_TIMESTAMP,
-            CHANNEL_RAW_DATA, CHANNEL_INVERTER_OUTPUT_POWER, CHANNEL_INVERTER_OUTPUT_CURRENT,
-            CHANNEL_INVERTER_OUTPUT_VOLTAGE, CHANNEL_INVERTER_OUTPUT_FREQUENCY, CHANNEL_INVERTER_WORKMODE);
-
-    @Override
-    public InverterData getData(LocalConnectRawDataBean rawData) {
-        return new X1HybridG4InverterData(rawData);
-    }
-
-    @Override
-    public Set<String> getSupportedChannels() {
-        return X1_HYBRID_G4_SUPPORTED_CHANNELS;
-    }
-}
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/parsers/X3HybridG4DataParser.java
deleted file mode 100644 (file)
index 7f0680a..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-/**
- * 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.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;
-
-/**
- * The {@link X3HybridG4DataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the
- * X3 Hybrid G4 inverter.
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public class X3HybridG4DataParser implements RawDataParser {
-
-    private static final Set<String> X3_HYBRID_G4_SUPPORTED_CHANNELS = Set.of(CHANNEL_INVERTER_PV1_POWER,
-            CHANNEL_INVERTER_PV1_VOLTAGE, CHANNEL_INVERTER_PV1_CURRENT, CHANNEL_INVERTER_PV2_POWER,
-            CHANNEL_INVERTER_PV2_VOLTAGE, CHANNEL_INVERTER_PV2_CURRENT, CHANNEL_INVERTER_PV_TOTAL_POWER,
-            CHANNEL_INVERTER_PV_TOTAL_CURRENT, CHANNEL_BATTERY_POWER, CHANNEL_BATTERY_VOLTAGE, CHANNEL_BATTERY_CURRENT,
-            CHANNEL_BATTERY_TEMPERATURE, CHANNEL_BATTERY_STATE_OF_CHARGE, CHANNEL_FEED_IN_POWER, CHANNEL_TIMESTAMP,
-            CHANNEL_RAW_DATA, CHANNEL_INVERTER_OUTPUT_POWER_PHASE1, CHANNEL_INVERTER_OUTPUT_POWER_PHASE2,
-            CHANNEL_INVERTER_OUTPUT_POWER_PHASE3, CHANNEL_INVERTER_TOTAL_OUTPUT_POWER,
-            CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1, CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2,
-            CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3, CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1,
-            CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2, CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3,
-            CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2,
-            CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, CHANNEL_POWER_USAGE, CHANNEL_TOTAL_ENERGY,
-            CHANNEL_TOTAL_BATTERY_CHARGE_ENERGY, CHANNEL_TOTAL_PV_ENERGY, CHANNEL_TOTAL_CONSUMPTION,
-            CHANNEL_TODAY_ENERGY, CHANNEL_TODAY_FEED_IN_ENERGY, CHANNEL_TODAY_CONSUMPTION,
-            CHANNEL_TODAY_BATTERY_CHARGE_ENERGY, CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY, CHANNEL_INVERTER_WORKMODE);
-
-    @Override
-    public InverterData getData(LocalConnectRawDataBean rawData) {
-        return new X3HybridG4InverterData(rawData);
-    }
-
-    @Override
-    public Set<String> getSupportedChannels() {
-        return X3_HYBRID_G4_SUPPORTED_CHANNELS;
-    }
-}
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/parsers/X3MicOrProG2DataParser.java
deleted file mode 100644 (file)
index 6a1660b..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * 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.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;
-
-/**
- * The {@link X3MicOrProG2DataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the
- * X3 Mic / Pro G2 inverter.
- *
- * @author Henrik Tóth - Initial contribution
- *         (based on X1/X3 G4 parser from Konstantin Polihronov)
- */
-@NonNullByDefault
-public class X3MicOrProG2DataParser implements RawDataParser {
-
-    private static final Set<String> X3_MIC_OR_PRO_G2_SUPPORTED_CHANNELS = Set.of(
-            CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2,
-            CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3, CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1,
-            CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2, CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3,
-            CHANNEL_INVERTER_OUTPUT_POWER_PHASE1, CHANNEL_INVERTER_OUTPUT_POWER_PHASE2,
-            CHANNEL_INVERTER_OUTPUT_POWER_PHASE3, CHANNEL_INVERTER_PV1_VOLTAGE, CHANNEL_INVERTER_PV2_VOLTAGE,
-            CHANNEL_INVERTER_PV1_CURRENT, CHANNEL_INVERTER_PV2_CURRENT, CHANNEL_INVERTER_PV1_POWER,
-            CHANNEL_INVERTER_PV2_POWER, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1,
-            CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, CHANNEL_TOTAL_ENERGY,
-            CHANNEL_TODAY_ENERGY, CHANNEL_INVERTER_TOTAL_OUTPUT_POWER, CHANNEL_INVERTER_TEMPERATURE1,
-            CHANNEL_INVERTER_TEMPERATURE2, CHANNEL_INVERTER_WORKMODE, CHANNEL_RAW_DATA);
-
-    @Override
-    public InverterData getData(LocalConnectRawDataBean rawData) {
-        return new X3MicOrProG2InverterData(rawData);
-    }
-
-    @Override
-    public Set<String> getSupportedChannels() {
-        return X3_MIC_OR_PRO_G2_SUPPORTED_CHANNELS;
-    }
-}
index 7344060f39424d34f8f6ba6032ebc04bcf536dba..e5f3ee6b0cd245cb14dbeb588f1dd23c5ad99a39 100644 (file)
@@ -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.
index 472c9eea76d003bae02a3919840d64573c1ac69e..2267461b2e7acbc445612d4b2d0711c8fa5218cb 100644 (file)
                        </options>
                </state>
        </channel-type>
+       <channel-type id="inverter-workmode-cloud">
+               <item-type>String</item-type>
+               <label>Inverter Workmode</label>
+               <description>Inverter Workmode</description>
+               <state pattern="%s" readOnly="true">
+                       <options>
+                               <option value="100">Waiting</option>
+                               <option value="101">Checking</option>
+                               <option value="102">Normal</option>
+                               <option value="103">Fault</option>
+                               <option value="104">Permanent Fault</option>
+                               <option value="105">Updating</option>
+                               <option value="106">EPS Check</option>
+                               <option value="107">EPS Normal</option>
+                               <option value="108">Self Test</option>
+                               <option value="109">Idle</option>
+                               <option value="110">Standby</option>
+                               <option value="111">PV Wake-up Battery</option>
+                               <option value="112">GEN Check</option>
+                               <option value="113">GEN Run</option>
+                       </options>
+               </state>
+       </channel-type>
        <channel-type id="last-retrieve-time-stamp">
                <item-type>DateTime</item-type>
                <label>Last Retrieve Time Stamp</label>
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 (file)
index 0000000..fb8a15e
--- /dev/null
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="solax"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <thing-type id="cloud-connect-inverter">
+
+               <label>Cloud Connect Inverter</label>
+               <description>The inverter representation that is retrieved from Solax cloud API</description>
+
+               <channels>
+                       <channel id="inverter-output-power" typeId="system.electric-power">
+                               <label>Inverter Input/Output Power</label>
+                               <description>Power to/from the inverter</description>
+                       </channel>
+                       <channel id="pv1-power" typeId="system.electric-power">
+                               <label>PV 1 Power</label>
+                               <description>Electric power of PV String 1</description>
+                       </channel>
+                       <channel id="pv2-power" typeId="system.electric-power">
+                               <label>PV 2 Power</label>
+                               <description>Electric power of PV String 2</description>
+                       </channel>
+                       <channel id="pv3-power" typeId="system.electric-power">
+                               <label>PV 3 Power</label>
+                               <description>Electric power of PV String 3</description>
+                       </channel>
+                       <channel id="pv4-power" typeId="system.electric-power">
+                               <label>PV 4 Power</label>
+                               <description>Electric power of PV String 4</description>
+                       </channel>
+                       <channel id="pv-total-power" typeId="system.electric-power">
+                               <label>PV Total Power</label>
+                               <description>The sum of PV powers from all PV strings</description>
+                       </channel>
+                       <channel id="battery-level" typeId="system.battery-level">
+                               <label>Battery Level</label>
+                               <description>The battery state of charge in percent</description>
+                       </channel>
+                       <channel id="battery-power" typeId="system.electric-power">
+                               <label>Battery Power</label>
+                               <description>Power to/from the battery</description>
+                       </channel>
+                       <channel id="feed-in-power" typeId="system.electric-power">
+                               <label>Feed-In Power</label>
+                               <description>Power to/from the electricity network.</description>
+                       </channel>
+                       <channel id="total-feed-in-energy" typeId="system.electric-energy">
+                               <label>Total Feed-In Energy</label>
+                               <description>Total energy feed-in to the electricity network.</description>
+                       </channel>
+                       <channel id="total-consumption" typeId="system.electric-energy">
+                               <label>Total Consumed Energy</label>
+                               <description>Total Energy consumed from the electricity network.</description>
+                       </channel>
+                       <channel id="inverter-meter2-power" typeId="system.electric-power">
+                               <label>Meter2 Power</label>
+                               <description>Inverter power on meter2.</description>
+                       </channel>
+                       <channel id="inverter-eps-power-r" typeId="system.electric-power">
+                               <label>EPS power R</label>
+                               <description>Inverter AC EPS power R</description>
+                       </channel>
+                       <channel id="inverter-eps-power-s" typeId="system.electric-power">
+                               <label>EPS power S</label>
+                               <description>Inverter AC EPS power S</description>
+                       </channel>
+                       <channel id="inverter-eps-power-t" typeId="system.electric-power">
+                               <label>EPS power T</label>
+                               <description>Inverter AC EPS power T</description>
+                       </channel>
+
+                       <channel id="today-energy" typeId="system.electric-energy">
+                               <label>Yield today</label>
+                               <description>Inverter output energy for the day</description>
+                       </channel>
+                       <channel id="total-energy" typeId="system.electric-energy">
+                               <label>Yield total</label>
+                               <description>Total inverter output energy</description>
+                       </channel>
+
+                       <channel id="last-update-time" typeId="last-retrieve-time-stamp"/>
+
+                       <channel id="inverter-workmode" typeId="inverter-workmode-cloud">
+                               <label>Inverter Workmode</label>
+                               <description>Inverter Workmode</description>
+                       </channel>
+
+                       <channel id="raw-data" typeId="raw-data-type"/>
+               </channels>
+
+               <config-description>
+                       <parameter name="refreshInterval" type="integer" min="9" max="600">
+                               <label>Refresh Interval</label>
+                               <description>Refresh interval in seconds. (Cloud API is limited to max 10 calls per minute and 10000 times per day)</description>
+                               <default>30</default>
+                       </parameter>
+                       <parameter name="password" type="text" required="true">
+                               <label>Password</label>
+                               <description>Password for accessing cloud API (the serial number of the Wi-Fi module)</description>
+                               <context>password</context>
+                       </parameter>
+                       <parameter name="token" type="text" required="true">
+                               <label>Token</label>
+                               <description>Token to access the Solax cloud API</description>
+                               <context>password</context>
+                       </parameter>
+               </config-description>
+       </thing-type>
+</thing:thing-descriptions>
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/TestX1HybridG4Parser.java
deleted file mode 100644 (file)
index c8c8c3a..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-/**
- * 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;
-
-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.model.InverterType;
-import org.openhab.binding.solax.internal.model.parsers.RawDataParser;
-
-/**
- * The {@link TestX1HybridG4Parser} Simple test that tests for proper parsing against a real data from the inverter
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public class TestX1HybridG4Parser {
-
-    private static final String RAW_DATA = """
-            {
-                sn:SOME_SERIAL_NUMBER,
-                ver:3.008.10,
-                type:15,
-                Data:[
-                    2388,21,460,4998,4483,4483,10,1,487,65,
-                    2,59781,0,70,12180,500,605,33,99,12000,
-                    0,23159,0,57,100,0,39,4501,0,0,
-                    0,0,12,0,13240,0,63348,2,448,43,
-                    256,1314,900,0,350,311,279,33,33,279,1,1,652,0,708,1,65077,65535,65386,65535,0,0,0,0,0,0,0,0,0,0,0,0,65068,65535,4500,0,61036,65535,10,0,90,0,0,12,0,116,7,57,0,0,2320,0,110,0,0,0,0,0,0,12544,7440,5896,594,521,9252,0,0,0,0,0,1,1201,0,0,3342,3336,7296,54,21302,14389,18753,12852,16692,12355,13618,21302,14389,18753,12852,16692,12355,13618,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1025,4609,1026,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
-                Information:[7.500,15,H4752TI1063020,8,1.24,0.00,1.21,1.03,0.00,1]}
-            """;
-
-    @Test
-    public void testParser() {
-        LocalConnectRawDataBean bean = LocalConnectRawDataBean.fromJson(RAW_DATA);
-        int type = bean.getType();
-        InverterType inverterType = InverterType.fromIndex(type);
-        assertEquals(InverterType.X1_HYBRID_G4, inverterType, "Inverter type not recognized properly");
-
-        RawDataParser parser = inverterType.getParser();
-        assertNotNull(parser);
-
-        InverterData data = parser.getData(bean);
-        assertEquals("SOME_SERIAL_NUMBER", data.getWifiSerial());
-        assertEquals("3.008.10", data.getWifiVersion());
-
-        assertEquals(238.8, data.getInverterVoltage()); // [0]
-        assertEquals(2.1, data.getInverterCurrent()); // [1]
-        assertEquals(460, data.getInverterOutputPower()); // [2]
-        assertEquals(49.98, data.getInverterFrequency()); // [3]
-
-        assertEquals(448.3, data.getPV1Voltage()); // [4]
-        assertEquals(448.3, data.getPV2Voltage()); // [5]
-        assertEquals(1, data.getPV1Current()); // [6]
-        assertEquals(0.1, data.getPV2Current()); // [7]
-        assertEquals(487, data.getPV1Power()); // [8]
-        assertEquals(65, data.getPV2Power()); // [9]
-
-        assertEquals(2, data.getInverterWorkModeCode()); // [10]
-        assertEquals("2", data.getInverterWorkMode()); // [10]
-
-        assertEquals(121.8, data.getBatteryVoltage()); // [14]
-        assertEquals(5, data.getBatteryCurrent()); // [15]
-        assertEquals(605, data.getBatteryPower()); // [16]
-        assertEquals(33, data.getBatteryTemperature()); // [17]
-        assertEquals(99, data.getBatteryLevel()); // [18]
-
-        assertEquals(12, data.getFeedInPower()); // [32]
-    }
-
-    // Yield_Today: Data[13] / 10,
-    // Yield_Total: read32BitUnsigned(Data[11], Data[12]) / 10,
-    // PowerDc1: Data[8],
-    // PowerDc2: Data[9],
-    // BAT_Power: read16BitSigned(Data[16]),
-    // feedInPower: read32BitSigned(Data[32], Data[33]),
-    // GridAPower: read16BitSigned(Data[2]),
-    // FeedInEnergy: read32BitUnsigned(Data[34], Data[35]) / 100,
-    // ConsumeEnergy: read32BitUnsigned(Data[36], Data[37]) / 100,
-    // RunMode: Data[10],
-    // EPSAPower: read16BitSigned(Data[28]),
-    // Vdc1: Data[4] / 10,
-    // Vdc2: Data[5] / 10,
-    // Idc1: Data[6] / 10,
-    // Idc2: Data[7] / 10,
-    // EPSAVoltage: Data[29] / 10,
-    // EPSACurrent: read16BitSigned(Data[30]) / 10,
-    // BatteryCapacity: Data[18],
-    // BatteryVoltage: Data[14] / 100,
-    // BatteryTemperature: read16BitSigned(Data[17]),
-    // GridAVoltage: Data[0] / 10,
-    // GridACurrent: read16BitSigned(Data[1]) / 10,
-    // FreqacA: Data[3] / 100,
-}
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/TestX3HybridG4Parser.java
deleted file mode 100644 (file)
index 8c02197..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-/**
- * 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;
-
-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.model.InverterType;
-import org.openhab.binding.solax.internal.model.parsers.RawDataParser;
-
-/**
- * The {@link TestX3HybridG4Parser} simple test that tests for proper parsing against a real data from the inverter
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public class TestX3HybridG4Parser {
-
-    String rawData = """
-            {
-                sn:XYZ,
-                ver:3.005.01,
-                type:14,Data:[
-                    2316,2329,2315,18,18,18,372,363,365,1100,
-                    12,23,34,45,56,67,4996,4996,4996,2,
-                    0,0,0,0,0,0,0,0,0,0,
-                    0,0,0,1,65494,65535,0,0,0,31330,
-                    320,1034,3078,1,44,1100,256,1294,0,0,
-                    7445,5895,100,0,38,0,0,0,0,0,
-                    0,0,0,0,0,0,0,0,505,0,
-                    396,0,0,0,102,0,142,0,62,110,
-                    570,0,463,0,0,0,1925,0,369,0,
-                    506,1925,304,309,0,0,0,0,0,0,
-                    0,0,0,45,1,59,1,34,54,256,
-                    3504,2400,300,300,295,276,33,33,2,1620,779,15163,15163,14906,0,0,0,3270,3264,45581,0,20564,12339,18753,12353,18742,12356,13625,20564,12339,18754,12866,18743,14151,13104,20564,12339,18754,12866,18743,14151,12592,20564,12339,18754,12865,18738,12871,13620,0,0,0,0,0,0,0,1025,8195,769,259,0,31460,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
-                Information:[12.000,14,XY,8,1.23,0.00,1.24,1.09,0.00,1]
-             }
-            """;
-
-    @Test
-    public void testParser() {
-        LocalConnectRawDataBean bean = LocalConnectRawDataBean.fromJson(rawData);
-        int type = bean.getType();
-        InverterType inverterType = InverterType.fromIndex(type);
-        assertEquals(InverterType.X3_HYBRID_G4, inverterType, "Inverter type not recognized properly");
-
-        RawDataParser parser = inverterType.getParser();
-        assertNotNull(parser);
-
-        InverterData data = parser.getData(bean);
-        assertEquals("XYZ", data.getWifiSerial());
-        assertEquals("3.005.01", data.getWifiVersion());
-
-        assertEquals(231.6, data.getVoltagePhase1()); // [0]
-        assertEquals(232.9, data.getVoltagePhase2()); // [1]
-        assertEquals(231.5, data.getVoltagePhase3()); // [2]
-
-        assertEquals(1.8, data.getCurrentPhase1()); // [3]
-        assertEquals(1.8, data.getCurrentPhase2()); // [4]
-        assertEquals(1.8, data.getCurrentPhase3()); // [5]
-
-        assertEquals(372, data.getOutputPowerPhase1()); // [6]
-        assertEquals(363, data.getOutputPowerPhase2()); // [7]
-        assertEquals(365, data.getOutputPowerPhase3()); // [8]
-
-        assertEquals(1100, data.getTotalOutputPower()); // [9]
-
-        assertEquals(1.2, data.getPV1Voltage()); // [10]
-        assertEquals(2.3, data.getPV2Voltage()); // [11]
-        assertEquals(3.4, data.getPV1Current()); // [12]
-        assertEquals(4.5, data.getPV2Current()); // [13]
-        assertEquals(56, data.getPV1Power()); // [14]
-        assertEquals(67, data.getPV2Power()); // [15]
-
-        assertEquals(49.96, data.getFrequencyPhase1()); // [16]
-        assertEquals(49.96, data.getFrequencyPhase2()); // [17]
-        assertEquals(49.96, data.getFrequencyPhase3()); // [18]
-
-        assertEquals(2, data.getInverterWorkModeCode()); // [19]
-        assertEquals("2", data.getInverterWorkMode()); // [19]
-
-        assertEquals(-41, data.getFeedInPower()); // [34] - [35]
-
-        assertEquals(313.3, data.getBatteryVoltage()); // [39]
-        assertEquals(3.2, data.getBatteryCurrent()); // [40]
-        assertEquals(1034, data.getBatteryPower()); // [41]
-        assertEquals(45, data.getBatteryLevel()); // [103]
-        assertEquals(59, data.getBatteryTemperature()); // [105]
-
-        // Totals
-        assertEquals(1294, data.getPowerUsage()); // [47]
-        assertEquals(50.5, data.getTotalEnergy()); // [68]
-        assertEquals(102, data.getTotalBatteryDischargeEnergy()); // [74]
-        assertEquals(142, data.getTotalBatteryChargeEnergy()); // [76]
-        assertEquals(57, data.getTotalPVEnergy()); // [80]
-        assertEquals(1925, data.getTotalFeedInEnergy()); // [86]
-        assertEquals(36.9, data.getTotalConsumption()); // [88]
-        assertEquals(46.3, data.getTodayEnergy()); // [82] / 10
-        assertEquals(5.06, data.getTodayFeedInEnergy()); // [90] / 100
-        assertEquals(3.04, data.getTodayConsumption()); // [92] / 100
-        assertEquals(6.2, data.getTodayBatteryDischargeEnergy()); // [78] / 100
-        assertEquals(11, data.getTodayBatteryChargeEnergy()); // [79] / 100
-    }
-}
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/TestX3MicOrProG2Parser.java
deleted file mode 100644 (file)
index c3d353d..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-/**
- * 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;
-
-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.model.InverterType;
-import org.openhab.binding.solax.internal.model.parsers.RawDataParser;
-
-/**
- * The {@link TestX3HybridG4Parser} simple test that tests for proper parsing against a real data from the inverter
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public class TestX3MicOrProG2Parser {
-
-    String rawData = """
-            {
-                sn:XYZ,
-                ver:3.003.02,
-                type:16,Data:[
-                    2515,2449,2484,5,5,9,54,44,20,4080,4340,
-                    0,1,2,0,67,102,0,4999,4999,4999,2,19035,
-                    0,50,8000,5,9,
-                    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-                    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,
-                    0,0,0,0,0,0,0,0,120,40,1,6,5,0,6772,0,0,0,
-                    0,0,0,0,0,0,0,0,0,0,0,0
-                    ],
-                Information:[8.000,16,XY,8,1.15,0.00,1.11,1.01,0.00,1]
-             }
-            """;
-
-    @Test
-    public void testParser() {
-        LocalConnectRawDataBean bean = LocalConnectRawDataBean.fromJson(rawData);
-        int type = bean.getType();
-        InverterType inverterType = InverterType.fromIndex(type);
-        assertEquals(InverterType.X3_MIC_OR_PRO_G2, inverterType, "Inverter type not recognized properly");
-
-        RawDataParser parser = inverterType.getParser();
-        assertNotNull(parser);
-
-        InverterData data = parser.getData(bean);
-        assertEquals("XYZ", data.getWifiSerial());
-        assertEquals("3.003.02", data.getWifiVersion());
-
-        assertEquals(251.5, data.getVoltagePhase1()); // [0]
-        assertEquals(244.9, data.getVoltagePhase2()); // [1]
-        assertEquals(248.4, data.getVoltagePhase3()); // [2]
-
-        assertEquals(0.5, data.getCurrentPhase1()); // [3]
-        assertEquals(0.5, data.getCurrentPhase2()); // [4]
-        assertEquals(0.9, data.getCurrentPhase3()); // [5]
-
-        assertEquals(54, data.getOutputPowerPhase1()); // [6]
-        assertEquals(44, data.getOutputPowerPhase2()); // [7]
-        assertEquals(20, data.getOutputPowerPhase3()); // [8]
-
-        assertEquals(408, data.getPV1Voltage()); // [9]
-        assertEquals(434, data.getPV2Voltage()); // [10]
-        assertEquals(0.1, data.getPV1Current()); // [12]
-        assertEquals(0.2, data.getPV2Current()); // [13]
-        assertEquals(67, data.getPV1Power()); // [15]
-        assertEquals(102, data.getPV2Power()); // [16]
-
-        assertEquals(49.99, data.getFrequencyPhase1()); // [18]
-        assertEquals(49.99, data.getFrequencyPhase2()); // [19]
-        assertEquals(49.99, data.getFrequencyPhase3()); // [20]
-
-        assertEquals(2, data.getInverterWorkModeCode()); // [21]
-        assertEquals("2", data.getInverterWorkMode()); // [21]
-
-        assertEquals(5, data.getInverterTemperature1()); // [26]
-        assertEquals(9, data.getInverterTemperature2()); // [27]
-
-        assertEquals(120, data.getTotalOutputPower()); // [78]
-
-        assertEquals(1903.5, data.getTotalEnergy()); // [22]
-        assertEquals(5.0, data.getTodayEnergy()); // [24]
-    }
-}
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 (file)
index 0000000..7dfd577
--- /dev/null
@@ -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/local/TestX1HybridG4Parser.java b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/local/TestX1HybridG4Parser.java
new file mode 100644 (file)
index 0000000..a2dcc34
--- /dev/null
@@ -0,0 +1,107 @@
+/**
+ * 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.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.local.LocalConnectRawDataBean;
+import org.openhab.binding.solax.internal.model.InverterType;
+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
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class TestX1HybridG4Parser {
+
+    private static final String RAW_DATA = """
+            {
+                sn:SOME_SERIAL_NUMBER,
+                ver:3.008.10,
+                type:15,
+                Data:[
+                    2388,21,460,4998,4483,4483,10,1,487,65,
+                    2,59781,0,70,12180,500,605,33,99,12000,
+                    0,23159,0,57,100,0,39,4501,0,0,
+                    0,0,12,0,13240,0,63348,2,448,43,
+                    256,1314,900,0,350,311,279,33,33,279,1,1,652,0,708,1,65077,65535,65386,65535,0,0,0,0,0,0,0,0,0,0,0,0,65068,65535,4500,0,61036,65535,10,0,90,0,0,12,0,116,7,57,0,0,2320,0,110,0,0,0,0,0,0,12544,7440,5896,594,521,9252,0,0,0,0,0,1,1201,0,0,3342,3336,7296,54,21302,14389,18753,12852,16692,12355,13618,21302,14389,18753,12852,16692,12355,13618,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1025,4609,1026,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
+                Information:[7.500,15,H4752TI1063020,8,1.24,0.00,1.21,1.03,0.00,1]}
+            """;
+
+    @Test
+    public void testParser() {
+        LocalConnectRawDataBean bean = LocalConnectRawDataBean.fromJson(RAW_DATA);
+        int type = bean.getType();
+        InverterType inverterType = InverterType.fromIndex(type);
+        assertEquals(InverterType.X1_HYBRID_G4, inverterType, "Inverter type not recognized properly");
+
+        RawDataParser parser = inverterType.getParser();
+        assertNotNull(parser);
+
+        LocalInverterData data = parser.getData(bean);
+        assertEquals("SOME_SERIAL_NUMBER", data.getWifiSerial());
+        assertEquals("3.008.10", data.getWifiVersion());
+
+        assertEquals(238.8, data.getInverterVoltage()); // [0]
+        assertEquals(2.1, data.getInverterCurrent()); // [1]
+        assertEquals(460, data.getInverterOutputPower()); // [2]
+        assertEquals(49.98, data.getInverterFrequency()); // [3]
+
+        assertEquals(448.3, data.getPV1Voltage()); // [4]
+        assertEquals(448.3, data.getPV2Voltage()); // [5]
+        assertEquals(1, data.getPV1Current()); // [6]
+        assertEquals(0.1, data.getPV2Current()); // [7]
+        assertEquals(487, data.getPV1Power()); // [8]
+        assertEquals(65, data.getPV2Power()); // [9]
+
+        assertEquals(2, data.getInverterWorkModeCode()); // [10]
+        assertEquals("2", data.getInverterWorkMode()); // [10]
+
+        assertEquals(121.8, data.getBatteryVoltage()); // [14]
+        assertEquals(5, data.getBatteryCurrent()); // [15]
+        assertEquals(605, data.getBatteryPower()); // [16]
+        assertEquals(33, data.getBatteryTemperature()); // [17]
+        assertEquals(99, data.getBatteryLevel()); // [18]
+
+        assertEquals(12, data.getFeedInPower()); // [32]
+    }
+
+    // Yield_Today: Data[13] / 10,
+    // Yield_Total: read32BitUnsigned(Data[11], Data[12]) / 10,
+    // PowerDc1: Data[8],
+    // PowerDc2: Data[9],
+    // BAT_Power: read16BitSigned(Data[16]),
+    // feedInPower: read32BitSigned(Data[32], Data[33]),
+    // GridAPower: read16BitSigned(Data[2]),
+    // FeedInEnergy: read32BitUnsigned(Data[34], Data[35]) / 100,
+    // ConsumeEnergy: read32BitUnsigned(Data[36], Data[37]) / 100,
+    // RunMode: Data[10],
+    // EPSAPower: read16BitSigned(Data[28]),
+    // Vdc1: Data[4] / 10,
+    // Vdc2: Data[5] / 10,
+    // Idc1: Data[6] / 10,
+    // Idc2: Data[7] / 10,
+    // EPSAVoltage: Data[29] / 10,
+    // EPSACurrent: read16BitSigned(Data[30]) / 10,
+    // BatteryCapacity: Data[18],
+    // BatteryVoltage: Data[14] / 100,
+    // BatteryTemperature: read16BitSigned(Data[17]),
+    // GridAVoltage: Data[0] / 10,
+    // GridACurrent: read16BitSigned(Data[1]) / 10,
+    // FreqacA: Data[3] / 100,
+}
diff --git a/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/local/TestX3HybridG4Parser.java b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/local/TestX3HybridG4Parser.java
new file mode 100644 (file)
index 0000000..951d725
--- /dev/null
@@ -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.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.local.LocalConnectRawDataBean;
+import org.openhab.binding.solax.internal.model.InverterType;
+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
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class TestX3HybridG4Parser {
+
+    String rawData = """
+            {
+                sn:XYZ,
+                ver:3.005.01,
+                type:14,Data:[
+                    2316,2329,2315,18,18,18,372,363,365,1100,
+                    12,23,34,45,56,67,4996,4996,4996,2,
+                    0,0,0,0,0,0,0,0,0,0,
+                    0,0,0,1,65494,65535,0,0,0,31330,
+                    320,1034,3078,1,44,1100,256,1294,0,0,
+                    7445,5895,100,0,38,0,0,0,0,0,
+                    0,0,0,0,0,0,0,0,505,0,
+                    396,0,0,0,102,0,142,0,62,110,
+                    570,0,463,0,0,0,1925,0,369,0,
+                    506,1925,304,309,0,0,0,0,0,0,
+                    0,0,0,45,1,59,1,34,54,256,
+                    3504,2400,300,300,295,276,33,33,2,1620,779,15163,15163,14906,0,0,0,3270,3264,45581,0,20564,12339,18753,12353,18742,12356,13625,20564,12339,18754,12866,18743,14151,13104,20564,12339,18754,12866,18743,14151,12592,20564,12339,18754,12865,18738,12871,13620,0,0,0,0,0,0,0,1025,8195,769,259,0,31460,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
+                Information:[12.000,14,XY,8,1.23,0.00,1.24,1.09,0.00,1]
+             }
+            """;
+
+    @Test
+    public void testParser() {
+        LocalConnectRawDataBean bean = LocalConnectRawDataBean.fromJson(rawData);
+        int type = bean.getType();
+        InverterType inverterType = InverterType.fromIndex(type);
+        assertEquals(InverterType.X3_HYBRID_G4, inverterType, "Inverter type not recognized properly");
+
+        RawDataParser parser = inverterType.getParser();
+        assertNotNull(parser);
+
+        LocalInverterData data = parser.getData(bean);
+        assertEquals("XYZ", data.getWifiSerial());
+        assertEquals("3.005.01", data.getWifiVersion());
+
+        assertEquals(231.6, data.getVoltagePhase1()); // [0]
+        assertEquals(232.9, data.getVoltagePhase2()); // [1]
+        assertEquals(231.5, data.getVoltagePhase3()); // [2]
+
+        assertEquals(1.8, data.getCurrentPhase1()); // [3]
+        assertEquals(1.8, data.getCurrentPhase2()); // [4]
+        assertEquals(1.8, data.getCurrentPhase3()); // [5]
+
+        assertEquals(372, data.getOutputPowerPhase1()); // [6]
+        assertEquals(363, data.getOutputPowerPhase2()); // [7]
+        assertEquals(365, data.getOutputPowerPhase3()); // [8]
+
+        assertEquals(1100, data.getTotalOutputPower()); // [9]
+
+        assertEquals(1.2, data.getPV1Voltage()); // [10]
+        assertEquals(2.3, data.getPV2Voltage()); // [11]
+        assertEquals(3.4, data.getPV1Current()); // [12]
+        assertEquals(4.5, data.getPV2Current()); // [13]
+        assertEquals(56, data.getPV1Power()); // [14]
+        assertEquals(67, data.getPV2Power()); // [15]
+
+        assertEquals(49.96, data.getFrequencyPhase1()); // [16]
+        assertEquals(49.96, data.getFrequencyPhase2()); // [17]
+        assertEquals(49.96, data.getFrequencyPhase3()); // [18]
+
+        assertEquals(2, data.getInverterWorkModeCode()); // [19]
+        assertEquals("2", data.getInverterWorkMode()); // [19]
+
+        assertEquals(-41, data.getFeedInPower()); // [34] - [35]
+
+        assertEquals(313.3, data.getBatteryVoltage()); // [39]
+        assertEquals(3.2, data.getBatteryCurrent()); // [40]
+        assertEquals(1034, data.getBatteryPower()); // [41]
+        assertEquals(45, data.getBatteryLevel()); // [103]
+        assertEquals(59, data.getBatteryTemperature()); // [105]
+
+        // Totals
+        assertEquals(1294, data.getPowerUsage()); // [47]
+        assertEquals(50.5, data.getTotalEnergy()); // [68]
+        assertEquals(102, data.getTotalBatteryDischargeEnergy()); // [74]
+        assertEquals(142, data.getTotalBatteryChargeEnergy()); // [76]
+        assertEquals(57, data.getTotalPVEnergy()); // [80]
+        assertEquals(1925, data.getTotalFeedInEnergy()); // [86]
+        assertEquals(36.9, data.getTotalConsumption()); // [88]
+        assertEquals(46.3, data.getTodayEnergy()); // [82] / 10
+        assertEquals(5.06, data.getTodayFeedInEnergy()); // [90] / 100
+        assertEquals(3.04, data.getTodayConsumption()); // [92] / 100
+        assertEquals(6.2, data.getTodayBatteryDischargeEnergy()); // [78] / 100
+        assertEquals(11, data.getTodayBatteryChargeEnergy()); // [79] / 100
+    }
+}
diff --git a/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/local/TestX3MicOrProG2Parser.java b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/local/TestX3MicOrProG2Parser.java
new file mode 100644 (file)
index 0000000..29846be
--- /dev/null
@@ -0,0 +1,97 @@
+/**
+ * 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.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.local.LocalConnectRawDataBean;
+import org.openhab.binding.solax.internal.model.InverterType;
+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
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class TestX3MicOrProG2Parser {
+
+    String rawData = """
+            {
+                sn:XYZ,
+                ver:3.003.02,
+                type:16,Data:[
+                    2515,2449,2484,5,5,9,54,44,20,4080,4340,
+                    0,1,2,0,67,102,0,4999,4999,4999,2,19035,
+                    0,50,8000,5,9,
+                    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+                    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,
+                    0,0,0,0,0,0,0,0,120,40,1,6,5,0,6772,0,0,0,
+                    0,0,0,0,0,0,0,0,0,0,0,0
+                    ],
+                Information:[8.000,16,XY,8,1.15,0.00,1.11,1.01,0.00,1]
+             }
+            """;
+
+    @Test
+    public void testParser() {
+        LocalConnectRawDataBean bean = LocalConnectRawDataBean.fromJson(rawData);
+        int type = bean.getType();
+        InverterType inverterType = InverterType.fromIndex(type);
+        assertEquals(InverterType.X3_MIC_OR_PRO_G2, inverterType, "Inverter type not recognized properly");
+
+        RawDataParser parser = inverterType.getParser();
+        assertNotNull(parser);
+
+        LocalInverterData data = parser.getData(bean);
+        assertEquals("XYZ", data.getWifiSerial());
+        assertEquals("3.003.02", data.getWifiVersion());
+
+        assertEquals(251.5, data.getVoltagePhase1()); // [0]
+        assertEquals(244.9, data.getVoltagePhase2()); // [1]
+        assertEquals(248.4, data.getVoltagePhase3()); // [2]
+
+        assertEquals(0.5, data.getCurrentPhase1()); // [3]
+        assertEquals(0.5, data.getCurrentPhase2()); // [4]
+        assertEquals(0.9, data.getCurrentPhase3()); // [5]
+
+        assertEquals(54, data.getOutputPowerPhase1()); // [6]
+        assertEquals(44, data.getOutputPowerPhase2()); // [7]
+        assertEquals(20, data.getOutputPowerPhase3()); // [8]
+
+        assertEquals(408, data.getPV1Voltage()); // [9]
+        assertEquals(434, data.getPV2Voltage()); // [10]
+        assertEquals(0.1, data.getPV1Current()); // [12]
+        assertEquals(0.2, data.getPV2Current()); // [13]
+        assertEquals(67, data.getPV1Power()); // [15]
+        assertEquals(102, data.getPV2Power()); // [16]
+
+        assertEquals(49.99, data.getFrequencyPhase1()); // [18]
+        assertEquals(49.99, data.getFrequencyPhase2()); // [19]
+        assertEquals(49.99, data.getFrequencyPhase3()); // [20]
+
+        assertEquals(2, data.getInverterWorkModeCode()); // [21]
+        assertEquals("2", data.getInverterWorkMode()); // [21]
+
+        assertEquals(5, data.getInverterTemperature1()); // [26]
+        assertEquals(9, data.getInverterTemperature2()); // [27]
+
+        assertEquals(120, data.getTotalOutputPower()); // [78]
+
+        assertEquals(1903.5, data.getTotalEnergy()); // [22]
+        assertEquals(5.0, data.getTodayEnergy()); // [24]
+    }
+}