[solax] Cloud connection support (#16124)

* Initial rearrangement of classes and cloud response in test

Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
Konstantin Polihronov 2024-02-14 17:22:23 +02:00 committed by Ciprian Pascu
parent 6982f7a058
commit 0f6ae6506e
31 changed files with 1590 additions and 228 deletions

View File

@ -14,7 +14,8 @@ In case the parsed information that comes with the binding out of the box differ
| Thing | Thing Type | Description |
|------------------------|------------|-------------------------------------------------------------------------------------|
| local-connect-inverter | Thing | This is model representation of inverter with all the data available as a channels |
| local-connect-inverter | Thing | An inverter representation with all the data available as a channels (directly retrieved from the wi-fi module |
| cloud-connect-inverter | Thing | An inverter representation with all the data available as a channels (retrieved from the Solax cloud API) |
Note: Channels may vary depending on the inverter type and the availability of information for parsing the raw data.
If you're missing a channel this means that it's not supported for your inverter type.
@ -66,13 +67,13 @@ If you're missing a channel this means that it's not supported for your inverter
### Battery channels
| Channel | Type | Description |
|---------------------------|----------------------------|------------------------------------------------------------------------------------------------|
| battery-power | Number:Power | The power to / from battery (negative means power is pulled from battery and vice-versa) [W] |
| battery-current | Number:ElectricCurrent | The current to / from battery (negative means power is pulled from battery and vice-versa) [A] |
| battery-voltage | Number:ElectricPotential | The voltage of the battery [V] |
| battery-temperature | Number:Temperature | The temperature of the battery [C/F] |
| battery-state-of-charge | Number | The state of charge of the battery [%] |
| Channel | Type | Description |
|---------------------------|----------------------------|----------------------------------------------------------------------------------------------------|
| battery-power | Number:Power | The power to / from battery (negative means power is pulled from the battery and vice-versa) [W] |
| battery-current | Number:ElectricCurrent | The current to / from battery (negative means power is pulled from the battery and vice-versa) [A] |
| battery-voltage | Number:ElectricPotential | The voltage of the battery [V] |
| battery-temperature | Number:Temperature | The temperature of the battery [C/F] |
| battery-level | Number | The state of charge of the battery [%] |
### Grid related channels
@ -111,6 +112,40 @@ If you're missing a channel this means that it's not supported for your inverter
| serialNumber | The serial number of the Wi-Fi module |
| inverterType | Inverter Type (for example X1_HYBRID_G4) |
### Cloud Connect Inverter Configuration
| Parameter | Description |
|-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
| refreshInterval | Defines the refresh interval when the binding polls from from the Solax cloud (in seconds). Optional parameter(min=15, max=600). Default is 30 seconds. Be advised that the cloud API is limited to max 10 calls per minute and 10000 calls per day. |
| password | The registration number, shown in the Solax Cloud web portal. Mandatory parameter. |
| token | Token for accessing the Solax Cloud API. Can be obtained via Service -> API on the Solax cloud web portal. Mandatory parameter. |
### Channels
| Channel | Type | Description |
|---------------------------------|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
| inverter-output-power | Number:Power | The output power of the inverter [W] |
| pv1-power | Number:Power | The output power PV1 string [W] |
| pv2-power | Number:Power | The output power PV2 string [W] |
| pv3-power | Number:Power | The output power PV3 string [W] |
| pv4-power | Number:Power | The output power PV4 string [W] |
| pv-total-power | Number:Power | The output power of all the photovoltaic strings [W] |
| battery-power | Number:Power | The power to / from battery (negative means power is pulled from the battery and vice-versa) [W] |
| battery-level | Number | The state of charge of the battery [%] |
| feed-in-power | Number:Power | The power to / from grid (negative means power is pulled from the grid and vice-versa) [W] |
| total-feed-in-energy | Number:Energy | Total energy consumed from the electricity provider [kWh] |
| total-consumption | Number:Energy | Total energy consumed for the building [kWh] |
| today-energy | Number:Energy | Energy output from the inverter for the day [kWh] |
| total-energy | Number:Energy | Total energy output from the inverter [kWh] |
| raw-data | String | The raw data retrieved from inverter in JSON format. (Usable for channels not implemented. Can be consumed with the JSONpath transformation |
| inverter-status | String | The status of the inverter. (For the various status types, refer to the API documentation) |
| last-update-time | DateTime | Last time when a call has been made to the inverter |
| inverter-meter2-power | Number:Power | Inverter power on meter2 [W] |
| inverter-eps-power-r | Number:Power | Inverter AC EPS power R [W] |
| inverter-eps-power-s | Number:Power | Inverter AC EPS power S [W] |
| inverter-eps-power-t | Number:Power | Inverter AC EPS power T [W] |
## Full Example
Here are some file based examples.
@ -120,6 +155,7 @@ Here are some file based examples.
```java
// The local connect inverter thing
Thing solax:local-connect-inverter:localInverter [ refreshInterval=10, password="<SERIAL NUMBER OF THE WIFI MODULE>", hostname="<local IP/hostname in the network>" ]
Thing solax:cloud-connect-inverter:cloudInverter [ refresh=30, password="<REG_NUMBER>", token="<TOKEN>" ]
```
### Item Configuration
@ -128,17 +164,38 @@ Thing solax:local-connect-inverter:localInverter [ refreshInterval=10, password
Group gSolaxInverter "Solax Inverter" <energy> (boilerRoom)
Group solarPanels "Solar panels" <energy> (gSolaxInverter)
Number solaxPowerWest "West [%.0f W]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv1-power" }
Number solaxPowerEast "East [%.0f W]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv2-power" }
// Direct connect
Number solaxPowerWest "West Power [%d W]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv1-power" }
Number solaxPowerEast "East Power [%d W]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv2-power" }
Number solaxGenerationTotal "Total generаtion now [%.0f W]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels) { channel="solax:local-connect-inverter:localInverter:pv-total-power" }
Number solaxVoltageWest "West Voltage [%.1f V]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv1-voltage" }
Number solaxVoltageEast "East Voltage [%.1f V]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv2-voltage" }
Number solaxCurrentWest "West Current [%.1f A]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv1-current" }
Number solaxCurrentEast "East Current [%.1f A]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv2-current" }
Number solaxBatteryPower "Battery power [%.0f W]" <energy> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-power" }
Number solaxBatterySoc "Battery SoC [%.0f %%]" <batterylevel> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-state-of-charge" }
Number solaxBatterySoc "Battery SoC [%.0f %%]" <batterylevel> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-level" }
Number solaxBatteryTemperature "Battery temperature [%d °C]" <temperature> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-temperature" }
Number solaxBatteryCurrent "Battery current [%.1f A]" <energy> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-current" }
Number solaxBatteryVoltage "Battery voltage [%.1f V]" <energy> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-voltage" }
Number solaxFeedInPower "Feed-in power (CEZ) [%.0f W]" <energy> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:feed-in-power" }
Number solaxCalculatedTotalFeedInPower "Calculated feed-in total power (CEZ) [%.0f KWh]" <energy> (gsolax_inverter,EveryChangePersist)
Number solaxCalculatedTotalFeedInPowerThisMonth "Calculated feed-in total power this month (CEZ) [%.0f KWh]" <energy> (gsolax_inverter,EveryChangePersist)
Number solaxAcPower "Invertor output power [%.0f W]" <energy> (gsolax_inverter,EveryChangePersist){ channel="solax:local-connect-inverter:localInverter:inverter-output-power" }
Number solaxFrequency "Invertor frequency [%.2f Hz]" <energy> (gsolax_inverter,EveryChangePersist){ channel="solax:local-connect-inverter:localInverter:inverter-frequency" }
Number solaxVoltage "Invertor voltage [%.1f V]" <energy> (gsolax_inverter,EveryChangePersist){ channel="solax:local-connect-inverter:localInverter:inverter-voltage" }
String solaxInverterType "Inverter Type [%s]" <energy> (gsolax_inverter) { channel="solax:local-connect-inverter:localInverter:inverter-type"}
String solaxUploadTime "Last update time [%s]" <calendar> (gsolax_inverter) { channel="solax:local-connect-inverter:localInverter:last-update-time" }
String solaxRawData "Raw data [%s]" <data> (gsolax_inverter) { channel="solax:local-connect-inverter:localInverter:raw-data" }
String solaxLocalUploadTime "Local update time [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1tS]" <calendar> (gsolax_inverter) { channel="solax:local-connect-inverter:localInverter:last-update-time" }
String solaxCloudUploadTime "Cloud update time [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1tS]" <calendar> (gsolax_inverter) { channel="solax:cloud-connect-inverter:cloudInverter:last-update-time" }
String solaxLocalRawData "Local raw data [%s]" <data> (gsolax_inverter) { channel="solax:local-connect-inverter:localInverter:raw-data" }
String solaxCloudRawData "Cloud raw data [%s]" <data> (gsolax_inverter) { channel="solax:cloud-connect-inverter:cloudInverter:raw-data" }
// Cloud
Number solaxYieldToday "Yield today [%.0f kWh]" <energy> (gsolax_inverter){ channel="solax:cloud-connect-inverter:cloudInverter:today-energy" }
Number solaxYieldTotal "Yield total [%.0f kWh]" <energy> (gsolax_inverter) { channel="solax:cloud-connect-inverter:cloudInverter:total-energy" }
Number solaxFeedInEnergy "Total Feed-in (CEZ) Power [%.0f kWh]" <energy> (gsolax_inverter,EveryChangePersist) { channel="solax:cloud-connect-inverter:cloudInverter:total-feed-in-energy" }
String solaxInverterStatus "Inverter Status [%s]" <energy> (gsolax_inverter,EveryChangePersist) { channel="solax:cloud-connect-inverter:cloudInverter:inverter-status" }
```
### Sitemap Configuration
@ -149,74 +206,146 @@ Frame label="Solar power strings" {
Text item=solaxPowerEast icon="energy" valuecolor=[<=30="gray",<=300="red", <1500="orange", >=1500="green"]
Text item=solaxPowerWest icon="energy" valuecolor=[<=30="gray",<=300="red", <1500="orange", >=1500="green"]
Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"]
Chart item=solarPanels period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solarPanels period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solarPanels period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solarPanels period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solarPanels period=Y refresh=3600 visibility=[Chart_Period==4]
Chart item=solarPanels period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solarPanels period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solarPanels period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solarPanels period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solarPanels period=Y refresh=3600 visibility=[Chart_Period==4]
}
Text item=solaxPowerWest valuecolor=[<=30="gray",<=300="red", <1500="orange", >=1500="green"] {
Text item=solaxPowerEast icon="energy" valuecolor=[<=30="gray",<=300="red", <1500="orange", >=1500="green"]
Text item=solaxPowerWest icon="energy" valuecolor=[<=30="gray",<=300="red", <1500="orange", >=1500="green"]
Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"]
Chart item=solarPanels period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solarPanels period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solarPanels period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solarPanels period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solarPanels period=Y refresh=3600 visibility=[Chart_Period==4]
Chart item=solarPanels period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solarPanels period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solarPanels period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solarPanels period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solarPanels period=Y refresh=3600 visibility=[Chart_Period==4]
}
Text item=solaxVoltageEast valuecolor=[==0="gray",>0="green", >480="orange", >=500="red"]
Text item=solaxVoltageWest valuecolor=[==0="gray",>0="green", >480="orange", >=500="red"]
Text item=solaxCurrentEast valuecolor=[==0="gray",>0="green", >5="orange", >=10="red"]
Text item=solaxCurrentWest valuecolor=[==0="gray",>0="green", >5="orange", >=10="red"]
Text item=solaxGenerationTotal valuecolor=[<=100="gray",<=500="red", <2000="orange", >=2000="green"] {
Text item=solaxPowerEast icon="energy" valuecolor=[<=30="gray",<=300="red", <1500="orange", >=1500="green"]
Text item=solaxPowerWest icon="energy" valuecolor=[<=30="gray",<=300="red", <1500="orange", >=1500="green"]
Text item=solaxGenerationTotal icon="energy" valuecolor=[<=30="gray",<=300="red", <1500="orange", >=1500="green"]
Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"]
Chart item=solarPanels period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solarPanels period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solarPanels period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solarPanels period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solarPanels period=Y refresh=3600 visibility=[Chart_Period==4]
Chart item=solarPanels period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solarPanels period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solarPanels period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solarPanels period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solarPanels period=Y refresh=3600 visibility=[Chart_Period==4]
}
}
Frame label="Consumption" {
Text item=solaxAcPower valuecolor=[<=30="gray", <800="green", <1500="orange", >=1500="red"] {
Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"]
Text item=solaxAcPower icon="energy" valuecolor=[<=30="gray", <800="green", <1500="orange", >=1500="red"]
Chart item=solaxAcPower period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solaxAcPower period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solaxAcPower period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solaxAcPower period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solaxAcPower period=Y refresh=3600 visibility=[Chart_Period==4]
Chart item=solaxAcPower period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solaxAcPower period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solaxAcPower period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solaxAcPower period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solaxAcPower period=Y refresh=3600 visibility=[Chart_Period==4]
}
Text item=solaxFeedInPower valuecolor=[<=30="gray", <800="green", <1500="orange", >=1500="red"] {
Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"]
Text item=solaxFeedInPower icon="energy" valuecolor=[<=30="gray", <800="green", <1500="orange", >=1500="red"]
Chart item=solaxFeedInPower period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solaxFeedInPower period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solaxFeedInPower period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solaxFeedInPower period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solaxFeedInPower period=Y refresh=3600 visibility=[Chart_Period==4]
Chart item=solaxFeedInPower period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solaxFeedInPower period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solaxFeedInPower period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solaxFeedInPower period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solaxFeedInPower period=Y refresh=3600 visibility=[Chart_Period==4]
}
Text item=solaxFrequency valuecolor=[<=30="gray", <800="green", <1500="orange", >=1500="red"] {
Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"]
Text item=solaxFrequency icon="energy" valuecolor=[<=30="gray", <800="green", <1500="orange", >=1500="red"]
Chart item=solaxFrequency period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solaxFrequency period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solaxFrequency period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solaxFrequency period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solaxFrequency period=Y refresh=3600 visibility=[Chart_Period==4]
}
Text item=solaxVoltage valuecolor=[<=30="gray", <800="green", <1500="orange", >=1500="red"] {
Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"]
Text item=solaxVoltage icon="energy" valuecolor=[<=30="gray", <800="green", <1500="orange", >=1500="red"]
Chart item=solaxVoltage period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solaxVoltage period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solaxVoltage period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solaxVoltage period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solaxVoltage period=Y refresh=3600 visibility=[Chart_Period==4]
}
}
Frame label="Battery" {
Text item=solaxBatteryPower valuecolor=[<=-500="red", <0="orange", ==0="gray", >0="green"] {
Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"]
Text item=solaxBatteryPower icon="energy" valuecolor=[<-800="red", <0="orange", ==0="gray", >=0="green"]
Chart item=solaxBatteryPower period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solaxBatteryPower period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solaxBatteryPower period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solaxBatteryPower period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solaxBatteryPower period=Y refresh=3600 visibility=[Chart_Period==4]
Chart item=solaxBatteryPower period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solaxBatteryPower period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solaxBatteryPower period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solaxBatteryPower period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solaxBatteryPower period=Y refresh=3600 visibility=[Chart_Period==4]
}
Text item=solaxBatteryCurrent valuecolor=[<=-5="red", <0="orange", ==0="gray", >0="green"] {
Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"]
Text item=solaxBatteryCurrent icon="energy" valuecolor=[<-800="red", <0="orange", ==0="gray", >=0="green"]
Chart item=solaxBatteryCurrent period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solaxBatteryCurrent period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solaxBatteryCurrent period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solaxBatteryCurrent period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solaxBatteryCurrent period=Y refresh=3600 visibility=[Chart_Period==4]
}
Text item=solaxBatteryVoltage valuecolor=[<=-500="red", <0="orange", ==0="gray", >0="green"] {
Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"]
Text item=solaxBatteryVoltage icon="energy" valuecolor=[<-800="red", <0="orange", ==0="gray", >=0="green"]
Chart item=solaxBatteryVoltage period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solaxBatteryVoltage period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solaxBatteryVoltage period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solaxBatteryVoltage period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solaxBatteryVoltage period=Y refresh=3600 visibility=[Chart_Period==4]
}
Text item=solaxBatterySoc valuecolor=[<=30="red", <50="orange", >=50="green"] {
Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"]
Text item=solaxBatterySoc valuecolor=[<=30="red", <50="orange", >=50="green"]
Chart item=solaxBatterySoc period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solaxBatterySoc period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solaxBatterySoc period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solaxBatterySoc period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solaxBatterySoc period=Y refresh=3600 visibility=[Chart_Period==4]
Chart item=solaxBatterySoc period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solaxBatterySoc period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solaxBatterySoc period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solaxBatterySoc period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solaxBatterySoc period=Y refresh=3600 visibility=[Chart_Period==4]
}
Text item=solaxBatteryTemperature valuecolor=[<=35="green", <45="orange", >=45="red"] {
Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"]
Text item=solaxBatteryTemperature valuecolor=[<=25="green", <40="orange", >=40="red"]
Chart item=solaxBatteryTemperature period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solaxBatteryTemperature period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solaxBatteryTemperature period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solaxBatteryTemperature period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solaxBatteryTemperature period=Y refresh=3600 visibility=[Chart_Period==4]
}
}
Frame label="Statistics" {
Text item=solaxYieldToday
Text item=solaxYieldTotal
Text item=solaxConsumeEnergy
Text item=solaxFeedInEnergy
Text item=solaxCalculatedTotalFeedInPower
Text item=solaxCalculatedTotalFeedInPowerThisMonth valuecolor=[<200="green", >=200="orange", >=300="red"] {
Switch item=Chart_Period label="Chart Period" mappings=[0="H", 1="D", 2="W", 3="M", 4="Y"]
Text item=solaxCalculatedTotalFeedInPowerThisMonth valuecolor=[<=30="red", <50="orange", >=50="green"]
Chart item=solaxCalculatedTotalFeedInPowerThisMonth period=h refresh=600 visibility=[Chart_Period==0]
Chart item=solaxCalculatedTotalFeedInPowerThisMonth period=D refresh=3600 visibility=[Chart_Period==1]
Chart item=solaxCalculatedTotalFeedInPowerThisMonth period=W refresh=3600 visibility=[Chart_Period==2, Chart_Period==Uninitialized]
Chart item=solaxCalculatedTotalFeedInPowerThisMonth period=M refresh=3600 visibility=[Chart_Period==3]
Chart item=solaxCalculatedTotalFeedInPowerThisMonth period=Y refresh=3600 visibility=[Chart_Period==4]
}
}
Frame label="General" {
Text item=solaxInverterStatus
Text item=solaxLocalUploadTime
Text item=solaxCloudUploadTime
}
Frame label="Raw data" {
Text item=solaxLocalRawData
Text item=solaxCloudRawData
}
```

View File

@ -26,14 +26,18 @@ import org.openhab.core.thing.ThingTypeUID;
@NonNullByDefault
public class SolaxBindingConstants {
protected static final String BINDING_ID = "solax";
public static final String BINDING_ID = "solax";
private static final String THING_LOCAL_CONNECT_INVERTER_ID = "local-connect-inverter";
private static final String THING_CLOUD_CONNECT_INVERTER_ID = "cloud-connect-inverter";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_LOCAL_CONNECT_INVERTER = new ThingTypeUID(BINDING_ID,
THING_LOCAL_CONNECT_INVERTER_ID);
public static final ThingTypeUID THING_TYPE_CLOUD_CONNECT_INVERTER = new ThingTypeUID(BINDING_ID,
THING_CLOUD_CONNECT_INVERTER_ID);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_LOCAL_CONNECT_INVERTER);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_LOCAL_CONNECT_INVERTER,
THING_TYPE_CLOUD_CONNECT_INVERTER);
// List of properties
public static final String PROPERTY_INVERTER_TYPE = "inverterType";
@ -104,6 +108,14 @@ public class SolaxBindingConstants {
public static final String CHANNEL_TODAY_FEED_IN_ENERGY = "today-feed-in-energy";
public static final String CHANNEL_TODAY_CONSUMPTION = "today-consumption";
// Cloud specific channels
public static final String CHANNEL_INVERTER_PV3_POWER = "pv3-power";
public static final String CHANNEL_INVERTER_PV4_POWER = "pv4-power";
public static final String CHANNEL_INVERTER_OUTPUT_POWER_METER2 = "inverter-meter2-power";
public static final String CHANNEL_INVERTER_EPS_POWER_R = "inverter-eps-power-r";
public static final String CHANNEL_INVERTER_EPS_POWER_S = "inverter-eps-power-s";
public static final String CHANNEL_INVERTER_EPS_POWER_T = "inverter-eps-power-t";
// I18N Keys
protected static final String I18N_KEY_OFFLINE_COMMUNICATION_ERROR_JSON_CANNOT_BE_RETRIEVED = "@text/offline.communication-error.json-cannot-be-retrieved";
public static final String I18N_KEY_OFFLINE_COMMUNICATION_ERROR_JSON_CANNOT_BE_RETRIEVED = "@text/offline.communication-error.json-cannot-be-retrieved";
}

View File

@ -25,4 +25,5 @@ public class SolaxConfiguration {
public String hostname = "";
public String password = "";
public int refreshInterval = 10;
public String token = "";
}

View File

@ -16,12 +16,19 @@ import static org.openhab.binding.solax.internal.SolaxBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.solax.internal.handlers.SolaxCloudHandler;
import org.openhab.binding.solax.internal.handlers.SolaxLocalAccessHandler;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link SolaxHandlerFactory} is responsible for creating things and thing
@ -33,6 +40,18 @@ import org.osgi.service.component.annotations.Component;
@Component(configurationPid = "binding.solax", service = ThingHandlerFactory.class)
public class SolaxHandlerFactory extends BaseThingHandlerFactory {
private TranslationProvider i18nProvider;
private TimeZoneProvider timeZoneProvider;
private LocaleProvider localeProvider;
@Activate
public SolaxHandlerFactory(final @Reference TranslationProvider i18nProvider,
final @Reference TimeZoneProvider timeZoneProvider, final @Reference LocaleProvider localeProvider) {
this.i18nProvider = i18nProvider;
this.timeZoneProvider = timeZoneProvider;
this.localeProvider = localeProvider;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
@ -42,7 +61,9 @@ public class SolaxHandlerFactory extends BaseThingHandlerFactory {
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_LOCAL_CONNECT_INVERTER.equals(thingTypeUID)) {
return new SolaxLocalAccessHandler(thing);
return new SolaxLocalAccessHandler(thing, i18nProvider, timeZoneProvider);
} else if (THING_TYPE_CLOUD_CONNECT_INVERTER.equals(thingTypeUID)) {
return new SolaxCloudHandler(thing, i18nProvider, timeZoneProvider, localeProvider);
}
return null;
}

View File

@ -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;
}
}

View File

@ -25,4 +25,6 @@ import org.eclipse.jdt.annotation.Nullable;
public interface RawDataBean {
@Nullable
String getRawData();
public void setRawData(String rawData);
}

View File

@ -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 + "]";
}
}

View File

@ -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 + "]";
}
}

View File

@ -10,12 +10,13 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal.connectivity.rawdata;
package org.openhab.binding.solax.internal.connectivity.rawdata.local;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.solax.internal.connectivity.rawdata.RawDataBean;
import org.openhab.binding.solax.internal.util.GsonSupplier;
import com.google.gson.Gson;
@ -23,7 +24,6 @@ import com.google.gson.annotations.SerializedName;
/**
* The {@link LocalConnectRawDataBean} collects the raw data and the specific implementation to return the parsed data.
* If there are differences between the inverters probably would be wise to split the parsing in seprate class(es)
*
* @author Konstantin Polihronov - Initial contribution
*/
@ -90,6 +90,7 @@ public class LocalConnectRawDataBean implements RawDataBean {
return rawData;
}
@Override
public void setRawData(String rawData) {
this.rawData = rawData;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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()));
}
}

View File

@ -10,40 +10,36 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal;
package org.openhab.binding.solax.internal.handlers;
import java.io.IOException;
import java.time.ZonedDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import javax.measure.Quantity;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.solax.internal.SolaxBindingConstants;
import org.openhab.binding.solax.internal.SolaxConfiguration;
import org.openhab.binding.solax.internal.connectivity.LocalHttpConnector;
import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.InverterData;
import org.openhab.binding.solax.internal.connectivity.SolaxConnector;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.InverterType;
import org.openhab.binding.solax.internal.model.parsers.RawDataParser;
import org.openhab.binding.solax.internal.model.local.LocalInverterData;
import org.openhab.binding.solax.internal.model.local.parsers.RawDataParser;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -57,64 +53,25 @@ import com.google.gson.JsonParseException;
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public class SolaxLocalAccessHandler extends BaseThingHandler {
public class SolaxLocalAccessHandler extends AbstractSolaxHandler {
private final Logger logger = LoggerFactory.getLogger(SolaxLocalAccessHandler.class);
private static final int INITIAL_SCHEDULE_DELAY_SECONDS = 5;
private @NonNullByDefault({}) LocalHttpConnector localHttpConnector;
private @Nullable ScheduledFuture<?> schedule;
private boolean alreadyRemovedUnsupportedChannels;
private final Set<String> unsupportedExistingChannels = new HashSet<>();
private final ReentrantLock retrieveDataCallLock = new ReentrantLock();
public SolaxLocalAccessHandler(Thing thing) {
super(thing);
public SolaxLocalAccessHandler(Thing thing, TranslationProvider i18nProvider, TimeZoneProvider timeZoneProvider) {
super(thing, i18nProvider, timeZoneProvider);
}
@Override
public void initialize() {
updateStatus(ThingStatus.UNKNOWN);
SolaxConfiguration config = getConfigAs(SolaxConfiguration.class);
localHttpConnector = new LocalHttpConnector(config.password, config.hostname);
int refreshInterval = config.refreshInterval;
TimeUnit timeUnit = TimeUnit.SECONDS;
logger.debug("Scheduling regular interval retrieval every {} {}", refreshInterval, timeUnit);
schedule = scheduler.scheduleWithFixedDelay(this::retrieveData, INITIAL_SCHEDULE_DELAY_SECONDS, refreshInterval,
timeUnit);
protected SolaxConnector createConnector(SolaxConfiguration config) {
return new LocalHttpConnector(config.password, config.hostname);
}
private void retrieveData() {
if (retrieveDataCallLock.tryLock()) {
try {
String rawJsonData = localHttpConnector.retrieveData();
logger.debug("Raw data retrieved = {}", rawJsonData);
if (rawJsonData != null && !rawJsonData.isEmpty()) {
updateFromData(rawJsonData);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
SolaxBindingConstants.I18N_KEY_OFFLINE_COMMUNICATION_ERROR_JSON_CANNOT_BE_RETRIEVED);
}
} catch (IOException e) {
logger.debug("Exception received while attempting to retrieve data via HTTP", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} finally {
retrieveDataCallLock.unlock();
}
} else {
logger.debug("Unable to retrieve data because a request is already in progress.");
}
}
private void updateFromData(String rawJsonData) {
@Override
protected void updateFromData(String rawJsonData) {
try {
LocalConnectRawDataBean rawDataBean = parseJson(rawJsonData);
InverterType inverterType = calculateInverterType(rawDataBean);
@ -125,13 +82,9 @@ public class SolaxLocalAccessHandler extends BaseThingHandler {
alreadyRemovedUnsupportedChannels = true;
}
InverterData genericInverterData = parser.getData(rawDataBean);
LocalInverterData genericInverterData = parser.getData(rawDataBean);
updateChannels(parser, genericInverterData);
updateProperties(genericInverterData);
if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
} else {
cancelSchedule();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
@ -144,9 +97,9 @@ public class SolaxLocalAccessHandler extends BaseThingHandler {
}
private LocalConnectRawDataBean parseJson(String rawJsonData) {
LocalConnectRawDataBean inverterParsedData = LocalConnectRawDataBean.fromJson(rawJsonData);
logger.debug("Received a new inverter JSON object. Data = {}", inverterParsedData.toString());
return inverterParsedData;
LocalConnectRawDataBean fromJson = LocalConnectRawDataBean.fromJson(rawJsonData);
logger.debug("Received a new inverter JSON object. Data = {}", fromJson.toString());
return fromJson;
}
private InverterType calculateInverterType(LocalConnectRawDataBean rawDataBean) {
@ -154,12 +107,12 @@ public class SolaxLocalAccessHandler extends BaseThingHandler {
return InverterType.fromIndex(type);
}
private void updateProperties(InverterData genericInverterData) {
private void updateProperties(LocalInverterData genericInverterData) {
updateProperty(Thing.PROPERTY_SERIAL_NUMBER, genericInverterData.getWifiSerial());
updateProperty(SolaxBindingConstants.PROPERTY_INVERTER_TYPE, genericInverterData.getInverterType().name());
}
private void updateChannels(RawDataParser parser, InverterData inverterData) {
private void updateChannels(RawDataParser parser, LocalInverterData inverterData) {
updateState(SolaxBindingConstants.CHANNEL_RAW_DATA, new StringType(inverterData.getRawData()));
Set<String> supportedChannels = parser.getSupportedChannels();
@ -190,10 +143,6 @@ public class SolaxLocalAccessHandler extends BaseThingHandler {
supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_TEMPERATURE, inverterData.getBatteryTemperature(),
SIUnits.CELSIUS, supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_TEMPERATURE1, inverterData.getInverterTemperature1(),
SIUnits.CELSIUS, supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_TEMPERATURE2, inverterData.getInverterTemperature2(),
SIUnits.CELSIUS, supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_STATE_OF_CHARGE, inverterData.getBatteryLevel(),
Units.PERCENT, supportedChannels);
updateChannel(SolaxBindingConstants.CHANNEL_FEED_IN_POWER, inverterData.getFeedInPower(), Units.WATT,
@ -297,29 +246,6 @@ public class SolaxLocalAccessHandler extends BaseThingHandler {
channelsToRemoveForLog);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
scheduler.execute(this::retrieveData);
} else {
logger.debug("Binding {} only supports refresh command", SolaxBindingConstants.BINDING_ID);
}
}
@Override
public void dispose() {
super.dispose();
cancelSchedule();
}
private void cancelSchedule() {
ScheduledFuture<?> schedule = this.schedule;
if (schedule != null) {
schedule.cancel(true);
this.schedule = null;
}
}
private <T extends Quantity<T>> void updateChannel(String channelID, double value, Unit<T> unit,
Set<String> supportedChannels) {
if (supportedChannels.contains(channelID)) {

View File

@ -18,10 +18,10 @@ import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.solax.internal.model.parsers.RawDataParser;
import org.openhab.binding.solax.internal.model.parsers.X1HybridG4DataParser;
import org.openhab.binding.solax.internal.model.parsers.X3HybridG4DataParser;
import org.openhab.binding.solax.internal.model.parsers.X3MicOrProG2DataParser;
import org.openhab.binding.solax.internal.model.local.parsers.RawDataParser;
import org.openhab.binding.solax.internal.model.local.parsers.X1HybridG4DataParser;
import org.openhab.binding.solax.internal.model.local.parsers.X3HybridG4DataParser;
import org.openhab.binding.solax.internal.model.local.parsers.X3MicOrProG2DataParser;
/**
* The {@link InverterType} class is enum representing the different inverter types with a simple logic to convert from

View File

@ -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);
}

View File

@ -10,30 +10,29 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal.model.impl;
package org.openhab.binding.solax.internal.model.local;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.InverterData;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.InverterType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link CommonInverterData} is an abstract class that contains the common information, applicable for all
* The {@link CommonLocalInverterData} is an abstract class that contains the common information, applicable for all
* inverters.
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public abstract class CommonInverterData implements InverterData {
public abstract class CommonLocalInverterData implements LocalInverterData {
private final Logger logger = LoggerFactory.getLogger(CommonInverterData.class);
private final Logger logger = LoggerFactory.getLogger(CommonLocalInverterData.class);
private LocalConnectRawDataBean data;
public CommonInverterData(LocalConnectRawDataBean data) {
public CommonLocalInverterData(LocalConnectRawDataBean data) {
this.data = data;
}

View File

@ -10,18 +10,19 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal.model;
package org.openhab.binding.solax.internal.model.local;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.solax.internal.model.InverterType;
/**
* The {@link InverterData} Interface for the parsed inverter data in meaningful format
* The {@link LocalInverterData} Interface for the parsed inverter data in meaningful format
*
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public interface InverterData {
public interface LocalInverterData {
@Nullable
String getWifiSerial();
@ -90,14 +91,6 @@ public interface InverterData {
return Short.MIN_VALUE;
}
default short getInverterWorkModeCode() {
return Short.MIN_VALUE;
}
default String getInverterWorkMode() {
return String.valueOf(getInverterWorkModeCode());
}
default short getBatteryLevel() {
return Short.MIN_VALUE;
}
@ -222,6 +215,14 @@ public interface InverterData {
return Short.MIN_VALUE;
}
default short getInverterWorkModeCode() {
return Short.MIN_VALUE;
}
default String getInverterWorkMode() {
return String.valueOf(getInverterWorkModeCode());
}
default String toStringDetailed() {
return "WifiSerial = " + getWifiSerial() + ", WifiVersion = " + getWifiVersion() + ", InverterType = "
+ getInverterType() + ", BatteryPower = " + getBatteryPower() + "W, Battery SoC = " + getBatteryLevel()

View File

@ -10,10 +10,10 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal.model.impl;
package org.openhab.binding.solax.internal.model.local;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
/**
* The {@link X1HybridG4InverterData} is an implementation of the single phased inverter data interface for X1 Hybrid G4
@ -22,7 +22,7 @@ import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDa
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public class X1HybridG4InverterData extends CommonInverterData {
public class X1HybridG4InverterData extends CommonLocalInverterData {
public X1HybridG4InverterData(LocalConnectRawDataBean data) {
super(data);
@ -83,11 +83,6 @@ public class X1HybridG4InverterData extends CommonInverterData {
return getData(9);
}
@Override
public short getInverterWorkModeCode() {
return getData(10);
}
@Override
public double getBatteryVoltage() {
return ((double) getData(14)) / 100;
@ -112,4 +107,9 @@ public class X1HybridG4InverterData extends CommonInverterData {
public short getBatteryLevel() {
return getData(18);
}
@Override
public short getInverterWorkModeCode() {
return getData(10);
}
}

View File

@ -10,10 +10,10 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal.model.impl;
package org.openhab.binding.solax.internal.model.local;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
/**
* The {@link X3HybridG4InverterData} is responsible for handling commands, which are
@ -22,7 +22,7 @@ import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDa
* @author Konstantin Polihronov - Initial contribution
*/
@NonNullByDefault
public class X3HybridG4InverterData extends CommonInverterData {
public class X3HybridG4InverterData extends CommonLocalInverterData {
public X3HybridG4InverterData(LocalConnectRawDataBean data) {
super(data);
@ -125,11 +125,6 @@ public class X3HybridG4InverterData extends CommonInverterData {
return ((double) getData(18)) / 100;
}
@Override
public short getInverterWorkModeCode() {
return getData(19);
}
// Battery
@Override
@ -225,4 +220,9 @@ public class X3HybridG4InverterData extends CommonInverterData {
public double getTodayBatteryChargeEnergy() {
return ((double) getData(79)) / 10;
}
@Override
public short getInverterWorkModeCode() {
return getData(19);
}
}

View File

@ -10,10 +10,10 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal.model.impl;
package org.openhab.binding.solax.internal.model.local;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
/**
* The {@link X3HybridG4InverterData} is responsible for handling commands, which are
@ -23,7 +23,7 @@ import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDa
* (based on X1/X3 G4 parser from Konstantin Polihronov)
*/
@NonNullByDefault
public class X3MicOrProG2InverterData extends CommonInverterData {
public class X3MicOrProG2InverterData extends CommonLocalInverterData {
public X3MicOrProG2InverterData(LocalConnectRawDataBean data) {
super(data);

View File

@ -10,13 +10,13 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal.model.parsers;
package org.openhab.binding.solax.internal.model.local.parsers;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.InverterData;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.local.LocalInverterData;
/**
* The {@link RawDataParser} declares generic parser implementation that parses raw data to generic inverter data which
@ -27,7 +27,7 @@ import org.openhab.binding.solax.internal.model.InverterData;
@NonNullByDefault
public interface RawDataParser {
InverterData getData(LocalConnectRawDataBean bean);
LocalInverterData getData(LocalConnectRawDataBean bean);
Set<String> getSupportedChannels();
}

View File

@ -10,16 +10,16 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal.model.parsers;
package org.openhab.binding.solax.internal.model.local.parsers;
import static org.openhab.binding.solax.internal.SolaxBindingConstants.*;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.InverterData;
import org.openhab.binding.solax.internal.model.impl.X1HybridG4InverterData;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.local.LocalInverterData;
import org.openhab.binding.solax.internal.model.local.X1HybridG4InverterData;
/**
* The {@link SinglePhaseDataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the
@ -39,7 +39,7 @@ public class X1HybridG4DataParser implements RawDataParser {
CHANNEL_INVERTER_OUTPUT_VOLTAGE, CHANNEL_INVERTER_OUTPUT_FREQUENCY, CHANNEL_INVERTER_WORKMODE);
@Override
public InverterData getData(LocalConnectRawDataBean rawData) {
public LocalInverterData getData(LocalConnectRawDataBean rawData) {
return new X1HybridG4InverterData(rawData);
}

View File

@ -10,16 +10,16 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal.model.parsers;
package org.openhab.binding.solax.internal.model.local.parsers;
import static org.openhab.binding.solax.internal.SolaxBindingConstants.*;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.InverterData;
import org.openhab.binding.solax.internal.model.impl.X3HybridG4InverterData;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.local.LocalInverterData;
import org.openhab.binding.solax.internal.model.local.X3HybridG4InverterData;
/**
* The {@link X3HybridG4DataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the
@ -47,7 +47,7 @@ public class X3HybridG4DataParser implements RawDataParser {
CHANNEL_TODAY_BATTERY_CHARGE_ENERGY, CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY, CHANNEL_INVERTER_WORKMODE);
@Override
public InverterData getData(LocalConnectRawDataBean rawData) {
public LocalInverterData getData(LocalConnectRawDataBean rawData) {
return new X3HybridG4InverterData(rawData);
}

View File

@ -10,16 +10,16 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal.model.parsers;
package org.openhab.binding.solax.internal.model.local.parsers;
import static org.openhab.binding.solax.internal.SolaxBindingConstants.*;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.InverterData;
import org.openhab.binding.solax.internal.model.impl.X3MicOrProG2InverterData;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.local.LocalInverterData;
import org.openhab.binding.solax.internal.model.local.X3MicOrProG2InverterData;
/**
* The {@link X3MicOrProG2DataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the
@ -44,7 +44,7 @@ public class X3MicOrProG2DataParser implements RawDataParser {
CHANNEL_INVERTER_TEMPERATURE2, CHANNEL_INVERTER_WORKMODE, CHANNEL_RAW_DATA);
@Override
public InverterData getData(LocalConnectRawDataBean rawData) {
public LocalInverterData getData(LocalConnectRawDataBean rawData) {
return new X3MicOrProG2InverterData(rawData);
}

View File

@ -5,6 +5,42 @@ addon.solax.description = This is the binding for Solax inverters.
# thing types
thing-type.solax.cloud-connect-inverter.label = Cloud Connect Inverter
thing-type.solax.cloud-connect-inverter.description = The inverter representation that is retrieved from Solax cloud API
thing-type.solax.cloud-connect-inverter.channel.battery-level.label = Battery Level
thing-type.solax.cloud-connect-inverter.channel.battery-level.description = The battery state of charge in percent
thing-type.solax.cloud-connect-inverter.channel.battery-power.label = Battery Power
thing-type.solax.cloud-connect-inverter.channel.battery-power.description = Power to/from the battery
thing-type.solax.cloud-connect-inverter.channel.feed-in-power.label = Feed-In Power
thing-type.solax.cloud-connect-inverter.channel.feed-in-power.description = Power to/from the electricity network.
thing-type.solax.cloud-connect-inverter.channel.inverter-eps-power-r.label = EPS power R
thing-type.solax.cloud-connect-inverter.channel.inverter-eps-power-r.description = Inverter AC EPS power R
thing-type.solax.cloud-connect-inverter.channel.inverter-eps-power-s.label = EPS power S
thing-type.solax.cloud-connect-inverter.channel.inverter-eps-power-s.description = Inverter AC EPS power S
thing-type.solax.cloud-connect-inverter.channel.inverter-eps-power-t.label = EPS power T
thing-type.solax.cloud-connect-inverter.channel.inverter-eps-power-t.description = Inverter AC EPS power T
thing-type.solax.cloud-connect-inverter.channel.inverter-meter2-power.label = Meter2 Power
thing-type.solax.cloud-connect-inverter.channel.inverter-meter2-power.description = Inverter power on meter2.
thing-type.solax.cloud-connect-inverter.channel.inverter-output-power.label = Inverter Input/Output Power
thing-type.solax.cloud-connect-inverter.channel.inverter-output-power.description = Power to/from the inverter
thing-type.solax.cloud-connect-inverter.channel.pv-total-power.label = PV Total Power
thing-type.solax.cloud-connect-inverter.channel.pv-total-power.description = The sum of PV powers from all PV strings
thing-type.solax.cloud-connect-inverter.channel.pv1-power.label = PV 1 Power
thing-type.solax.cloud-connect-inverter.channel.pv1-power.description = Electric power of PV String 1
thing-type.solax.cloud-connect-inverter.channel.pv2-power.label = PV 2 Power
thing-type.solax.cloud-connect-inverter.channel.pv2-power.description = Electric power of PV String 2
thing-type.solax.cloud-connect-inverter.channel.pv3-power.label = PV 3 Power
thing-type.solax.cloud-connect-inverter.channel.pv3-power.description = Electric power of PV String 3
thing-type.solax.cloud-connect-inverter.channel.pv4-power.label = PV 4 Power
thing-type.solax.cloud-connect-inverter.channel.pv4-power.description = Electric power of PV String 4
thing-type.solax.cloud-connect-inverter.channel.today-energy.label = Yield today
thing-type.solax.cloud-connect-inverter.channel.today-energy.description = Inverter output energy for the day
thing-type.solax.cloud-connect-inverter.channel.total-consumption.label = Total Consumed Energy
thing-type.solax.cloud-connect-inverter.channel.total-consumption.description = Total Energy consumed from the electricity network.
thing-type.solax.cloud-connect-inverter.channel.total-energy.label = Yield total
thing-type.solax.cloud-connect-inverter.channel.total-energy.description = Total inverter output energy
thing-type.solax.cloud-connect-inverter.channel.total-feed-in-energy.label = Total Feed-In Energy
thing-type.solax.cloud-connect-inverter.channel.total-feed-in-energy.description = Total energy feed-in to the electricity network.
thing-type.solax.local-connect-inverter.label = Local Connect Inverter
thing-type.solax.local-connect-inverter.description = The inverter representation that supports local connections via HTTP
thing-type.solax.local-connect-inverter.channel.battery-current.label = Battery Current
@ -100,6 +136,12 @@ thing-type.solax.local-connect-inverter.channel.total-pv-energy.description = To
# thing types config
thing-type.config.solax.cloud-connect-inverter.password.label = Password
thing-type.config.solax.cloud-connect-inverter.password.description = Password for accessing cloud API (the serial number of the Wi-Fi module)
thing-type.config.solax.cloud-connect-inverter.refreshInterval.label = Refresh Interval
thing-type.config.solax.cloud-connect-inverter.refreshInterval.description = Refresh interval in seconds. (Cloud API is limited to max 10 calls per minute and 10000 times per day)
thing-type.config.solax.cloud-connect-inverter.token.label = Token
thing-type.config.solax.cloud-connect-inverter.token.description = Token to access the Solax cloud API
thing-type.config.solax.local-connect-inverter.hostname.label = Network Address
thing-type.config.solax.local-connect-inverter.hostname.description = IP address or the host name of the Wi-Fi module
thing-type.config.solax.local-connect-inverter.password.label = Password
@ -113,6 +155,8 @@ channel-type.solax.battery-temperature.label = Battery Temperature
channel-type.solax.battery-temperature.description = Battery Temperature
channel-type.solax.frequency.label = Electric Frequency
channel-type.solax.frequency.description = Frequency of the electricity to/from the inverter
channel-type.solax.inverter-status-type.label = Inverter Status
channel-type.solax.inverter-status-type.description = The status of the inverter.
channel-type.solax.inverter-temperature.label = Inverter Temperature
channel-type.solax.inverter-temperature.description = Inverter Temperature
channel-type.solax.inverter-workmode.label = Inverter Workmode
@ -138,5 +182,19 @@ channel-type.solax.raw-data-type.description = The raw JSON data retrieved from
# thing status descriptions
cloud.inverter.status.wait = Wait
cloud.inverter.status.check = Check
cloud.inverter.status.normal = Normal
cloud.inverter.status.fault = Fault
cloud.inverter.status.permanent-fault = Permanent Fault
cloud.inverter.status.update = Update
cloud.inverter.status.eps-check = EPS Check
cloud.inverter.status.self-test = Self Test
cloud.inverter.status.idle = Idle
cloud.inverter.status.standby = Standby
cloud.inverter.status.pv-wake-up-battery = PV Wake-up Battery
cloud.inverter.status.gen-check = Gen Check
cloud.inverter.status.gen-run = Gen Run
cloud.inverter.status.unknown = Unknown
offline.communication-error.json-cannot-be-retrieved = JSON data could not be retrieved.
offline.configuration-error.parser-not-implemented = Parser for inverter of type {0} is not implemented.

View File

@ -57,6 +57,29 @@
</options>
</state>
</channel-type>
<channel-type id="inverter-workmode-cloud">
<item-type>String</item-type>
<label>Inverter Workmode</label>
<description>Inverter Workmode</description>
<state pattern="%s" readOnly="true">
<options>
<option value="100">Waiting</option>
<option value="101">Checking</option>
<option value="102">Normal</option>
<option value="103">Fault</option>
<option value="104">Permanent Fault</option>
<option value="105">Updating</option>
<option value="106">EPS Check</option>
<option value="107">EPS Normal</option>
<option value="108">Self Test</option>
<option value="109">Idle</option>
<option value="110">Standby</option>
<option value="111">PV Wake-up Battery</option>
<option value="112">GEN Check</option>
<option value="113">GEN Run</option>
</options>
</state>
</channel-type>
<channel-type id="last-retrieve-time-stamp">
<item-type>DateTime</item-type>
<label>Last Retrieve Time Stamp</label>

View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="solax"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="cloud-connect-inverter">
<label>Cloud Connect Inverter</label>
<description>The inverter representation that is retrieved from Solax cloud API</description>
<channels>
<channel id="inverter-output-power" typeId="system.electric-power">
<label>Inverter Input/Output Power</label>
<description>Power to/from the inverter</description>
</channel>
<channel id="pv1-power" typeId="system.electric-power">
<label>PV 1 Power</label>
<description>Electric power of PV String 1</description>
</channel>
<channel id="pv2-power" typeId="system.electric-power">
<label>PV 2 Power</label>
<description>Electric power of PV String 2</description>
</channel>
<channel id="pv3-power" typeId="system.electric-power">
<label>PV 3 Power</label>
<description>Electric power of PV String 3</description>
</channel>
<channel id="pv4-power" typeId="system.electric-power">
<label>PV 4 Power</label>
<description>Electric power of PV String 4</description>
</channel>
<channel id="pv-total-power" typeId="system.electric-power">
<label>PV Total Power</label>
<description>The sum of PV powers from all PV strings</description>
</channel>
<channel id="battery-level" typeId="system.battery-level">
<label>Battery Level</label>
<description>The battery state of charge in percent</description>
</channel>
<channel id="battery-power" typeId="system.electric-power">
<label>Battery Power</label>
<description>Power to/from the battery</description>
</channel>
<channel id="feed-in-power" typeId="system.electric-power">
<label>Feed-In Power</label>
<description>Power to/from the electricity network.</description>
</channel>
<channel id="total-feed-in-energy" typeId="system.electric-energy">
<label>Total Feed-In Energy</label>
<description>Total energy feed-in to the electricity network.</description>
</channel>
<channel id="total-consumption" typeId="system.electric-energy">
<label>Total Consumed Energy</label>
<description>Total Energy consumed from the electricity network.</description>
</channel>
<channel id="inverter-meter2-power" typeId="system.electric-power">
<label>Meter2 Power</label>
<description>Inverter power on meter2.</description>
</channel>
<channel id="inverter-eps-power-r" typeId="system.electric-power">
<label>EPS power R</label>
<description>Inverter AC EPS power R</description>
</channel>
<channel id="inverter-eps-power-s" typeId="system.electric-power">
<label>EPS power S</label>
<description>Inverter AC EPS power S</description>
</channel>
<channel id="inverter-eps-power-t" typeId="system.electric-power">
<label>EPS power T</label>
<description>Inverter AC EPS power T</description>
</channel>
<channel id="today-energy" typeId="system.electric-energy">
<label>Yield today</label>
<description>Inverter output energy for the day</description>
</channel>
<channel id="total-energy" typeId="system.electric-energy">
<label>Yield total</label>
<description>Total inverter output energy</description>
</channel>
<channel id="last-update-time" typeId="last-retrieve-time-stamp"/>
<channel id="inverter-workmode" typeId="inverter-workmode-cloud">
<label>Inverter Workmode</label>
<description>Inverter Workmode</description>
</channel>
<channel id="raw-data" typeId="raw-data-type"/>
</channels>
<config-description>
<parameter name="refreshInterval" type="integer" min="9" max="600">
<label>Refresh Interval</label>
<description>Refresh interval in seconds. (Cloud API is limited to max 10 calls per minute and 10000 times per day)</description>
<default>30</default>
</parameter>
<parameter name="password" type="text" required="true">
<label>Password</label>
<description>Password for accessing cloud API (the serial number of the Wi-Fi module)</description>
<context>password</context>
</parameter>
<parameter name="token" type="text" required="true">
<label>Token</label>
<description>Token to access the Solax cloud API</description>
<context>password</context>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@ -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");
}
}

View File

@ -10,16 +10,16 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal;
package org.openhab.binding.solax.internal.local;
import static org.junit.jupiter.api.Assertions.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.InverterData;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.InverterType;
import org.openhab.binding.solax.internal.model.parsers.RawDataParser;
import org.openhab.binding.solax.internal.model.local.LocalInverterData;
import org.openhab.binding.solax.internal.model.local.parsers.RawDataParser;
/**
* The {@link TestX1HybridG4Parser} Simple test that tests for proper parsing against a real data from the inverter
@ -53,7 +53,7 @@ public class TestX1HybridG4Parser {
RawDataParser parser = inverterType.getParser();
assertNotNull(parser);
InverterData data = parser.getData(bean);
LocalInverterData data = parser.getData(bean);
assertEquals("SOME_SERIAL_NUMBER", data.getWifiSerial());
assertEquals("3.008.10", data.getWifiVersion());

View File

@ -10,16 +10,16 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal;
package org.openhab.binding.solax.internal.local;
import static org.junit.jupiter.api.Assertions.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.InverterData;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.InverterType;
import org.openhab.binding.solax.internal.model.parsers.RawDataParser;
import org.openhab.binding.solax.internal.model.local.LocalInverterData;
import org.openhab.binding.solax.internal.model.local.parsers.RawDataParser;
/**
* The {@link TestX3HybridG4Parser} simple test that tests for proper parsing against a real data from the inverter
@ -60,7 +60,7 @@ public class TestX3HybridG4Parser {
RawDataParser parser = inverterType.getParser();
assertNotNull(parser);
InverterData data = parser.getData(bean);
LocalInverterData data = parser.getData(bean);
assertEquals("XYZ", data.getWifiSerial());
assertEquals("3.005.01", data.getWifiVersion());

View File

@ -10,16 +10,16 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.solax.internal;
package org.openhab.binding.solax.internal.local;
import static org.junit.jupiter.api.Assertions.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.InverterData;
import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
import org.openhab.binding.solax.internal.model.InverterType;
import org.openhab.binding.solax.internal.model.parsers.RawDataParser;
import org.openhab.binding.solax.internal.model.local.LocalInverterData;
import org.openhab.binding.solax.internal.model.local.parsers.RawDataParser;
/**
* The {@link TestX3HybridG4Parser} simple test that tests for proper parsing against a real data from the inverter
@ -56,7 +56,7 @@ public class TestX3MicOrProG2Parser {
RawDataParser parser = inverterType.getParser();
assertNotNull(parser);
InverterData data = parser.getData(bean);
LocalInverterData data = parser.getData(bean);
assertEquals("XYZ", data.getWifiSerial());
assertEquals("3.003.02", data.getWifiVersion());