| 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.
### 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
| 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.
```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
Group gSolaxInverter "Solax Inverter" <energy> (boilerRoom)
Group solarPanels "Solar panels" <energy> (gSolaxInverter)
-Number solaxPowerWest "West [%.0f W]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv1-power" }
-Number solaxPowerEast "East [%.0f W]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv2-power" }
+// Direct connect
+Number solaxPowerWest "West Power [%d W]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv1-power" }
+Number solaxPowerEast "East Power [%d W]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv2-power" }
+Number solaxGenerationTotal "Total generаtion now [%.0f W]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels) { channel="solax:local-connect-inverter:localInverter:pv-total-power" }
+Number solaxVoltageWest "West Voltage [%.1f V]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv1-voltage" }
+Number solaxVoltageEast "East Voltage [%.1f V]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv2-voltage" }
+Number solaxCurrentWest "West Current [%.1f A]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv1-current" }
+Number solaxCurrentEast "East Current [%.1f A]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:local-connect-inverter:localInverter:pv2-current" }
Number solaxBatteryPower "Battery power [%.0f W]" <energy> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-power" }
-Number solaxBatterySoc "Battery SoC [%.0f %%]" <batterylevel> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-state-of-charge" }
+Number solaxBatterySoc "Battery SoC [%.0f %%]" <batterylevel> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-level" }
+Number solaxBatteryTemperature "Battery temperature [%d °C]" <temperature> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-temperature" }
+Number solaxBatteryCurrent "Battery current [%.1f A]" <energy> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-current" }
+Number solaxBatteryVoltage "Battery voltage [%.1f V]" <energy> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:battery-voltage" }
Number solaxFeedInPower "Feed-in power (CEZ) [%.0f W]" <energy> (gsolax_inverter,EveryChangePersist) { channel="solax:local-connect-inverter:localInverter:feed-in-power" }
+Number solaxCalculatedTotalFeedInPower "Calculated feed-in total power (CEZ) [%.0f KWh]" <energy> (gsolax_inverter,EveryChangePersist)
+Number solaxCalculatedTotalFeedInPowerThisMonth "Calculated feed-in total power this month (CEZ) [%.0f KWh]" <energy> (gsolax_inverter,EveryChangePersist)
Number solaxAcPower "Invertor output power [%.0f W]" <energy> (gsolax_inverter,EveryChangePersist){ channel="solax:local-connect-inverter:localInverter:inverter-output-power" }
+Number solaxFrequency "Invertor frequency [%.2f Hz]" <energy> (gsolax_inverter,EveryChangePersist){ channel="solax:local-connect-inverter:localInverter:inverter-frequency" }
+Number solaxVoltage "Invertor voltage [%.1f V]" <energy> (gsolax_inverter,EveryChangePersist){ channel="solax:local-connect-inverter:localInverter:inverter-voltage" }
+
+String solaxLocalUploadTime "Local update time [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1tS]" <calendar> (gsolax_inverter) { channel="solax:local-connect-inverter:localInverter:last-update-time" }
+String solaxCloudUploadTime "Cloud update time [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1tS]" <calendar> (gsolax_inverter) { channel="solax:cloud-connect-inverter:cloudInverter:last-update-time" }
-String solaxInverterType "Inverter Type [%s]" <energy> (gsolax_inverter) { channel="solax:local-connect-inverter:localInverter:inverter-type"}
-String solaxUploadTime "Last update time [%s]" <calendar> (gsolax_inverter) { channel="solax:local-connect-inverter:localInverter:last-update-time" }
-String solaxRawData "Raw data [%s]" <data> (gsolax_inverter) { channel="solax:local-connect-inverter:localInverter:raw-data" }
+String solaxLocalRawData "Local raw data [%s]" <data> (gsolax_inverter) { channel="solax:local-connect-inverter:localInverter:raw-data" }
+String solaxCloudRawData "Cloud raw data [%s]" <data> (gsolax_inverter) { channel="solax:cloud-connect-inverter:cloudInverter:raw-data" }
+
+// Cloud
+Number solaxYieldToday "Yield today [%.0f kWh]" <energy> (gsolax_inverter){ channel="solax:cloud-connect-inverter:cloudInverter:today-energy" }
+Number solaxYieldTotal "Yield total [%.0f kWh]" <energy> (gsolax_inverter) { channel="solax:cloud-connect-inverter:cloudInverter:total-energy" }
+Number solaxFeedInEnergy "Total Feed-in (CEZ) Power [%.0f kWh]" <energy> (gsolax_inverter,EveryChangePersist) { channel="solax:cloud-connect-inverter:cloudInverter:total-feed-in-energy" }
+String solaxInverterStatus "Inverter Status [%s]" <energy> (gsolax_inverter,EveryChangePersist) { channel="solax:cloud-connect-inverter:cloudInverter:inverter-status" }
```
### Sitemap Configuration
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
+}
```
-
-
@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";
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";
}
public String hostname = "";
public String password = "";
public int refreshInterval = 10;
+ public String token = "";
}
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
@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);
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;
}
+++ /dev/null
-/**
- * Copyright (c) 2010-2024 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.solax.internal;
-
-import java.io.IOException;
-import java.time.ZonedDateTime;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.ReentrantLock;
-
-import javax.measure.Quantity;
-import javax.measure.Unit;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.solax.internal.connectivity.LocalHttpConnector;
-import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
-import org.openhab.binding.solax.internal.model.InverterData;
-import org.openhab.binding.solax.internal.model.InverterType;
-import org.openhab.binding.solax.internal.model.parsers.RawDataParser;
-import org.openhab.core.library.types.DateTimeType;
-import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.library.unit.SIUnits;
-import org.openhab.core.library.unit.Units;
-import org.openhab.core.thing.Channel;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.thing.binding.BaseThingHandler;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.RefreshType;
-import org.openhab.core.types.UnDefType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.gson.JsonParseException;
-
-/**
- * The {@link SolaxLocalAccessHandler} is responsible for handling commands, which are
- * sent to one of the channels.
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public class SolaxLocalAccessHandler extends BaseThingHandler {
-
- private final Logger logger = LoggerFactory.getLogger(SolaxLocalAccessHandler.class);
-
- private static final int INITIAL_SCHEDULE_DELAY_SECONDS = 5;
-
- private @NonNullByDefault({}) LocalHttpConnector localHttpConnector;
-
- private @Nullable ScheduledFuture<?> schedule;
-
- private boolean alreadyRemovedUnsupportedChannels;
-
- private final Set<String> unsupportedExistingChannels = new HashSet<>();
-
- private final ReentrantLock retrieveDataCallLock = new ReentrantLock();
-
- public SolaxLocalAccessHandler(Thing thing) {
- super(thing);
- }
-
- @Override
- public void initialize() {
- updateStatus(ThingStatus.UNKNOWN);
-
- SolaxConfiguration config = getConfigAs(SolaxConfiguration.class);
- localHttpConnector = new LocalHttpConnector(config.password, config.hostname);
- int refreshInterval = config.refreshInterval;
- TimeUnit timeUnit = TimeUnit.SECONDS;
-
- logger.debug("Scheduling regular interval retrieval every {} {}", refreshInterval, timeUnit);
- schedule = scheduler.scheduleWithFixedDelay(this::retrieveData, INITIAL_SCHEDULE_DELAY_SECONDS, refreshInterval,
- timeUnit);
- }
-
- private void retrieveData() {
- if (retrieveDataCallLock.tryLock()) {
- try {
- String rawJsonData = localHttpConnector.retrieveData();
- logger.debug("Raw data retrieved = {}", rawJsonData);
-
- if (rawJsonData != null && !rawJsonData.isEmpty()) {
- updateFromData(rawJsonData);
- } else {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
- SolaxBindingConstants.I18N_KEY_OFFLINE_COMMUNICATION_ERROR_JSON_CANNOT_BE_RETRIEVED);
- }
- } catch (IOException e) {
- logger.debug("Exception received while attempting to retrieve data via HTTP", e);
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
- } finally {
- retrieveDataCallLock.unlock();
- }
- } else {
- logger.debug("Unable to retrieve data because a request is already in progress.");
- }
- }
-
- private void updateFromData(String rawJsonData) {
- try {
- LocalConnectRawDataBean rawDataBean = parseJson(rawJsonData);
- InverterType inverterType = calculateInverterType(rawDataBean);
- RawDataParser parser = inverterType.getParser();
- if (parser != null) {
- if (!alreadyRemovedUnsupportedChannels) {
- removeUnsupportedChannels(inverterType.getSupportedChannels());
- alreadyRemovedUnsupportedChannels = true;
- }
-
- InverterData genericInverterData = parser.getData(rawDataBean);
- updateChannels(parser, genericInverterData);
- updateProperties(genericInverterData);
-
- if (getThing().getStatus() != ThingStatus.ONLINE) {
- updateStatus(ThingStatus.ONLINE);
- }
- } else {
- cancelSchedule();
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
- "@text/offline.configuration-error.parser-not-implemented [\"" + inverterType.name() + "\"]");
- }
- } catch (JsonParseException e) {
- logger.debug("Unable to deserialize from JSON.", e);
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
- }
- }
-
- private LocalConnectRawDataBean parseJson(String rawJsonData) {
- LocalConnectRawDataBean inverterParsedData = LocalConnectRawDataBean.fromJson(rawJsonData);
- logger.debug("Received a new inverter JSON object. Data = {}", inverterParsedData.toString());
- return inverterParsedData;
- }
-
- private InverterType calculateInverterType(LocalConnectRawDataBean rawDataBean) {
- int type = rawDataBean.getType();
- return InverterType.fromIndex(type);
- }
-
- private void updateProperties(InverterData genericInverterData) {
- updateProperty(Thing.PROPERTY_SERIAL_NUMBER, genericInverterData.getWifiSerial());
- updateProperty(SolaxBindingConstants.PROPERTY_INVERTER_TYPE, genericInverterData.getInverterType().name());
- }
-
- private void updateChannels(RawDataParser parser, InverterData inverterData) {
- updateState(SolaxBindingConstants.CHANNEL_RAW_DATA, new StringType(inverterData.getRawData()));
-
- Set<String> supportedChannels = parser.getSupportedChannels();
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_POWER, inverterData.getPV1Power(), Units.WATT,
- supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_CURRENT, inverterData.getPV1Current(), Units.AMPERE,
- supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_VOLTAGE, inverterData.getPV1Voltage(), Units.VOLT,
- supportedChannels);
-
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_POWER, inverterData.getPV2Power(), Units.WATT,
- supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_CURRENT, inverterData.getPV2Current(), Units.AMPERE,
- supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_VOLTAGE, inverterData.getPV2Voltage(), Units.VOLT,
- supportedChannels);
-
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV_TOTAL_POWER, inverterData.getPVTotalPower(), Units.WATT,
- supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV_TOTAL_CURRENT, inverterData.getPVTotalCurrent(),
- Units.AMPERE, supportedChannels);
-
- updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_POWER, inverterData.getBatteryPower(), Units.WATT,
- supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_CURRENT, inverterData.getBatteryCurrent(), Units.AMPERE,
- supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_VOLTAGE, inverterData.getBatteryVoltage(), Units.VOLT,
- supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_TEMPERATURE, inverterData.getBatteryTemperature(),
- SIUnits.CELSIUS, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_TEMPERATURE1, inverterData.getInverterTemperature1(),
- SIUnits.CELSIUS, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_TEMPERATURE2, inverterData.getInverterTemperature2(),
- SIUnits.CELSIUS, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_STATE_OF_CHARGE, inverterData.getBatteryLevel(),
- Units.PERCENT, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_FEED_IN_POWER, inverterData.getFeedInPower(), Units.WATT,
- supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_POWER_USAGE, inverterData.getPowerUsage(), Units.WATT,
- supportedChannels);
- updateState(SolaxBindingConstants.CHANNEL_INVERTER_WORKMODE,
- new StringType(inverterData.getInverterWorkMode()));
-
- // Totals
- updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_ENERGY, inverterData.getTotalEnergy(), Units.KILOWATT_HOUR,
- supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_BATTERY_DISCHARGE_ENERGY,
- inverterData.getTotalBatteryDischargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_BATTERY_CHARGE_ENERGY,
- inverterData.getTotalBatteryChargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_PV_ENERGY, inverterData.getTotalPVEnergy(),
- Units.KILOWATT_HOUR, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_FEED_IN_ENERGY, inverterData.getTotalFeedInEnergy(),
- Units.KILOWATT_HOUR, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_CONSUMPTION, inverterData.getTotalConsumption(),
- Units.KILOWATT_HOUR, supportedChannels);
-
- // Today's
- updateChannel(SolaxBindingConstants.CHANNEL_TODAY_ENERGY, inverterData.getTodayEnergy(), Units.KILOWATT_HOUR,
- supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY,
- inverterData.getTodayBatteryDischargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_TODAY_BATTERY_CHARGE_ENERGY,
- inverterData.getTodayBatteryChargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_TODAY_FEED_IN_ENERGY, inverterData.getTodayFeedInEnergy(),
- Units.KILOWATT_HOUR, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_TODAY_CONSUMPTION, inverterData.getTodayConsumption(),
- Units.KILOWATT_HOUR, supportedChannels);
-
- // Single phase specific channels
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER, inverterData.getInverterOutputPower(),
- Units.WATT, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT, inverterData.getInverterCurrent(),
- Units.AMPERE, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE, inverterData.getInverterVoltage(),
- Units.VOLT, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY, inverterData.getInverterFrequency(),
- Units.HERTZ, supportedChannels);
-
- // Three phase specific channels
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE1, inverterData.getOutputPowerPhase1(),
- Units.WATT, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE2, inverterData.getOutputPowerPhase2(),
- Units.WATT, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE3, inverterData.getOutputPowerPhase3(),
- Units.WATT, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_TOTAL_OUTPUT_POWER, inverterData.getTotalOutputPower(),
- Units.WATT, supportedChannels);
-
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1, inverterData.getCurrentPhase1(),
- Units.AMPERE, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2, inverterData.getCurrentPhase2(),
- Units.AMPERE, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3, inverterData.getCurrentPhase3(),
- Units.AMPERE, supportedChannels);
-
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1, inverterData.getVoltagePhase1(),
- Units.VOLT, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2, inverterData.getVoltagePhase2(),
- Units.VOLT, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3, inverterData.getVoltagePhase3(),
- Units.VOLT, supportedChannels);
-
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1, inverterData.getFrequencyPhase1(),
- Units.HERTZ, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2, inverterData.getFrequencyPhase2(),
- Units.HERTZ, supportedChannels);
- updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, inverterData.getFrequencyPhase3(),
- Units.HERTZ, supportedChannels);
-
- // Binding provided data
- updateState(SolaxBindingConstants.CHANNEL_TIMESTAMP, new DateTimeType(ZonedDateTime.now()));
- }
-
- private void removeUnsupportedChannels(Set<String> supportedChannels) {
- if (supportedChannels.isEmpty()) {
- return;
- }
- List<Channel> channels = getThing().getChannels();
- List<Channel> channelsToRemove = channels.stream()
- .filter(channel -> !supportedChannels.contains(channel.getUID().getId())).toList();
-
- if (!channelsToRemove.isEmpty()) {
- if (logger.isDebugEnabled()) {
- logRemovedChannels(channelsToRemove);
- }
- updateThing(editThing().withoutChannels(channelsToRemove).build());
- }
- }
-
- private void logRemovedChannels(List<Channel> channelsToRemove) {
- List<String> channelsToRemoveForLog = channelsToRemove.stream().map(channel -> channel.getUID().getId())
- .toList();
- logger.debug("Detected unsupported channels for the current inverter. Channels to be removed: {}",
- channelsToRemoveForLog);
- }
-
- @Override
- public void handleCommand(ChannelUID channelUID, Command command) {
- if (command instanceof RefreshType) {
- scheduler.execute(this::retrieveData);
- } else {
- logger.debug("Binding {} only supports refresh command", SolaxBindingConstants.BINDING_ID);
- }
- }
-
- @Override
- public void dispose() {
- super.dispose();
- cancelSchedule();
- }
-
- private void cancelSchedule() {
- ScheduledFuture<?> schedule = this.schedule;
- if (schedule != null) {
- schedule.cancel(true);
- this.schedule = null;
- }
- }
-
- private <T extends Quantity<T>> void updateChannel(String channelID, double value, Unit<T> unit,
- Set<String> supportedChannels) {
- if (supportedChannels.contains(channelID)) {
- if (value > Short.MIN_VALUE) {
- updateState(channelID, new QuantityType<>(value, unit));
- } else if (!unsupportedExistingChannels.contains(channelID)) {
- updateState(channelID, UnDefType.UNDEF);
- unsupportedExistingChannels.add(channelID);
- logger.warn(
- "Channel {} is marked as supported, but its value is out of the defined range. Value = {}. This is unexpected behaviour. Please file a bug.",
- channelID, value);
- }
- }
- }
-}
--- /dev/null
+/**
+ * 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;
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2024 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.solax.internal.connectivity.rawdata;
-
-import java.util.Arrays;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.solax.internal.util.GsonSupplier;
-
-import com.google.gson.Gson;
-import com.google.gson.annotations.SerializedName;
-
-/**
- * The {@link LocalConnectRawDataBean} collects the raw data and the specific implementation to return the parsed data.
- * If there are differences between the inverters probably would be wise to split the parsing in seprate class(es)
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public class LocalConnectRawDataBean implements RawDataBean {
-
- private @Nullable String sn;
- private @Nullable String ver;
- private int type;
- @SerializedName("Data")
- private short @Nullable [] data;
- @SerializedName("Information")
- private String @Nullable [] information;
- private @Nullable String rawData;
-
- @Override
- public String toString() {
- return "LocalConnectRawDataBean [sn=" + sn + ", ver=" + ver + ", type=" + type + ", Information="
- + Arrays.toString(information) + ", Data=" + Arrays.toString(data) + "]";
- }
-
- public @Nullable String getSn() {
- return sn;
- }
-
- public void setSn(@Nullable String sn) {
- this.sn = sn;
- }
-
- public @Nullable String getVer() {
- return ver;
- }
-
- public void setVer(@Nullable String ver) {
- this.ver = ver;
- }
-
- public int getType() {
- return type;
- }
-
- public void setType(int type) {
- this.type = type;
- }
-
- public short @Nullable [] getData() {
- return data;
- }
-
- public void setData(short @Nullable [] data) {
- this.data = data;
- }
-
- public String @Nullable [] getInformation() {
- return information;
- }
-
- public void setInformation(String @Nullable [] information) {
- this.information = information;
- }
-
- @Override
- public @Nullable String getRawData() {
- return rawData;
- }
-
- public void setRawData(String rawData) {
- this.rawData = rawData;
- }
-
- public static LocalConnectRawDataBean fromJson(String json) {
- if (json.isEmpty()) {
- throw new IllegalArgumentException("JSON payload should not be empty");
- }
-
- Gson gson = GsonSupplier.getInstance();
- LocalConnectRawDataBean deserializedObject = gson.fromJson(json, LocalConnectRawDataBean.class);
- if (deserializedObject == null) {
- throw new IllegalStateException("Unexpected null result when deserializing JSON");
- }
- deserializedObject.setRawData(json);
- return deserializedObject;
- }
-}
public interface RawDataBean {
@Nullable
String getRawData();
+
+ public void setRawData(String rawData);
}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.solax.internal.connectivity.rawdata.local;
+
+import java.util.Arrays;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solax.internal.connectivity.rawdata.RawDataBean;
+import org.openhab.binding.solax.internal.util.GsonSupplier;
+
+import com.google.gson.Gson;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link LocalConnectRawDataBean} collects the raw data and the specific implementation to return the parsed data.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class LocalConnectRawDataBean implements RawDataBean {
+
+ private @Nullable String sn;
+ private @Nullable String ver;
+ private int type;
+ @SerializedName("Data")
+ private short @Nullable [] data;
+ @SerializedName("Information")
+ private String @Nullable [] information;
+ private @Nullable String rawData;
+
+ @Override
+ public String toString() {
+ return "LocalConnectRawDataBean [sn=" + sn + ", ver=" + ver + ", type=" + type + ", Information="
+ + Arrays.toString(information) + ", Data=" + Arrays.toString(data) + "]";
+ }
+
+ public @Nullable String getSn() {
+ return sn;
+ }
+
+ public void setSn(@Nullable String sn) {
+ this.sn = sn;
+ }
+
+ public @Nullable String getVer() {
+ return ver;
+ }
+
+ public void setVer(@Nullable String ver) {
+ this.ver = ver;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ public short @Nullable [] getData() {
+ return data;
+ }
+
+ public void setData(short @Nullable [] data) {
+ this.data = data;
+ }
+
+ public String @Nullable [] getInformation() {
+ return information;
+ }
+
+ public void setInformation(String @Nullable [] information) {
+ this.information = information;
+ }
+
+ @Override
+ public @Nullable String getRawData() {
+ return rawData;
+ }
+
+ @Override
+ public void setRawData(String rawData) {
+ this.rawData = rawData;
+ }
+
+ public static LocalConnectRawDataBean fromJson(String json) {
+ if (json.isEmpty()) {
+ throw new IllegalArgumentException("JSON payload should not be empty");
+ }
+
+ Gson gson = GsonSupplier.getInstance();
+ LocalConnectRawDataBean deserializedObject = gson.fromJson(json, LocalConnectRawDataBean.class);
+ if (deserializedObject == null) {
+ throw new IllegalStateException("Unexpected null result when deserializing JSON");
+ }
+ deserializedObject.setRawData(json);
+ return deserializedObject;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+/**
+ * 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()));
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.solax.internal.handlers;
+
+import java.time.ZonedDateTime;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.measure.Quantity;
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solax.internal.SolaxBindingConstants;
+import org.openhab.binding.solax.internal.SolaxConfiguration;
+import org.openhab.binding.solax.internal.connectivity.LocalHttpConnector;
+import org.openhab.binding.solax.internal.connectivity.SolaxConnector;
+import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
+import org.openhab.binding.solax.internal.model.InverterType;
+import org.openhab.binding.solax.internal.model.local.LocalInverterData;
+import org.openhab.binding.solax.internal.model.local.parsers.RawDataParser;
+import org.openhab.core.i18n.TimeZoneProvider;
+import org.openhab.core.i18n.TranslationProvider;
+import org.openhab.core.library.types.DateTimeType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonParseException;
+
+/**
+ * The {@link SolaxLocalAccessHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class SolaxLocalAccessHandler extends AbstractSolaxHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(SolaxLocalAccessHandler.class);
+
+ private boolean alreadyRemovedUnsupportedChannels;
+
+ private final Set<String> unsupportedExistingChannels = new HashSet<>();
+
+ public SolaxLocalAccessHandler(Thing thing, TranslationProvider i18nProvider, TimeZoneProvider timeZoneProvider) {
+ super(thing, i18nProvider, timeZoneProvider);
+ }
+
+ @Override
+ protected SolaxConnector createConnector(SolaxConfiguration config) {
+ return new LocalHttpConnector(config.password, config.hostname);
+ }
+
+ @Override
+ protected void updateFromData(String rawJsonData) {
+ try {
+ LocalConnectRawDataBean rawDataBean = parseJson(rawJsonData);
+ InverterType inverterType = calculateInverterType(rawDataBean);
+ RawDataParser parser = inverterType.getParser();
+ if (parser != null) {
+ if (!alreadyRemovedUnsupportedChannels) {
+ removeUnsupportedChannels(inverterType.getSupportedChannels());
+ alreadyRemovedUnsupportedChannels = true;
+ }
+
+ LocalInverterData genericInverterData = parser.getData(rawDataBean);
+ updateChannels(parser, genericInverterData);
+ updateProperties(genericInverterData);
+ } else {
+ cancelSchedule();
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "@text/offline.configuration-error.parser-not-implemented [\"" + inverterType.name() + "\"]");
+ }
+ } catch (JsonParseException e) {
+ logger.debug("Unable to deserialize from JSON.", e);
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ }
+ }
+
+ private LocalConnectRawDataBean parseJson(String rawJsonData) {
+ LocalConnectRawDataBean fromJson = LocalConnectRawDataBean.fromJson(rawJsonData);
+ logger.debug("Received a new inverter JSON object. Data = {}", fromJson.toString());
+ return fromJson;
+ }
+
+ private InverterType calculateInverterType(LocalConnectRawDataBean rawDataBean) {
+ int type = rawDataBean.getType();
+ return InverterType.fromIndex(type);
+ }
+
+ private void updateProperties(LocalInverterData genericInverterData) {
+ updateProperty(Thing.PROPERTY_SERIAL_NUMBER, genericInverterData.getWifiSerial());
+ updateProperty(SolaxBindingConstants.PROPERTY_INVERTER_TYPE, genericInverterData.getInverterType().name());
+ }
+
+ private void updateChannels(RawDataParser parser, LocalInverterData inverterData) {
+ updateState(SolaxBindingConstants.CHANNEL_RAW_DATA, new StringType(inverterData.getRawData()));
+
+ Set<String> supportedChannels = parser.getSupportedChannels();
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_POWER, inverterData.getPV1Power(), Units.WATT,
+ supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_CURRENT, inverterData.getPV1Current(), Units.AMPERE,
+ supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_VOLTAGE, inverterData.getPV1Voltage(), Units.VOLT,
+ supportedChannels);
+
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_POWER, inverterData.getPV2Power(), Units.WATT,
+ supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_CURRENT, inverterData.getPV2Current(), Units.AMPERE,
+ supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_VOLTAGE, inverterData.getPV2Voltage(), Units.VOLT,
+ supportedChannels);
+
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV_TOTAL_POWER, inverterData.getPVTotalPower(), Units.WATT,
+ supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV_TOTAL_CURRENT, inverterData.getPVTotalCurrent(),
+ Units.AMPERE, supportedChannels);
+
+ updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_POWER, inverterData.getBatteryPower(), Units.WATT,
+ supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_CURRENT, inverterData.getBatteryCurrent(), Units.AMPERE,
+ supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_VOLTAGE, inverterData.getBatteryVoltage(), Units.VOLT,
+ supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_TEMPERATURE, inverterData.getBatteryTemperature(),
+ SIUnits.CELSIUS, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_STATE_OF_CHARGE, inverterData.getBatteryLevel(),
+ Units.PERCENT, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_FEED_IN_POWER, inverterData.getFeedInPower(), Units.WATT,
+ supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_POWER_USAGE, inverterData.getPowerUsage(), Units.WATT,
+ supportedChannels);
+ updateState(SolaxBindingConstants.CHANNEL_INVERTER_WORKMODE,
+ new StringType(inverterData.getInverterWorkMode()));
+
+ // Totals
+ updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_ENERGY, inverterData.getTotalEnergy(), Units.KILOWATT_HOUR,
+ supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_BATTERY_DISCHARGE_ENERGY,
+ inverterData.getTotalBatteryDischargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_BATTERY_CHARGE_ENERGY,
+ inverterData.getTotalBatteryChargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_PV_ENERGY, inverterData.getTotalPVEnergy(),
+ Units.KILOWATT_HOUR, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_FEED_IN_ENERGY, inverterData.getTotalFeedInEnergy(),
+ Units.KILOWATT_HOUR, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_CONSUMPTION, inverterData.getTotalConsumption(),
+ Units.KILOWATT_HOUR, supportedChannels);
+
+ // Today's
+ updateChannel(SolaxBindingConstants.CHANNEL_TODAY_ENERGY, inverterData.getTodayEnergy(), Units.KILOWATT_HOUR,
+ supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY,
+ inverterData.getTodayBatteryDischargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_TODAY_BATTERY_CHARGE_ENERGY,
+ inverterData.getTodayBatteryChargeEnergy(), Units.KILOWATT_HOUR, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_TODAY_FEED_IN_ENERGY, inverterData.getTodayFeedInEnergy(),
+ Units.KILOWATT_HOUR, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_TODAY_CONSUMPTION, inverterData.getTodayConsumption(),
+ Units.KILOWATT_HOUR, supportedChannels);
+
+ // Single phase specific channels
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER, inverterData.getInverterOutputPower(),
+ Units.WATT, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT, inverterData.getInverterCurrent(),
+ Units.AMPERE, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE, inverterData.getInverterVoltage(),
+ Units.VOLT, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY, inverterData.getInverterFrequency(),
+ Units.HERTZ, supportedChannels);
+
+ // Three phase specific channels
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE1, inverterData.getOutputPowerPhase1(),
+ Units.WATT, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE2, inverterData.getOutputPowerPhase2(),
+ Units.WATT, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE3, inverterData.getOutputPowerPhase3(),
+ Units.WATT, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_TOTAL_OUTPUT_POWER, inverterData.getTotalOutputPower(),
+ Units.WATT, supportedChannels);
+
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1, inverterData.getCurrentPhase1(),
+ Units.AMPERE, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2, inverterData.getCurrentPhase2(),
+ Units.AMPERE, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3, inverterData.getCurrentPhase3(),
+ Units.AMPERE, supportedChannels);
+
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1, inverterData.getVoltagePhase1(),
+ Units.VOLT, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2, inverterData.getVoltagePhase2(),
+ Units.VOLT, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3, inverterData.getVoltagePhase3(),
+ Units.VOLT, supportedChannels);
+
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1, inverterData.getFrequencyPhase1(),
+ Units.HERTZ, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2, inverterData.getFrequencyPhase2(),
+ Units.HERTZ, supportedChannels);
+ updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, inverterData.getFrequencyPhase3(),
+ Units.HERTZ, supportedChannels);
+
+ // Binding provided data
+ updateState(SolaxBindingConstants.CHANNEL_TIMESTAMP, new DateTimeType(ZonedDateTime.now()));
+ }
+
+ private void removeUnsupportedChannels(Set<String> supportedChannels) {
+ if (supportedChannels.isEmpty()) {
+ return;
+ }
+ List<Channel> channels = getThing().getChannels();
+ List<Channel> channelsToRemove = channels.stream()
+ .filter(channel -> !supportedChannels.contains(channel.getUID().getId())).toList();
+
+ if (!channelsToRemove.isEmpty()) {
+ if (logger.isDebugEnabled()) {
+ logRemovedChannels(channelsToRemove);
+ }
+ updateThing(editThing().withoutChannels(channelsToRemove).build());
+ }
+ }
+
+ private void logRemovedChannels(List<Channel> channelsToRemove) {
+ List<String> channelsToRemoveForLog = channelsToRemove.stream().map(channel -> channel.getUID().getId())
+ .toList();
+ logger.debug("Detected unsupported channels for the current inverter. Channels to be removed: {}",
+ channelsToRemoveForLog);
+ }
+
+ private <T extends Quantity<T>> void updateChannel(String channelID, double value, Unit<T> unit,
+ Set<String> supportedChannels) {
+ if (supportedChannels.contains(channelID)) {
+ if (value > Short.MIN_VALUE) {
+ updateState(channelID, new QuantityType<>(value, unit));
+ } else if (!unsupportedExistingChannels.contains(channelID)) {
+ updateState(channelID, UnDefType.UNDEF);
+ unsupportedExistingChannels.add(channelID);
+ logger.warn(
+ "Channel {} is marked as supported, but its value is out of the defined range. Value = {}. This is unexpected behaviour. Please file a bug.",
+ channelID, value);
+ }
+ }
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2024 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.solax.internal.model;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-
-/**
- * The {@link InverterData} Interface for the parsed inverter data in meaningful format
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public interface InverterData {
-
- @Nullable
- String getWifiSerial();
-
- @Nullable
- String getWifiVersion();
-
- InverterType getInverterType();
-
- @Nullable
- String getRawData();
-
- default double getPV1Voltage() {
- return Short.MIN_VALUE;
- }
-
- default double getPV1Current() {
- return Short.MIN_VALUE;
- }
-
- default short getPV1Power() {
- return Short.MIN_VALUE;
- }
-
- default double getPV2Voltage() {
- return Short.MIN_VALUE;
- }
-
- default double getPV2Current() {
- return Short.MIN_VALUE;
- }
-
- default short getPV2Power() {
- return Short.MIN_VALUE;
- }
-
- default double getPVTotalPower() {
- return getPV1Power() + getPV2Power();
- }
-
- default double getPVTotalCurrent() {
- return getPV1Current() + getPV2Current();
- }
-
- default double getBatteryVoltage() {
- return Short.MIN_VALUE;
- }
-
- default double getBatteryCurrent() {
- return Short.MIN_VALUE;
- }
-
- default short getBatteryPower() {
- return Short.MIN_VALUE;
- }
-
- default short getBatteryTemperature() {
- return Short.MIN_VALUE;
- }
-
- default short getInverterTemperature1() {
- return Short.MIN_VALUE;
- }
-
- default short getInverterTemperature2() {
- return Short.MIN_VALUE;
- }
-
- default short getInverterWorkModeCode() {
- return Short.MIN_VALUE;
- }
-
- default String getInverterWorkMode() {
- return String.valueOf(getInverterWorkModeCode());
- }
-
- default short getBatteryLevel() {
- return Short.MIN_VALUE;
- }
-
- default short getFeedInPower() {
- return Short.MIN_VALUE;
- }
-
- default short getPowerUsage() {
- return Short.MIN_VALUE;
- }
-
- default double getTotalEnergy() {
- return Short.MIN_VALUE;
- }
-
- default short getTotalBatteryDischargeEnergy() {
- return Short.MIN_VALUE;
- }
-
- default short getTotalBatteryChargeEnergy() {
- return Short.MIN_VALUE;
- }
-
- default double getTotalPVEnergy() {
- return Short.MIN_VALUE;
- }
-
- default short getTotalFeedInEnergy() {
- return Short.MIN_VALUE;
- }
-
- default double getTotalConsumption() {
- return Short.MIN_VALUE;
- }
-
- default double getTodayEnergy() {
- return Short.MIN_VALUE;
- }
-
- default double getTodayFeedInEnergy() {
- return Short.MIN_VALUE;
- }
-
- default double getTodayConsumption() {
- return Short.MIN_VALUE;
- }
-
- default double getTodayBatteryDischargeEnergy() {
- return Short.MIN_VALUE;
- }
-
- default double getTodayBatteryChargeEnergy() {
- return Short.MIN_VALUE;
- }
-
- default double getInverterVoltage() {
- return Short.MIN_VALUE;
- }
-
- default double getInverterCurrent() {
- return Short.MIN_VALUE;
- }
-
- default short getInverterOutputPower() {
- return Short.MIN_VALUE;
- }
-
- default double getInverterFrequency() {
- return Short.MIN_VALUE;
- }
-
- default double getVoltagePhase1() {
- return Short.MIN_VALUE;
- }
-
- default double getVoltagePhase2() {
- return Short.MIN_VALUE;
- }
-
- default double getVoltagePhase3() {
- return Short.MIN_VALUE;
- }
-
- default double getCurrentPhase1() {
- return Short.MIN_VALUE;
- }
-
- default double getCurrentPhase2() {
- return Short.MIN_VALUE;
- }
-
- default double getCurrentPhase3() {
- return Short.MIN_VALUE;
- }
-
- default short getOutputPowerPhase1() {
- return Short.MIN_VALUE;
- }
-
- default short getOutputPowerPhase2() {
- return Short.MIN_VALUE;
- }
-
- default short getOutputPowerPhase3() {
- return Short.MIN_VALUE;
- }
-
- default short getTotalOutputPower() {
- return Short.MIN_VALUE;
- }
-
- default double getFrequencyPhase1() {
- return Short.MIN_VALUE;
- }
-
- default double getFrequencyPhase2() {
- return Short.MIN_VALUE;
- }
-
- default double getFrequencyPhase3() {
- return Short.MIN_VALUE;
- }
-
- default String toStringDetailed() {
- return "WifiSerial = " + getWifiSerial() + ", WifiVersion = " + getWifiVersion() + ", InverterType = "
- + getInverterType() + ", BatteryPower = " + getBatteryPower() + "W, Battery SoC = " + getBatteryLevel()
- + "%, FeedIn Power = " + getFeedInPower() + "W, Total PV Power = " + (getPV1Power() + getPV2Power())
- + "W";
- }
-}
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
--- /dev/null
+/**
+ * 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);
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2024 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.solax.internal.model.impl;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
-import org.openhab.binding.solax.internal.model.InverterData;
-import org.openhab.binding.solax.internal.model.InverterType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link CommonInverterData} is an abstract class that contains the common information, applicable for all
- * inverters.
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public abstract class CommonInverterData implements InverterData {
-
- private final Logger logger = LoggerFactory.getLogger(CommonInverterData.class);
-
- private LocalConnectRawDataBean data;
-
- public CommonInverterData(LocalConnectRawDataBean data) {
- this.data = data;
- }
-
- @Override
- public @Nullable String getRawData() {
- return data.getRawData();
- }
-
- @Override
- public @Nullable String getWifiSerial() {
- return data.getSn();
- }
-
- @Override
- public @Nullable String getWifiVersion() {
- return data.getVer();
- }
-
- @Override
- public InverterType getInverterType() {
- return InverterType.fromIndex(data.getType());
- }
-
- protected short getData(int index) {
- try {
- short[] dataArray = data.getData();
- if (dataArray != null) {
- return dataArray[index];
- }
- } catch (IndexOutOfBoundsException e) {
- logger.debug("Tried to get data out of bounds of the raw data array.", e);
- }
- return 0;
- }
-
- public long packU16(int indexMajor, int indexMinor) {
- short major = getData(indexMajor);
- short minor = getData(indexMinor);
- if (major == 0) {
- return minor;
- }
-
- return Integer.toUnsignedLong(major << 16 | minor & 0xFFFF);
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2024 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.solax.internal.model.impl;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
-
-/**
- * The {@link X1HybridG4InverterData} is an implementation of the single phased inverter data interface for X1 Hybrid G4
- * inverter.
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public class X1HybridG4InverterData extends CommonInverterData {
-
- public X1HybridG4InverterData(LocalConnectRawDataBean data) {
- super(data);
- }
-
- @Override
- public double getInverterVoltage() {
- return ((double) getData(0)) / 10;
- }
-
- @Override
- public double getInverterCurrent() {
- return ((double) getData(1)) / 10;
- }
-
- @Override
- public short getInverterOutputPower() {
- return getData(2);
- }
-
- @Override
- public double getInverterFrequency() {
- return ((double) getData(3)) / 100;
- }
-
- @Override
- public short getFeedInPower() {
- return getData(32);
- }
-
- @Override
- public double getPV1Voltage() {
- return ((double) getData(4)) / 10;
- }
-
- @Override
- public double getPV2Voltage() {
- return ((double) getData(5)) / 10;
- }
-
- @Override
- public double getPV1Current() {
- return ((double) getData(6)) / 10;
- }
-
- @Override
- public double getPV2Current() {
- return ((double) getData(7)) / 10;
- }
-
- @Override
- public short getPV1Power() {
- return getData(8);
- }
-
- @Override
- public short getPV2Power() {
- return getData(9);
- }
-
- @Override
- public short getInverterWorkModeCode() {
- return getData(10);
- }
-
- @Override
- public double getBatteryVoltage() {
- return ((double) getData(14)) / 100;
- }
-
- @Override
- public double getBatteryCurrent() {
- return ((double) getData(15)) / 100;
- }
-
- @Override
- public short getBatteryPower() {
- return getData(16);
- }
-
- @Override
- public short getBatteryTemperature() {
- return getData(17);
- }
-
- @Override
- public short getBatteryLevel() {
- return getData(18);
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2024 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.solax.internal.model.impl;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
-
-/**
- * The {@link X3HybridG4InverterData} is responsible for handling commands, which are
- * sent to one of the channels.
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public class X3HybridG4InverterData extends CommonInverterData {
-
- public X3HybridG4InverterData(LocalConnectRawDataBean data) {
- super(data);
- }
-
- // Inverter data
-
- @Override
- public double getVoltagePhase1() {
- return ((double) getData(0)) / 10;
- }
-
- @Override
- public double getVoltagePhase2() {
- return ((double) getData(1)) / 10;
- }
-
- @Override
- public double getVoltagePhase3() {
- return ((double) getData(2)) / 10;
- }
-
- @Override
- public double getCurrentPhase1() {
- return ((double) getData(3)) / 10;
- }
-
- @Override
- public double getCurrentPhase2() {
- return ((double) getData(4)) / 10;
- }
-
- @Override
- public double getCurrentPhase3() {
- return ((double) getData(5)) / 10;
- }
-
- @Override
- public short getOutputPowerPhase1() {
- return getData(6);
- }
-
- @Override
- public short getOutputPowerPhase2() {
- return getData(7);
- }
-
- @Override
- public short getOutputPowerPhase3() {
- return getData(8);
- }
-
- @Override
- public short getTotalOutputPower() {
- return getData(9);
- }
-
- @Override
- public double getPV1Voltage() {
- return ((double) getData(10)) / 10;
- }
-
- @Override
- public double getPV2Voltage() {
- return ((double) getData(11)) / 10;
- }
-
- @Override
- public double getPV1Current() {
- return ((double) getData(12)) / 10;
- }
-
- @Override
- public double getPV2Current() {
- return ((double) getData(13)) / 10;
- }
-
- @Override
- public short getPV1Power() {
- return getData(14);
- }
-
- @Override
- public short getPV2Power() {
- return getData(15);
- }
-
- @Override
- public double getFrequencyPhase1() {
- return ((double) getData(16)) / 100;
- }
-
- @Override
- public double getFrequencyPhase2() {
- return ((double) getData(17)) / 100;
- }
-
- @Override
- public double getFrequencyPhase3() {
- return ((double) getData(18)) / 100;
- }
-
- @Override
- public short getInverterWorkModeCode() {
- return getData(19);
- }
-
- // Battery
-
- @Override
- public double getBatteryVoltage() {
- return ((double) getData(39)) / 100;
- }
-
- @Override
- public double getBatteryCurrent() {
- return ((double) getData(40)) / 100;
- }
-
- @Override
- public short getBatteryPower() {
- return getData(41);
- }
-
- @Override
- public short getBatteryTemperature() {
- return getData(105);
- }
-
- @Override
- public short getBatteryLevel() {
- return getData(103);
- }
-
- // Feed in power
-
- @Override
- public short getFeedInPower() {
- return (short) (getData(34) - getData(35));
- }
-
- // Totals
-
- @Override
- public short getPowerUsage() {
- return getData(47);
- }
-
- @Override
- public double getTotalEnergy() {
- return ((double) getData(68)) / 10;
- }
-
- @Override
- public short getTotalBatteryDischargeEnergy() {
- return getData(74);
- }
-
- @Override
- public short getTotalBatteryChargeEnergy() {
- return getData(76);
- }
-
- @Override
- public double getTotalPVEnergy() {
- return ((double) getData(80)) / 10;
- }
-
- @Override
- public short getTotalFeedInEnergy() {
- return getData(86);
- }
-
- @Override
- public double getTotalConsumption() {
- return ((double) getData(88)) / 10;
- }
-
- @Override
- public double getTodayEnergy() {
- return ((double) getData(82)) / 10;
- }
-
- @Override
- public double getTodayFeedInEnergy() {
- return ((double) getData(90)) / 100;
- }
-
- @Override
- public double getTodayConsumption() {
- return ((double) getData(92)) / 100;
- }
-
- @Override
- public double getTodayBatteryDischargeEnergy() {
- return ((double) getData(78)) / 10;
- }
-
- @Override
- public double getTodayBatteryChargeEnergy() {
- return ((double) getData(79)) / 10;
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2024 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.solax.internal.model.impl;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
-
-/**
- * The {@link X3HybridG4InverterData} is responsible for handling commands, which are
- * sent to one of the channels.
- *
- * @author Henrik Tóth - Initial contribution
- * (based on X1/X3 G4 parser from Konstantin Polihronov)
- */
-@NonNullByDefault
-public class X3MicOrProG2InverterData extends CommonInverterData {
-
- public X3MicOrProG2InverterData(LocalConnectRawDataBean data) {
- super(data);
- }
-
- // Inverter data
-
- @Override
- public double getVoltagePhase1() {
- return ((double) getData(0)) / 10;
- }
-
- @Override
- public double getVoltagePhase2() {
- return ((double) getData(1)) / 10;
- }
-
- @Override
- public double getVoltagePhase3() {
- return ((double) getData(2)) / 10;
- }
-
- @Override
- public double getCurrentPhase1() {
- return ((double) getData(3)) / 10;
- }
-
- @Override
- public double getCurrentPhase2() {
- return ((double) getData(4)) / 10;
- }
-
- @Override
- public double getCurrentPhase3() {
- return ((double) getData(5)) / 10;
- }
-
- @Override
- public short getOutputPowerPhase1() {
- return getData(6);
- }
-
- @Override
- public short getOutputPowerPhase2() {
- return getData(7);
- }
-
- @Override
- public short getOutputPowerPhase3() {
- return getData(8);
- }
-
- @Override
- public double getPV1Voltage() {
- return ((double) getData(9)) / 10;
- }
-
- @Override
- public double getPV2Voltage() {
- return ((double) getData(10)) / 10;
- }
-
- @Override
- public double getPV1Current() {
- return ((double) getData(12)) / 10;
- }
-
- @Override
- public double getPV2Current() {
- return ((double) getData(13)) / 10;
- }
-
- @Override
- public short getPV1Power() {
- return getData(15);
- }
-
- @Override
- public short getPV2Power() {
- return getData(16);
- }
-
- @Override
- public double getFrequencyPhase1() {
- return ((double) getData(18)) / 100;
- }
-
- @Override
- public double getFrequencyPhase2() {
- return ((double) getData(19)) / 100;
- }
-
- @Override
- public double getFrequencyPhase3() {
- return ((double) getData(20)) / 100;
- }
-
- @Override
- public short getInverterWorkModeCode() {
- return getData(21);
- }
-
- @Override
- public double getTotalEnergy() {
- return ((double) getData(22)) / 10;
- }
-
- @Override
- public double getTodayEnergy() {
- return ((double) getData(24)) / 10;
- }
-
- @Override
- public short getInverterTemperature1() {
- return getData(26);
- }
-
- @Override
- public short getInverterTemperature2() {
- return getData(27);
- }
-
- @Override
- public short getTotalOutputPower() {
- return getData(78);
- }
-}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.solax.internal.model.local;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
+import org.openhab.binding.solax.internal.model.InverterType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link CommonLocalInverterData} is an abstract class that contains the common information, applicable for all
+ * inverters.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public abstract class CommonLocalInverterData implements LocalInverterData {
+
+ private final Logger logger = LoggerFactory.getLogger(CommonLocalInverterData.class);
+
+ private LocalConnectRawDataBean data;
+
+ public CommonLocalInverterData(LocalConnectRawDataBean data) {
+ this.data = data;
+ }
+
+ @Override
+ public @Nullable String getRawData() {
+ return data.getRawData();
+ }
+
+ @Override
+ public @Nullable String getWifiSerial() {
+ return data.getSn();
+ }
+
+ @Override
+ public @Nullable String getWifiVersion() {
+ return data.getVer();
+ }
+
+ @Override
+ public InverterType getInverterType() {
+ return InverterType.fromIndex(data.getType());
+ }
+
+ protected short getData(int index) {
+ try {
+ short[] dataArray = data.getData();
+ if (dataArray != null) {
+ return dataArray[index];
+ }
+ } catch (IndexOutOfBoundsException e) {
+ logger.debug("Tried to get data out of bounds of the raw data array.", e);
+ }
+ return 0;
+ }
+
+ public long packU16(int indexMajor, int indexMinor) {
+ short major = getData(indexMajor);
+ short minor = getData(indexMinor);
+ if (major == 0) {
+ return minor;
+ }
+
+ return Integer.toUnsignedLong(major << 16 | minor & 0xFFFF);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.solax.internal.model.local;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solax.internal.model.InverterType;
+
+/**
+ * The {@link LocalInverterData} Interface for the parsed inverter data in meaningful format
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public interface LocalInverterData {
+
+ @Nullable
+ String getWifiSerial();
+
+ @Nullable
+ String getWifiVersion();
+
+ InverterType getInverterType();
+
+ @Nullable
+ String getRawData();
+
+ default double getPV1Voltage() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getPV1Current() {
+ return Short.MIN_VALUE;
+ }
+
+ default short getPV1Power() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getPV2Voltage() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getPV2Current() {
+ return Short.MIN_VALUE;
+ }
+
+ default short getPV2Power() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getPVTotalPower() {
+ return getPV1Power() + getPV2Power();
+ }
+
+ default double getPVTotalCurrent() {
+ return getPV1Current() + getPV2Current();
+ }
+
+ default double getBatteryVoltage() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getBatteryCurrent() {
+ return Short.MIN_VALUE;
+ }
+
+ default short getBatteryPower() {
+ return Short.MIN_VALUE;
+ }
+
+ default short getBatteryTemperature() {
+ return Short.MIN_VALUE;
+ }
+
+ default short getInverterTemperature1() {
+ return Short.MIN_VALUE;
+ }
+
+ default short getInverterTemperature2() {
+ return Short.MIN_VALUE;
+ }
+
+ default short getBatteryLevel() {
+ return Short.MIN_VALUE;
+ }
+
+ default short getFeedInPower() {
+ return Short.MIN_VALUE;
+ }
+
+ default short getPowerUsage() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getTotalEnergy() {
+ return Short.MIN_VALUE;
+ }
+
+ default short getTotalBatteryDischargeEnergy() {
+ return Short.MIN_VALUE;
+ }
+
+ default short getTotalBatteryChargeEnergy() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getTotalPVEnergy() {
+ return Short.MIN_VALUE;
+ }
+
+ default short getTotalFeedInEnergy() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getTotalConsumption() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getTodayEnergy() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getTodayFeedInEnergy() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getTodayConsumption() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getTodayBatteryDischargeEnergy() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getTodayBatteryChargeEnergy() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getInverterVoltage() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getInverterCurrent() {
+ return Short.MIN_VALUE;
+ }
+
+ default short getInverterOutputPower() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getInverterFrequency() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getVoltagePhase1() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getVoltagePhase2() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getVoltagePhase3() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getCurrentPhase1() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getCurrentPhase2() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getCurrentPhase3() {
+ return Short.MIN_VALUE;
+ }
+
+ default short getOutputPowerPhase1() {
+ return Short.MIN_VALUE;
+ }
+
+ default short getOutputPowerPhase2() {
+ return Short.MIN_VALUE;
+ }
+
+ default short getOutputPowerPhase3() {
+ return Short.MIN_VALUE;
+ }
+
+ default short getTotalOutputPower() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getFrequencyPhase1() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getFrequencyPhase2() {
+ return Short.MIN_VALUE;
+ }
+
+ default double getFrequencyPhase3() {
+ return Short.MIN_VALUE;
+ }
+
+ default short getInverterWorkModeCode() {
+ return Short.MIN_VALUE;
+ }
+
+ default String getInverterWorkMode() {
+ return String.valueOf(getInverterWorkModeCode());
+ }
+
+ default String toStringDetailed() {
+ return "WifiSerial = " + getWifiSerial() + ", WifiVersion = " + getWifiVersion() + ", InverterType = "
+ + getInverterType() + ", BatteryPower = " + getBatteryPower() + "W, Battery SoC = " + getBatteryLevel()
+ + "%, FeedIn Power = " + getFeedInPower() + "W, Total PV Power = " + (getPV1Power() + getPV2Power())
+ + "W";
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.solax.internal.model.local;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
+
+/**
+ * The {@link X1HybridG4InverterData} is an implementation of the single phased inverter data interface for X1 Hybrid G4
+ * inverter.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class X1HybridG4InverterData extends CommonLocalInverterData {
+
+ public X1HybridG4InverterData(LocalConnectRawDataBean data) {
+ super(data);
+ }
+
+ @Override
+ public double getInverterVoltage() {
+ return ((double) getData(0)) / 10;
+ }
+
+ @Override
+ public double getInverterCurrent() {
+ return ((double) getData(1)) / 10;
+ }
+
+ @Override
+ public short getInverterOutputPower() {
+ return getData(2);
+ }
+
+ @Override
+ public double getInverterFrequency() {
+ return ((double) getData(3)) / 100;
+ }
+
+ @Override
+ public short getFeedInPower() {
+ return getData(32);
+ }
+
+ @Override
+ public double getPV1Voltage() {
+ return ((double) getData(4)) / 10;
+ }
+
+ @Override
+ public double getPV2Voltage() {
+ return ((double) getData(5)) / 10;
+ }
+
+ @Override
+ public double getPV1Current() {
+ return ((double) getData(6)) / 10;
+ }
+
+ @Override
+ public double getPV2Current() {
+ return ((double) getData(7)) / 10;
+ }
+
+ @Override
+ public short getPV1Power() {
+ return getData(8);
+ }
+
+ @Override
+ public short getPV2Power() {
+ return getData(9);
+ }
+
+ @Override
+ public double getBatteryVoltage() {
+ return ((double) getData(14)) / 100;
+ }
+
+ @Override
+ public double getBatteryCurrent() {
+ return ((double) getData(15)) / 100;
+ }
+
+ @Override
+ public short getBatteryPower() {
+ return getData(16);
+ }
+
+ @Override
+ public short getBatteryTemperature() {
+ return getData(17);
+ }
+
+ @Override
+ public short getBatteryLevel() {
+ return getData(18);
+ }
+
+ @Override
+ public short getInverterWorkModeCode() {
+ return getData(10);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.solax.internal.model.local;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
+
+/**
+ * The {@link X3HybridG4InverterData} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class X3HybridG4InverterData extends CommonLocalInverterData {
+
+ public X3HybridG4InverterData(LocalConnectRawDataBean data) {
+ super(data);
+ }
+
+ // Inverter data
+
+ @Override
+ public double getVoltagePhase1() {
+ return ((double) getData(0)) / 10;
+ }
+
+ @Override
+ public double getVoltagePhase2() {
+ return ((double) getData(1)) / 10;
+ }
+
+ @Override
+ public double getVoltagePhase3() {
+ return ((double) getData(2)) / 10;
+ }
+
+ @Override
+ public double getCurrentPhase1() {
+ return ((double) getData(3)) / 10;
+ }
+
+ @Override
+ public double getCurrentPhase2() {
+ return ((double) getData(4)) / 10;
+ }
+
+ @Override
+ public double getCurrentPhase3() {
+ return ((double) getData(5)) / 10;
+ }
+
+ @Override
+ public short getOutputPowerPhase1() {
+ return getData(6);
+ }
+
+ @Override
+ public short getOutputPowerPhase2() {
+ return getData(7);
+ }
+
+ @Override
+ public short getOutputPowerPhase3() {
+ return getData(8);
+ }
+
+ @Override
+ public short getTotalOutputPower() {
+ return getData(9);
+ }
+
+ @Override
+ public double getPV1Voltage() {
+ return ((double) getData(10)) / 10;
+ }
+
+ @Override
+ public double getPV2Voltage() {
+ return ((double) getData(11)) / 10;
+ }
+
+ @Override
+ public double getPV1Current() {
+ return ((double) getData(12)) / 10;
+ }
+
+ @Override
+ public double getPV2Current() {
+ return ((double) getData(13)) / 10;
+ }
+
+ @Override
+ public short getPV1Power() {
+ return getData(14);
+ }
+
+ @Override
+ public short getPV2Power() {
+ return getData(15);
+ }
+
+ @Override
+ public double getFrequencyPhase1() {
+ return ((double) getData(16)) / 100;
+ }
+
+ @Override
+ public double getFrequencyPhase2() {
+ return ((double) getData(17)) / 100;
+ }
+
+ @Override
+ public double getFrequencyPhase3() {
+ return ((double) getData(18)) / 100;
+ }
+
+ // Battery
+
+ @Override
+ public double getBatteryVoltage() {
+ return ((double) getData(39)) / 100;
+ }
+
+ @Override
+ public double getBatteryCurrent() {
+ return ((double) getData(40)) / 100;
+ }
+
+ @Override
+ public short getBatteryPower() {
+ return getData(41);
+ }
+
+ @Override
+ public short getBatteryTemperature() {
+ return getData(105);
+ }
+
+ @Override
+ public short getBatteryLevel() {
+ return getData(103);
+ }
+
+ // Feed in power
+
+ @Override
+ public short getFeedInPower() {
+ return (short) (getData(34) - getData(35));
+ }
+
+ // Totals
+
+ @Override
+ public short getPowerUsage() {
+ return getData(47);
+ }
+
+ @Override
+ public double getTotalEnergy() {
+ return ((double) getData(68)) / 10;
+ }
+
+ @Override
+ public short getTotalBatteryDischargeEnergy() {
+ return getData(74);
+ }
+
+ @Override
+ public short getTotalBatteryChargeEnergy() {
+ return getData(76);
+ }
+
+ @Override
+ public double getTotalPVEnergy() {
+ return ((double) getData(80)) / 10;
+ }
+
+ @Override
+ public short getTotalFeedInEnergy() {
+ return getData(86);
+ }
+
+ @Override
+ public double getTotalConsumption() {
+ return ((double) getData(88)) / 10;
+ }
+
+ @Override
+ public double getTodayEnergy() {
+ return ((double) getData(82)) / 10;
+ }
+
+ @Override
+ public double getTodayFeedInEnergy() {
+ return ((double) getData(90)) / 100;
+ }
+
+ @Override
+ public double getTodayConsumption() {
+ return ((double) getData(92)) / 100;
+ }
+
+ @Override
+ public double getTodayBatteryDischargeEnergy() {
+ return ((double) getData(78)) / 10;
+ }
+
+ @Override
+ public double getTodayBatteryChargeEnergy() {
+ return ((double) getData(79)) / 10;
+ }
+
+ @Override
+ public short getInverterWorkModeCode() {
+ return getData(19);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.solax.internal.model.local;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
+
+/**
+ * The {@link X3HybridG4InverterData} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Henrik Tóth - Initial contribution
+ * (based on X1/X3 G4 parser from Konstantin Polihronov)
+ */
+@NonNullByDefault
+public class X3MicOrProG2InverterData extends CommonLocalInverterData {
+
+ public X3MicOrProG2InverterData(LocalConnectRawDataBean data) {
+ super(data);
+ }
+
+ // Inverter data
+
+ @Override
+ public double getVoltagePhase1() {
+ return ((double) getData(0)) / 10;
+ }
+
+ @Override
+ public double getVoltagePhase2() {
+ return ((double) getData(1)) / 10;
+ }
+
+ @Override
+ public double getVoltagePhase3() {
+ return ((double) getData(2)) / 10;
+ }
+
+ @Override
+ public double getCurrentPhase1() {
+ return ((double) getData(3)) / 10;
+ }
+
+ @Override
+ public double getCurrentPhase2() {
+ return ((double) getData(4)) / 10;
+ }
+
+ @Override
+ public double getCurrentPhase3() {
+ return ((double) getData(5)) / 10;
+ }
+
+ @Override
+ public short getOutputPowerPhase1() {
+ return getData(6);
+ }
+
+ @Override
+ public short getOutputPowerPhase2() {
+ return getData(7);
+ }
+
+ @Override
+ public short getOutputPowerPhase3() {
+ return getData(8);
+ }
+
+ @Override
+ public double getPV1Voltage() {
+ return ((double) getData(9)) / 10;
+ }
+
+ @Override
+ public double getPV2Voltage() {
+ return ((double) getData(10)) / 10;
+ }
+
+ @Override
+ public double getPV1Current() {
+ return ((double) getData(12)) / 10;
+ }
+
+ @Override
+ public double getPV2Current() {
+ return ((double) getData(13)) / 10;
+ }
+
+ @Override
+ public short getPV1Power() {
+ return getData(15);
+ }
+
+ @Override
+ public short getPV2Power() {
+ return getData(16);
+ }
+
+ @Override
+ public double getFrequencyPhase1() {
+ return ((double) getData(18)) / 100;
+ }
+
+ @Override
+ public double getFrequencyPhase2() {
+ return ((double) getData(19)) / 100;
+ }
+
+ @Override
+ public double getFrequencyPhase3() {
+ return ((double) getData(20)) / 100;
+ }
+
+ @Override
+ public short getInverterWorkModeCode() {
+ return getData(21);
+ }
+
+ @Override
+ public double getTotalEnergy() {
+ return ((double) getData(22)) / 10;
+ }
+
+ @Override
+ public double getTodayEnergy() {
+ return ((double) getData(24)) / 10;
+ }
+
+ @Override
+ public short getInverterTemperature1() {
+ return getData(26);
+ }
+
+ @Override
+ public short getInverterTemperature2() {
+ return getData(27);
+ }
+
+ @Override
+ public short getTotalOutputPower() {
+ return getData(78);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.solax.internal.model.local.parsers;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
+import org.openhab.binding.solax.internal.model.local.LocalInverterData;
+
+/**
+ * The {@link RawDataParser} declares generic parser implementation that parses raw data to generic inverter data which
+ * is common for all inverters.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public interface RawDataParser {
+
+ LocalInverterData getData(LocalConnectRawDataBean bean);
+
+ Set<String> getSupportedChannels();
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.solax.internal.model.local.parsers;
+
+import static org.openhab.binding.solax.internal.SolaxBindingConstants.*;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
+import org.openhab.binding.solax.internal.model.local.LocalInverterData;
+import org.openhab.binding.solax.internal.model.local.X1HybridG4InverterData;
+
+/**
+ * The {@link SinglePhaseDataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the
+ * X1 Hybrid G4 inverter.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class X1HybridG4DataParser implements RawDataParser {
+
+ private static final Set<String> X1_HYBRID_G4_SUPPORTED_CHANNELS = Set.of(CHANNEL_INVERTER_PV1_POWER,
+ CHANNEL_INVERTER_PV1_VOLTAGE, CHANNEL_INVERTER_PV1_CURRENT, CHANNEL_INVERTER_PV2_POWER,
+ CHANNEL_INVERTER_PV2_VOLTAGE, CHANNEL_INVERTER_PV2_CURRENT, CHANNEL_INVERTER_PV_TOTAL_POWER,
+ CHANNEL_INVERTER_PV_TOTAL_CURRENT, CHANNEL_BATTERY_POWER, CHANNEL_BATTERY_VOLTAGE, CHANNEL_BATTERY_CURRENT,
+ CHANNEL_BATTERY_TEMPERATURE, CHANNEL_BATTERY_STATE_OF_CHARGE, CHANNEL_FEED_IN_POWER, CHANNEL_TIMESTAMP,
+ CHANNEL_RAW_DATA, CHANNEL_INVERTER_OUTPUT_POWER, CHANNEL_INVERTER_OUTPUT_CURRENT,
+ CHANNEL_INVERTER_OUTPUT_VOLTAGE, CHANNEL_INVERTER_OUTPUT_FREQUENCY, CHANNEL_INVERTER_WORKMODE);
+
+ @Override
+ public LocalInverterData getData(LocalConnectRawDataBean rawData) {
+ return new X1HybridG4InverterData(rawData);
+ }
+
+ @Override
+ public Set<String> getSupportedChannels() {
+ return X1_HYBRID_G4_SUPPORTED_CHANNELS;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.solax.internal.model.local.parsers;
+
+import static org.openhab.binding.solax.internal.SolaxBindingConstants.*;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
+import org.openhab.binding.solax.internal.model.local.LocalInverterData;
+import org.openhab.binding.solax.internal.model.local.X3HybridG4InverterData;
+
+/**
+ * The {@link X3HybridG4DataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the
+ * X3 Hybrid G4 inverter.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class X3HybridG4DataParser implements RawDataParser {
+
+ private static final Set<String> X3_HYBRID_G4_SUPPORTED_CHANNELS = Set.of(CHANNEL_INVERTER_PV1_POWER,
+ CHANNEL_INVERTER_PV1_VOLTAGE, CHANNEL_INVERTER_PV1_CURRENT, CHANNEL_INVERTER_PV2_POWER,
+ CHANNEL_INVERTER_PV2_VOLTAGE, CHANNEL_INVERTER_PV2_CURRENT, CHANNEL_INVERTER_PV_TOTAL_POWER,
+ CHANNEL_INVERTER_PV_TOTAL_CURRENT, CHANNEL_BATTERY_POWER, CHANNEL_BATTERY_VOLTAGE, CHANNEL_BATTERY_CURRENT,
+ CHANNEL_BATTERY_TEMPERATURE, CHANNEL_BATTERY_STATE_OF_CHARGE, CHANNEL_FEED_IN_POWER, CHANNEL_TIMESTAMP,
+ CHANNEL_RAW_DATA, CHANNEL_INVERTER_OUTPUT_POWER_PHASE1, CHANNEL_INVERTER_OUTPUT_POWER_PHASE2,
+ CHANNEL_INVERTER_OUTPUT_POWER_PHASE3, CHANNEL_INVERTER_TOTAL_OUTPUT_POWER,
+ CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1, CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2,
+ CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3, CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1,
+ CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2, CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3,
+ CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2,
+ CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, CHANNEL_POWER_USAGE, CHANNEL_TOTAL_ENERGY,
+ CHANNEL_TOTAL_BATTERY_CHARGE_ENERGY, CHANNEL_TOTAL_PV_ENERGY, CHANNEL_TOTAL_CONSUMPTION,
+ CHANNEL_TODAY_ENERGY, CHANNEL_TODAY_FEED_IN_ENERGY, CHANNEL_TODAY_CONSUMPTION,
+ CHANNEL_TODAY_BATTERY_CHARGE_ENERGY, CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY, CHANNEL_INVERTER_WORKMODE);
+
+ @Override
+ public LocalInverterData getData(LocalConnectRawDataBean rawData) {
+ return new X3HybridG4InverterData(rawData);
+ }
+
+ @Override
+ public Set<String> getSupportedChannels() {
+ return X3_HYBRID_G4_SUPPORTED_CHANNELS;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.solax.internal.model.local.parsers;
+
+import static org.openhab.binding.solax.internal.SolaxBindingConstants.*;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
+import org.openhab.binding.solax.internal.model.local.LocalInverterData;
+import org.openhab.binding.solax.internal.model.local.X3MicOrProG2InverterData;
+
+/**
+ * The {@link X3MicOrProG2DataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the
+ * X3 Mic / Pro G2 inverter.
+ *
+ * @author Henrik Tóth - Initial contribution
+ * (based on X1/X3 G4 parser from Konstantin Polihronov)
+ */
+@NonNullByDefault
+public class X3MicOrProG2DataParser implements RawDataParser {
+
+ private static final Set<String> X3_MIC_OR_PRO_G2_SUPPORTED_CHANNELS = Set.of(
+ CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2,
+ CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3, CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1,
+ CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2, CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3,
+ CHANNEL_INVERTER_OUTPUT_POWER_PHASE1, CHANNEL_INVERTER_OUTPUT_POWER_PHASE2,
+ CHANNEL_INVERTER_OUTPUT_POWER_PHASE3, CHANNEL_INVERTER_PV1_VOLTAGE, CHANNEL_INVERTER_PV2_VOLTAGE,
+ CHANNEL_INVERTER_PV1_CURRENT, CHANNEL_INVERTER_PV2_CURRENT, CHANNEL_INVERTER_PV1_POWER,
+ CHANNEL_INVERTER_PV2_POWER, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1,
+ CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, CHANNEL_TOTAL_ENERGY,
+ CHANNEL_TODAY_ENERGY, CHANNEL_INVERTER_TOTAL_OUTPUT_POWER, CHANNEL_INVERTER_TEMPERATURE1,
+ CHANNEL_INVERTER_TEMPERATURE2, CHANNEL_INVERTER_WORKMODE, CHANNEL_RAW_DATA);
+
+ @Override
+ public LocalInverterData getData(LocalConnectRawDataBean rawData) {
+ return new X3MicOrProG2InverterData(rawData);
+ }
+
+ @Override
+ public Set<String> getSupportedChannels() {
+ return X3_MIC_OR_PRO_G2_SUPPORTED_CHANNELS;
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2024 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.solax.internal.model.parsers;
-
-import java.util.Set;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
-import org.openhab.binding.solax.internal.model.InverterData;
-
-/**
- * The {@link RawDataParser} declares generic parser implementation that parses raw data to generic inverter data which
- * is common for all inverters.
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public interface RawDataParser {
-
- InverterData getData(LocalConnectRawDataBean bean);
-
- Set<String> getSupportedChannels();
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2024 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.solax.internal.model.parsers;
-
-import static org.openhab.binding.solax.internal.SolaxBindingConstants.*;
-
-import java.util.Set;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
-import org.openhab.binding.solax.internal.model.InverterData;
-import org.openhab.binding.solax.internal.model.impl.X1HybridG4InverterData;
-
-/**
- * The {@link SinglePhaseDataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the
- * X1 Hybrid G4 inverter.
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public class X1HybridG4DataParser implements RawDataParser {
-
- private static final Set<String> X1_HYBRID_G4_SUPPORTED_CHANNELS = Set.of(CHANNEL_INVERTER_PV1_POWER,
- CHANNEL_INVERTER_PV1_VOLTAGE, CHANNEL_INVERTER_PV1_CURRENT, CHANNEL_INVERTER_PV2_POWER,
- CHANNEL_INVERTER_PV2_VOLTAGE, CHANNEL_INVERTER_PV2_CURRENT, CHANNEL_INVERTER_PV_TOTAL_POWER,
- CHANNEL_INVERTER_PV_TOTAL_CURRENT, CHANNEL_BATTERY_POWER, CHANNEL_BATTERY_VOLTAGE, CHANNEL_BATTERY_CURRENT,
- CHANNEL_BATTERY_TEMPERATURE, CHANNEL_BATTERY_STATE_OF_CHARGE, CHANNEL_FEED_IN_POWER, CHANNEL_TIMESTAMP,
- CHANNEL_RAW_DATA, CHANNEL_INVERTER_OUTPUT_POWER, CHANNEL_INVERTER_OUTPUT_CURRENT,
- CHANNEL_INVERTER_OUTPUT_VOLTAGE, CHANNEL_INVERTER_OUTPUT_FREQUENCY, CHANNEL_INVERTER_WORKMODE);
-
- @Override
- public InverterData getData(LocalConnectRawDataBean rawData) {
- return new X1HybridG4InverterData(rawData);
- }
-
- @Override
- public Set<String> getSupportedChannels() {
- return X1_HYBRID_G4_SUPPORTED_CHANNELS;
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2024 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.solax.internal.model.parsers;
-
-import static org.openhab.binding.solax.internal.SolaxBindingConstants.*;
-
-import java.util.Set;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
-import org.openhab.binding.solax.internal.model.InverterData;
-import org.openhab.binding.solax.internal.model.impl.X3HybridG4InverterData;
-
-/**
- * The {@link X3HybridG4DataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the
- * X3 Hybrid G4 inverter.
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public class X3HybridG4DataParser implements RawDataParser {
-
- private static final Set<String> X3_HYBRID_G4_SUPPORTED_CHANNELS = Set.of(CHANNEL_INVERTER_PV1_POWER,
- CHANNEL_INVERTER_PV1_VOLTAGE, CHANNEL_INVERTER_PV1_CURRENT, CHANNEL_INVERTER_PV2_POWER,
- CHANNEL_INVERTER_PV2_VOLTAGE, CHANNEL_INVERTER_PV2_CURRENT, CHANNEL_INVERTER_PV_TOTAL_POWER,
- CHANNEL_INVERTER_PV_TOTAL_CURRENT, CHANNEL_BATTERY_POWER, CHANNEL_BATTERY_VOLTAGE, CHANNEL_BATTERY_CURRENT,
- CHANNEL_BATTERY_TEMPERATURE, CHANNEL_BATTERY_STATE_OF_CHARGE, CHANNEL_FEED_IN_POWER, CHANNEL_TIMESTAMP,
- CHANNEL_RAW_DATA, CHANNEL_INVERTER_OUTPUT_POWER_PHASE1, CHANNEL_INVERTER_OUTPUT_POWER_PHASE2,
- CHANNEL_INVERTER_OUTPUT_POWER_PHASE3, CHANNEL_INVERTER_TOTAL_OUTPUT_POWER,
- CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1, CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2,
- CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3, CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1,
- CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2, CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3,
- CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2,
- CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, CHANNEL_POWER_USAGE, CHANNEL_TOTAL_ENERGY,
- CHANNEL_TOTAL_BATTERY_CHARGE_ENERGY, CHANNEL_TOTAL_PV_ENERGY, CHANNEL_TOTAL_CONSUMPTION,
- CHANNEL_TODAY_ENERGY, CHANNEL_TODAY_FEED_IN_ENERGY, CHANNEL_TODAY_CONSUMPTION,
- CHANNEL_TODAY_BATTERY_CHARGE_ENERGY, CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY, CHANNEL_INVERTER_WORKMODE);
-
- @Override
- public InverterData getData(LocalConnectRawDataBean rawData) {
- return new X3HybridG4InverterData(rawData);
- }
-
- @Override
- public Set<String> getSupportedChannels() {
- return X3_HYBRID_G4_SUPPORTED_CHANNELS;
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2024 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.solax.internal.model.parsers;
-
-import static org.openhab.binding.solax.internal.SolaxBindingConstants.*;
-
-import java.util.Set;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
-import org.openhab.binding.solax.internal.model.InverterData;
-import org.openhab.binding.solax.internal.model.impl.X3MicOrProG2InverterData;
-
-/**
- * The {@link X3MicOrProG2DataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the
- * X3 Mic / Pro G2 inverter.
- *
- * @author Henrik Tóth - Initial contribution
- * (based on X1/X3 G4 parser from Konstantin Polihronov)
- */
-@NonNullByDefault
-public class X3MicOrProG2DataParser implements RawDataParser {
-
- private static final Set<String> X3_MIC_OR_PRO_G2_SUPPORTED_CHANNELS = Set.of(
- CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2,
- CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3, CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1,
- CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2, CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3,
- CHANNEL_INVERTER_OUTPUT_POWER_PHASE1, CHANNEL_INVERTER_OUTPUT_POWER_PHASE2,
- CHANNEL_INVERTER_OUTPUT_POWER_PHASE3, CHANNEL_INVERTER_PV1_VOLTAGE, CHANNEL_INVERTER_PV2_VOLTAGE,
- CHANNEL_INVERTER_PV1_CURRENT, CHANNEL_INVERTER_PV2_CURRENT, CHANNEL_INVERTER_PV1_POWER,
- CHANNEL_INVERTER_PV2_POWER, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1,
- CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, CHANNEL_TOTAL_ENERGY,
- CHANNEL_TODAY_ENERGY, CHANNEL_INVERTER_TOTAL_OUTPUT_POWER, CHANNEL_INVERTER_TEMPERATURE1,
- CHANNEL_INVERTER_TEMPERATURE2, CHANNEL_INVERTER_WORKMODE, CHANNEL_RAW_DATA);
-
- @Override
- public InverterData getData(LocalConnectRawDataBean rawData) {
- return new X3MicOrProG2InverterData(rawData);
- }
-
- @Override
- public Set<String> getSupportedChannels() {
- return X3_MIC_OR_PRO_G2_SUPPORTED_CHANNELS;
- }
-}
# 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
# 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
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
# 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.
</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>
--- /dev/null
+<?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>
+++ /dev/null
-/**
- * Copyright (c) 2010-2024 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.solax.internal;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.junit.jupiter.api.Test;
-import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
-import org.openhab.binding.solax.internal.model.InverterData;
-import org.openhab.binding.solax.internal.model.InverterType;
-import org.openhab.binding.solax.internal.model.parsers.RawDataParser;
-
-/**
- * The {@link TestX1HybridG4Parser} Simple test that tests for proper parsing against a real data from the inverter
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public class TestX1HybridG4Parser {
-
- private static final String RAW_DATA = """
- {
- sn:SOME_SERIAL_NUMBER,
- ver:3.008.10,
- type:15,
- Data:[
- 2388,21,460,4998,4483,4483,10,1,487,65,
- 2,59781,0,70,12180,500,605,33,99,12000,
- 0,23159,0,57,100,0,39,4501,0,0,
- 0,0,12,0,13240,0,63348,2,448,43,
- 256,1314,900,0,350,311,279,33,33,279,1,1,652,0,708,1,65077,65535,65386,65535,0,0,0,0,0,0,0,0,0,0,0,0,65068,65535,4500,0,61036,65535,10,0,90,0,0,12,0,116,7,57,0,0,2320,0,110,0,0,0,0,0,0,12544,7440,5896,594,521,9252,0,0,0,0,0,1,1201,0,0,3342,3336,7296,54,21302,14389,18753,12852,16692,12355,13618,21302,14389,18753,12852,16692,12355,13618,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1025,4609,1026,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
- Information:[7.500,15,H4752TI1063020,8,1.24,0.00,1.21,1.03,0.00,1]}
- """;
-
- @Test
- public void testParser() {
- LocalConnectRawDataBean bean = LocalConnectRawDataBean.fromJson(RAW_DATA);
- int type = bean.getType();
- InverterType inverterType = InverterType.fromIndex(type);
- assertEquals(InverterType.X1_HYBRID_G4, inverterType, "Inverter type not recognized properly");
-
- RawDataParser parser = inverterType.getParser();
- assertNotNull(parser);
-
- InverterData data = parser.getData(bean);
- assertEquals("SOME_SERIAL_NUMBER", data.getWifiSerial());
- assertEquals("3.008.10", data.getWifiVersion());
-
- assertEquals(238.8, data.getInverterVoltage()); // [0]
- assertEquals(2.1, data.getInverterCurrent()); // [1]
- assertEquals(460, data.getInverterOutputPower()); // [2]
- assertEquals(49.98, data.getInverterFrequency()); // [3]
-
- assertEquals(448.3, data.getPV1Voltage()); // [4]
- assertEquals(448.3, data.getPV2Voltage()); // [5]
- assertEquals(1, data.getPV1Current()); // [6]
- assertEquals(0.1, data.getPV2Current()); // [7]
- assertEquals(487, data.getPV1Power()); // [8]
- assertEquals(65, data.getPV2Power()); // [9]
-
- assertEquals(2, data.getInverterWorkModeCode()); // [10]
- assertEquals("2", data.getInverterWorkMode()); // [10]
-
- assertEquals(121.8, data.getBatteryVoltage()); // [14]
- assertEquals(5, data.getBatteryCurrent()); // [15]
- assertEquals(605, data.getBatteryPower()); // [16]
- assertEquals(33, data.getBatteryTemperature()); // [17]
- assertEquals(99, data.getBatteryLevel()); // [18]
-
- assertEquals(12, data.getFeedInPower()); // [32]
- }
-
- // Yield_Today: Data[13] / 10,
- // Yield_Total: read32BitUnsigned(Data[11], Data[12]) / 10,
- // PowerDc1: Data[8],
- // PowerDc2: Data[9],
- // BAT_Power: read16BitSigned(Data[16]),
- // feedInPower: read32BitSigned(Data[32], Data[33]),
- // GridAPower: read16BitSigned(Data[2]),
- // FeedInEnergy: read32BitUnsigned(Data[34], Data[35]) / 100,
- // ConsumeEnergy: read32BitUnsigned(Data[36], Data[37]) / 100,
- // RunMode: Data[10],
- // EPSAPower: read16BitSigned(Data[28]),
- // Vdc1: Data[4] / 10,
- // Vdc2: Data[5] / 10,
- // Idc1: Data[6] / 10,
- // Idc2: Data[7] / 10,
- // EPSAVoltage: Data[29] / 10,
- // EPSACurrent: read16BitSigned(Data[30]) / 10,
- // BatteryCapacity: Data[18],
- // BatteryVoltage: Data[14] / 100,
- // BatteryTemperature: read16BitSigned(Data[17]),
- // GridAVoltage: Data[0] / 10,
- // GridACurrent: read16BitSigned(Data[1]) / 10,
- // FreqacA: Data[3] / 100,
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2024 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.solax.internal;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.junit.jupiter.api.Test;
-import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
-import org.openhab.binding.solax.internal.model.InverterData;
-import org.openhab.binding.solax.internal.model.InverterType;
-import org.openhab.binding.solax.internal.model.parsers.RawDataParser;
-
-/**
- * The {@link TestX3HybridG4Parser} simple test that tests for proper parsing against a real data from the inverter
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public class TestX3HybridG4Parser {
-
- String rawData = """
- {
- sn:XYZ,
- ver:3.005.01,
- type:14,Data:[
- 2316,2329,2315,18,18,18,372,363,365,1100,
- 12,23,34,45,56,67,4996,4996,4996,2,
- 0,0,0,0,0,0,0,0,0,0,
- 0,0,0,1,65494,65535,0,0,0,31330,
- 320,1034,3078,1,44,1100,256,1294,0,0,
- 7445,5895,100,0,38,0,0,0,0,0,
- 0,0,0,0,0,0,0,0,505,0,
- 396,0,0,0,102,0,142,0,62,110,
- 570,0,463,0,0,0,1925,0,369,0,
- 506,1925,304,309,0,0,0,0,0,0,
- 0,0,0,45,1,59,1,34,54,256,
- 3504,2400,300,300,295,276,33,33,2,1620,779,15163,15163,14906,0,0,0,3270,3264,45581,0,20564,12339,18753,12353,18742,12356,13625,20564,12339,18754,12866,18743,14151,13104,20564,12339,18754,12866,18743,14151,12592,20564,12339,18754,12865,18738,12871,13620,0,0,0,0,0,0,0,1025,8195,769,259,0,31460,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
- Information:[12.000,14,XY,8,1.23,0.00,1.24,1.09,0.00,1]
- }
- """;
-
- @Test
- public void testParser() {
- LocalConnectRawDataBean bean = LocalConnectRawDataBean.fromJson(rawData);
- int type = bean.getType();
- InverterType inverterType = InverterType.fromIndex(type);
- assertEquals(InverterType.X3_HYBRID_G4, inverterType, "Inverter type not recognized properly");
-
- RawDataParser parser = inverterType.getParser();
- assertNotNull(parser);
-
- InverterData data = parser.getData(bean);
- assertEquals("XYZ", data.getWifiSerial());
- assertEquals("3.005.01", data.getWifiVersion());
-
- assertEquals(231.6, data.getVoltagePhase1()); // [0]
- assertEquals(232.9, data.getVoltagePhase2()); // [1]
- assertEquals(231.5, data.getVoltagePhase3()); // [2]
-
- assertEquals(1.8, data.getCurrentPhase1()); // [3]
- assertEquals(1.8, data.getCurrentPhase2()); // [4]
- assertEquals(1.8, data.getCurrentPhase3()); // [5]
-
- assertEquals(372, data.getOutputPowerPhase1()); // [6]
- assertEquals(363, data.getOutputPowerPhase2()); // [7]
- assertEquals(365, data.getOutputPowerPhase3()); // [8]
-
- assertEquals(1100, data.getTotalOutputPower()); // [9]
-
- assertEquals(1.2, data.getPV1Voltage()); // [10]
- assertEquals(2.3, data.getPV2Voltage()); // [11]
- assertEquals(3.4, data.getPV1Current()); // [12]
- assertEquals(4.5, data.getPV2Current()); // [13]
- assertEquals(56, data.getPV1Power()); // [14]
- assertEquals(67, data.getPV2Power()); // [15]
-
- assertEquals(49.96, data.getFrequencyPhase1()); // [16]
- assertEquals(49.96, data.getFrequencyPhase2()); // [17]
- assertEquals(49.96, data.getFrequencyPhase3()); // [18]
-
- assertEquals(2, data.getInverterWorkModeCode()); // [19]
- assertEquals("2", data.getInverterWorkMode()); // [19]
-
- assertEquals(-41, data.getFeedInPower()); // [34] - [35]
-
- assertEquals(313.3, data.getBatteryVoltage()); // [39]
- assertEquals(3.2, data.getBatteryCurrent()); // [40]
- assertEquals(1034, data.getBatteryPower()); // [41]
- assertEquals(45, data.getBatteryLevel()); // [103]
- assertEquals(59, data.getBatteryTemperature()); // [105]
-
- // Totals
- assertEquals(1294, data.getPowerUsage()); // [47]
- assertEquals(50.5, data.getTotalEnergy()); // [68]
- assertEquals(102, data.getTotalBatteryDischargeEnergy()); // [74]
- assertEquals(142, data.getTotalBatteryChargeEnergy()); // [76]
- assertEquals(57, data.getTotalPVEnergy()); // [80]
- assertEquals(1925, data.getTotalFeedInEnergy()); // [86]
- assertEquals(36.9, data.getTotalConsumption()); // [88]
- assertEquals(46.3, data.getTodayEnergy()); // [82] / 10
- assertEquals(5.06, data.getTodayFeedInEnergy()); // [90] / 100
- assertEquals(3.04, data.getTodayConsumption()); // [92] / 100
- assertEquals(6.2, data.getTodayBatteryDischargeEnergy()); // [78] / 100
- assertEquals(11, data.getTodayBatteryChargeEnergy()); // [79] / 100
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2024 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.solax.internal;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.junit.jupiter.api.Test;
-import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean;
-import org.openhab.binding.solax.internal.model.InverterData;
-import org.openhab.binding.solax.internal.model.InverterType;
-import org.openhab.binding.solax.internal.model.parsers.RawDataParser;
-
-/**
- * The {@link TestX3HybridG4Parser} simple test that tests for proper parsing against a real data from the inverter
- *
- * @author Konstantin Polihronov - Initial contribution
- */
-@NonNullByDefault
-public class TestX3MicOrProG2Parser {
-
- String rawData = """
- {
- sn:XYZ,
- ver:3.003.02,
- type:16,Data:[
- 2515,2449,2484,5,5,9,54,44,20,4080,4340,
- 0,1,2,0,67,102,0,4999,4999,4999,2,19035,
- 0,50,8000,5,9,
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,
- 0,0,0,0,0,0,0,0,120,40,1,6,5,0,6772,0,0,0,
- 0,0,0,0,0,0,0,0,0,0,0,0
- ],
- Information:[8.000,16,XY,8,1.15,0.00,1.11,1.01,0.00,1]
- }
- """;
-
- @Test
- public void testParser() {
- LocalConnectRawDataBean bean = LocalConnectRawDataBean.fromJson(rawData);
- int type = bean.getType();
- InverterType inverterType = InverterType.fromIndex(type);
- assertEquals(InverterType.X3_MIC_OR_PRO_G2, inverterType, "Inverter type not recognized properly");
-
- RawDataParser parser = inverterType.getParser();
- assertNotNull(parser);
-
- InverterData data = parser.getData(bean);
- assertEquals("XYZ", data.getWifiSerial());
- assertEquals("3.003.02", data.getWifiVersion());
-
- assertEquals(251.5, data.getVoltagePhase1()); // [0]
- assertEquals(244.9, data.getVoltagePhase2()); // [1]
- assertEquals(248.4, data.getVoltagePhase3()); // [2]
-
- assertEquals(0.5, data.getCurrentPhase1()); // [3]
- assertEquals(0.5, data.getCurrentPhase2()); // [4]
- assertEquals(0.9, data.getCurrentPhase3()); // [5]
-
- assertEquals(54, data.getOutputPowerPhase1()); // [6]
- assertEquals(44, data.getOutputPowerPhase2()); // [7]
- assertEquals(20, data.getOutputPowerPhase3()); // [8]
-
- assertEquals(408, data.getPV1Voltage()); // [9]
- assertEquals(434, data.getPV2Voltage()); // [10]
- assertEquals(0.1, data.getPV1Current()); // [12]
- assertEquals(0.2, data.getPV2Current()); // [13]
- assertEquals(67, data.getPV1Power()); // [15]
- assertEquals(102, data.getPV2Power()); // [16]
-
- assertEquals(49.99, data.getFrequencyPhase1()); // [18]
- assertEquals(49.99, data.getFrequencyPhase2()); // [19]
- assertEquals(49.99, data.getFrequencyPhase3()); // [20]
-
- assertEquals(2, data.getInverterWorkModeCode()); // [21]
- assertEquals("2", data.getInverterWorkMode()); // [21]
-
- assertEquals(5, data.getInverterTemperature1()); // [26]
- assertEquals(9, data.getInverterTemperature2()); // [27]
-
- assertEquals(120, data.getTotalOutputPower()); // [78]
-
- assertEquals(1903.5, data.getTotalEnergy()); // [22]
- assertEquals(5.0, data.getTodayEnergy()); // [24]
- }
-}
--- /dev/null
+/**
+ * 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");
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.solax.internal.local;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
+import org.openhab.binding.solax.internal.model.InverterType;
+import org.openhab.binding.solax.internal.model.local.LocalInverterData;
+import org.openhab.binding.solax.internal.model.local.parsers.RawDataParser;
+
+/**
+ * The {@link TestX1HybridG4Parser} Simple test that tests for proper parsing against a real data from the inverter
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class TestX1HybridG4Parser {
+
+ private static final String RAW_DATA = """
+ {
+ sn:SOME_SERIAL_NUMBER,
+ ver:3.008.10,
+ type:15,
+ Data:[
+ 2388,21,460,4998,4483,4483,10,1,487,65,
+ 2,59781,0,70,12180,500,605,33,99,12000,
+ 0,23159,0,57,100,0,39,4501,0,0,
+ 0,0,12,0,13240,0,63348,2,448,43,
+ 256,1314,900,0,350,311,279,33,33,279,1,1,652,0,708,1,65077,65535,65386,65535,0,0,0,0,0,0,0,0,0,0,0,0,65068,65535,4500,0,61036,65535,10,0,90,0,0,12,0,116,7,57,0,0,2320,0,110,0,0,0,0,0,0,12544,7440,5896,594,521,9252,0,0,0,0,0,1,1201,0,0,3342,3336,7296,54,21302,14389,18753,12852,16692,12355,13618,21302,14389,18753,12852,16692,12355,13618,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1025,4609,1026,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
+ Information:[7.500,15,H4752TI1063020,8,1.24,0.00,1.21,1.03,0.00,1]}
+ """;
+
+ @Test
+ public void testParser() {
+ LocalConnectRawDataBean bean = LocalConnectRawDataBean.fromJson(RAW_DATA);
+ int type = bean.getType();
+ InverterType inverterType = InverterType.fromIndex(type);
+ assertEquals(InverterType.X1_HYBRID_G4, inverterType, "Inverter type not recognized properly");
+
+ RawDataParser parser = inverterType.getParser();
+ assertNotNull(parser);
+
+ LocalInverterData data = parser.getData(bean);
+ assertEquals("SOME_SERIAL_NUMBER", data.getWifiSerial());
+ assertEquals("3.008.10", data.getWifiVersion());
+
+ assertEquals(238.8, data.getInverterVoltage()); // [0]
+ assertEquals(2.1, data.getInverterCurrent()); // [1]
+ assertEquals(460, data.getInverterOutputPower()); // [2]
+ assertEquals(49.98, data.getInverterFrequency()); // [3]
+
+ assertEquals(448.3, data.getPV1Voltage()); // [4]
+ assertEquals(448.3, data.getPV2Voltage()); // [5]
+ assertEquals(1, data.getPV1Current()); // [6]
+ assertEquals(0.1, data.getPV2Current()); // [7]
+ assertEquals(487, data.getPV1Power()); // [8]
+ assertEquals(65, data.getPV2Power()); // [9]
+
+ assertEquals(2, data.getInverterWorkModeCode()); // [10]
+ assertEquals("2", data.getInverterWorkMode()); // [10]
+
+ assertEquals(121.8, data.getBatteryVoltage()); // [14]
+ assertEquals(5, data.getBatteryCurrent()); // [15]
+ assertEquals(605, data.getBatteryPower()); // [16]
+ assertEquals(33, data.getBatteryTemperature()); // [17]
+ assertEquals(99, data.getBatteryLevel()); // [18]
+
+ assertEquals(12, data.getFeedInPower()); // [32]
+ }
+
+ // Yield_Today: Data[13] / 10,
+ // Yield_Total: read32BitUnsigned(Data[11], Data[12]) / 10,
+ // PowerDc1: Data[8],
+ // PowerDc2: Data[9],
+ // BAT_Power: read16BitSigned(Data[16]),
+ // feedInPower: read32BitSigned(Data[32], Data[33]),
+ // GridAPower: read16BitSigned(Data[2]),
+ // FeedInEnergy: read32BitUnsigned(Data[34], Data[35]) / 100,
+ // ConsumeEnergy: read32BitUnsigned(Data[36], Data[37]) / 100,
+ // RunMode: Data[10],
+ // EPSAPower: read16BitSigned(Data[28]),
+ // Vdc1: Data[4] / 10,
+ // Vdc2: Data[5] / 10,
+ // Idc1: Data[6] / 10,
+ // Idc2: Data[7] / 10,
+ // EPSAVoltage: Data[29] / 10,
+ // EPSACurrent: read16BitSigned(Data[30]) / 10,
+ // BatteryCapacity: Data[18],
+ // BatteryVoltage: Data[14] / 100,
+ // BatteryTemperature: read16BitSigned(Data[17]),
+ // GridAVoltage: Data[0] / 10,
+ // GridACurrent: read16BitSigned(Data[1]) / 10,
+ // FreqacA: Data[3] / 100,
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.solax.internal.local;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
+import org.openhab.binding.solax.internal.model.InverterType;
+import org.openhab.binding.solax.internal.model.local.LocalInverterData;
+import org.openhab.binding.solax.internal.model.local.parsers.RawDataParser;
+
+/**
+ * The {@link TestX3HybridG4Parser} simple test that tests for proper parsing against a real data from the inverter
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class TestX3HybridG4Parser {
+
+ String rawData = """
+ {
+ sn:XYZ,
+ ver:3.005.01,
+ type:14,Data:[
+ 2316,2329,2315,18,18,18,372,363,365,1100,
+ 12,23,34,45,56,67,4996,4996,4996,2,
+ 0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,1,65494,65535,0,0,0,31330,
+ 320,1034,3078,1,44,1100,256,1294,0,0,
+ 7445,5895,100,0,38,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,505,0,
+ 396,0,0,0,102,0,142,0,62,110,
+ 570,0,463,0,0,0,1925,0,369,0,
+ 506,1925,304,309,0,0,0,0,0,0,
+ 0,0,0,45,1,59,1,34,54,256,
+ 3504,2400,300,300,295,276,33,33,2,1620,779,15163,15163,14906,0,0,0,3270,3264,45581,0,20564,12339,18753,12353,18742,12356,13625,20564,12339,18754,12866,18743,14151,13104,20564,12339,18754,12866,18743,14151,12592,20564,12339,18754,12865,18738,12871,13620,0,0,0,0,0,0,0,1025,8195,769,259,0,31460,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
+ Information:[12.000,14,XY,8,1.23,0.00,1.24,1.09,0.00,1]
+ }
+ """;
+
+ @Test
+ public void testParser() {
+ LocalConnectRawDataBean bean = LocalConnectRawDataBean.fromJson(rawData);
+ int type = bean.getType();
+ InverterType inverterType = InverterType.fromIndex(type);
+ assertEquals(InverterType.X3_HYBRID_G4, inverterType, "Inverter type not recognized properly");
+
+ RawDataParser parser = inverterType.getParser();
+ assertNotNull(parser);
+
+ LocalInverterData data = parser.getData(bean);
+ assertEquals("XYZ", data.getWifiSerial());
+ assertEquals("3.005.01", data.getWifiVersion());
+
+ assertEquals(231.6, data.getVoltagePhase1()); // [0]
+ assertEquals(232.9, data.getVoltagePhase2()); // [1]
+ assertEquals(231.5, data.getVoltagePhase3()); // [2]
+
+ assertEquals(1.8, data.getCurrentPhase1()); // [3]
+ assertEquals(1.8, data.getCurrentPhase2()); // [4]
+ assertEquals(1.8, data.getCurrentPhase3()); // [5]
+
+ assertEquals(372, data.getOutputPowerPhase1()); // [6]
+ assertEquals(363, data.getOutputPowerPhase2()); // [7]
+ assertEquals(365, data.getOutputPowerPhase3()); // [8]
+
+ assertEquals(1100, data.getTotalOutputPower()); // [9]
+
+ assertEquals(1.2, data.getPV1Voltage()); // [10]
+ assertEquals(2.3, data.getPV2Voltage()); // [11]
+ assertEquals(3.4, data.getPV1Current()); // [12]
+ assertEquals(4.5, data.getPV2Current()); // [13]
+ assertEquals(56, data.getPV1Power()); // [14]
+ assertEquals(67, data.getPV2Power()); // [15]
+
+ assertEquals(49.96, data.getFrequencyPhase1()); // [16]
+ assertEquals(49.96, data.getFrequencyPhase2()); // [17]
+ assertEquals(49.96, data.getFrequencyPhase3()); // [18]
+
+ assertEquals(2, data.getInverterWorkModeCode()); // [19]
+ assertEquals("2", data.getInverterWorkMode()); // [19]
+
+ assertEquals(-41, data.getFeedInPower()); // [34] - [35]
+
+ assertEquals(313.3, data.getBatteryVoltage()); // [39]
+ assertEquals(3.2, data.getBatteryCurrent()); // [40]
+ assertEquals(1034, data.getBatteryPower()); // [41]
+ assertEquals(45, data.getBatteryLevel()); // [103]
+ assertEquals(59, data.getBatteryTemperature()); // [105]
+
+ // Totals
+ assertEquals(1294, data.getPowerUsage()); // [47]
+ assertEquals(50.5, data.getTotalEnergy()); // [68]
+ assertEquals(102, data.getTotalBatteryDischargeEnergy()); // [74]
+ assertEquals(142, data.getTotalBatteryChargeEnergy()); // [76]
+ assertEquals(57, data.getTotalPVEnergy()); // [80]
+ assertEquals(1925, data.getTotalFeedInEnergy()); // [86]
+ assertEquals(36.9, data.getTotalConsumption()); // [88]
+ assertEquals(46.3, data.getTodayEnergy()); // [82] / 10
+ assertEquals(5.06, data.getTodayFeedInEnergy()); // [90] / 100
+ assertEquals(3.04, data.getTodayConsumption()); // [92] / 100
+ assertEquals(6.2, data.getTodayBatteryDischargeEnergy()); // [78] / 100
+ assertEquals(11, data.getTodayBatteryChargeEnergy()); // [79] / 100
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.solax.internal.local;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.solax.internal.connectivity.rawdata.local.LocalConnectRawDataBean;
+import org.openhab.binding.solax.internal.model.InverterType;
+import org.openhab.binding.solax.internal.model.local.LocalInverterData;
+import org.openhab.binding.solax.internal.model.local.parsers.RawDataParser;
+
+/**
+ * The {@link TestX3HybridG4Parser} simple test that tests for proper parsing against a real data from the inverter
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class TestX3MicOrProG2Parser {
+
+ String rawData = """
+ {
+ sn:XYZ,
+ ver:3.003.02,
+ type:16,Data:[
+ 2515,2449,2484,5,5,9,54,44,20,4080,4340,
+ 0,1,2,0,67,102,0,4999,4999,4999,2,19035,
+ 0,50,8000,5,9,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,
+ 0,0,0,0,0,0,0,0,120,40,1,6,5,0,6772,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0
+ ],
+ Information:[8.000,16,XY,8,1.15,0.00,1.11,1.01,0.00,1]
+ }
+ """;
+
+ @Test
+ public void testParser() {
+ LocalConnectRawDataBean bean = LocalConnectRawDataBean.fromJson(rawData);
+ int type = bean.getType();
+ InverterType inverterType = InverterType.fromIndex(type);
+ assertEquals(InverterType.X3_MIC_OR_PRO_G2, inverterType, "Inverter type not recognized properly");
+
+ RawDataParser parser = inverterType.getParser();
+ assertNotNull(parser);
+
+ LocalInverterData data = parser.getData(bean);
+ assertEquals("XYZ", data.getWifiSerial());
+ assertEquals("3.003.02", data.getWifiVersion());
+
+ assertEquals(251.5, data.getVoltagePhase1()); // [0]
+ assertEquals(244.9, data.getVoltagePhase2()); // [1]
+ assertEquals(248.4, data.getVoltagePhase3()); // [2]
+
+ assertEquals(0.5, data.getCurrentPhase1()); // [3]
+ assertEquals(0.5, data.getCurrentPhase2()); // [4]
+ assertEquals(0.9, data.getCurrentPhase3()); // [5]
+
+ assertEquals(54, data.getOutputPowerPhase1()); // [6]
+ assertEquals(44, data.getOutputPowerPhase2()); // [7]
+ assertEquals(20, data.getOutputPowerPhase3()); // [8]
+
+ assertEquals(408, data.getPV1Voltage()); // [9]
+ assertEquals(434, data.getPV2Voltage()); // [10]
+ assertEquals(0.1, data.getPV1Current()); // [12]
+ assertEquals(0.2, data.getPV2Current()); // [13]
+ assertEquals(67, data.getPV1Power()); // [15]
+ assertEquals(102, data.getPV2Power()); // [16]
+
+ assertEquals(49.99, data.getFrequencyPhase1()); // [18]
+ assertEquals(49.99, data.getFrequencyPhase2()); // [19]
+ assertEquals(49.99, data.getFrequencyPhase3()); // [20]
+
+ assertEquals(2, data.getInverterWorkModeCode()); // [21]
+ assertEquals("2", data.getInverterWorkMode()); // [21]
+
+ assertEquals(5, data.getInverterTemperature1()); // [26]
+ assertEquals(9, data.getInverterTemperature2()); // [27]
+
+ assertEquals(120, data.getTotalOutputPower()); // [78]
+
+ assertEquals(1903.5, data.getTotalEnergy()); // [22]
+ assertEquals(5.0, data.getTodayEnergy()); // [24]
+ }
+}