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