]> git.basschouten.com Git - openhab-addons.git/commitdiff
[solax] Initial contribution (#14880)
authorKonstantin Polihronov <polychronov@gmail.com>
Thu, 24 Aug 2023 12:39:24 +0000 (15:39 +0300)
committerGitHub <noreply@github.com>
Thu, 24 Aug 2023 12:39:24 +0000 (14:39 +0200)
Signed-off-by: Konstantin Polihronov <polychronov@gmail.com>
22 files changed:
CODEOWNERS
bom/openhab-addons/pom.xml
bundles/org.openhab.binding.solax/NOTICE [new file with mode: 0644]
bundles/org.openhab.binding.solax/README.md [new file with mode: 0644]
bundles/org.openhab.binding.solax/pom.xml [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/feature/feature.xml [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxHandlerFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/LocalHttpConnector.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/SolaxConnector.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/LocalConnectRawDataBean.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/RawDataBean.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterData.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/util/GsonSupplier.java [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/resources/OH-INF/addon/addon.xml [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/channel_types.xml [new file with mode: 0644]
bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/localConnectInverter.xml [new file with mode: 0644]
bundles/pom.xml

index b433f9f1bda946eda1f5d71f464edcf62bf91194..2a39f5c6a570d2a02fd5a8f6b3e8b8ed52e9bc43 100644 (file)
 /bundles/org.openhab.binding.solarlog/ @johannrichard
 /bundles/org.openhab.binding.solarmax/ @jamietownsend
 /bundles/org.openhab.binding.solarwatt/ @sven-carstens
+/bundles/org.openhab.binding.solax/ @theater
 /bundles/org.openhab.binding.somfymylink/ @loungeflyz
 /bundles/org.openhab.binding.somfytahoma/ @octa22
 /bundles/org.openhab.binding.somneo/ @0x4d4d
index 024a15561867a57c3228a79a2c4cc0fb77bb73f1..95eba8ac828c2c2df701a9c672cd3146a5b91bf8 100644 (file)
       <artifactId>org.openhab.binding.solarwatt</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.openhab.addons.bundles</groupId>
+      <artifactId>org.openhab.binding.solax</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>org.openhab.addons.bundles</groupId>
       <artifactId>org.openhab.binding.somfymylink</artifactId>
diff --git a/bundles/org.openhab.binding.solax/NOTICE b/bundles/org.openhab.binding.solax/NOTICE
new file mode 100644 (file)
index 0000000..38d625e
--- /dev/null
@@ -0,0 +1,13 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
diff --git a/bundles/org.openhab.binding.solax/README.md b/bundles/org.openhab.binding.solax/README.md
new file mode 100644 (file)
index 0000000..6b35fb6
--- /dev/null
@@ -0,0 +1,189 @@
+# Solax Binding
+
+This is a binding for Solax solar power inverters.
+
+Solax Wi-Fi module with direct connection via HTTP is supported.
+Wi-Fi module firmware version 3.x+ is required.
+Please note that earlier firmware releases do not support direct connection, therefore the binding will not work in its current state.
+
+The binding retrieves a structured data from the inverter's Wi-Fi module, parses it and pushes it into the inverter Thing where each channel represents a specific information (inverter output power, voltage, PV1 power, etc.)
+
+In case the parsed information that comes with the binding out of the box differs, the raw data channel can be used with a combination of JSON Path transformation to map the proper values to the necessary items.
+
+## Supported Things
+
+| Thing                  | Thing Type | Description                                                                         |
+|------------------------|------------|-------------------------------------------------------------------------------------|
+| local-connect-inverter | Thing      | This is model representation of inverter with all the data available as a channels  |
+
+## Thing Configuration
+
+### Local Connect Inverter Configuration
+
+| Parameter         | Description                                                                                                                                        |
+|-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
+| refreshInterval   | Defines the refresh interval when the binding polls from the inverter's Wi-Fi module (in seconds). Optional parameter. Default 10 seconds.         |
+| password          | Password for accessing the Wi-Fi module (the serial number of the wifi). Mandatory parameter.                                                      |
+| hostname          | IP address or hostname of your Wi-Fi module. If hostname is used must be resolvable by OpenHAB. Mandatory parameter.                               |
+
+### Inverter Output Channels
+
+| Channel                  | Type                       | Description                                      |
+|--------------------------|----------------------------|--------------------------------------------------|
+| inverter-output-power    | Number:Power               | The output power of the inverter [W]             |
+| inverter-current         | Number:ElectricCurrent     | The output current of the inverter [A]           |
+| inverter-voltage         | Number:ElectricPotential   | The output voltage of the inverter [V]           |
+| inverter-frequency       | Number:Frequency           | The frequency of the output voltage [Hz]         |
+
+### Photovoltaic Panels Production Channels
+
+| Channel                  | Type                       | Description                                     |
+|--------------------------|----------------------------|-------------------------------------------------|
+| pv1-voltage              | Number:ElectricPotential   | The voltage of PV1 string [V]                   |
+| pv2-voltage              | Number:ElectricPotential   | The voltage of PV2 string [V]                   |
+| pv1-current              | Number:ElectricCurrent     | The current of PV1 string [A]                   |
+| pv2-current              | Number:ElectricCurrent     | The current of PV2 string [A]                   |
+| pv1-power                | Number:Power               | The output power PV1 string [W]                 |
+| pv2-power                | Number:Power               | The output power PV2 string [W]                 |
+| pv-total-power           | Number:Power               | The total output power of both PV strings [W]   |
+| pv-total-current         | Number:ElectricCurrent     | The total current of both PV strings [A]        |
+
+### 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 [%]                                                         |
+
+### Grid related channels
+
+| Channel                  | Type                       | Description                                                                                    |
+|--------------------------|----------------------------|------------------------------------------------------------------------------------------------|
+| feed-in-power            | Number:Power               | The power to / from grid (negative means power is pulled from the grid and vice-versa) [W]     |
+
+### General channels
+
+| Channel                  | Type                       | Description                                                                                                                                 |
+|--------------------------|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
+| last-update-time         | DateTime                   | Last time when a call has been made to the inverter                                                                                         |
+| raw-data                 | String                     | The raw data retrieved from inverter in JSON format. (Usable for channels not implemented. Can be consumed with the JSONpath transformation |
+
+### Properties
+
+| Property          | Description                               |
+|-------------------|-------------------------------------------|
+| serialNumber      | The serial number of the Wi-Fi module     |
+| inverterType      | Inverter Type (for example X1_HYBRID_G4)  |
+
+## Full Example
+
+Here are some file based examples.
+
+### Thing Configuration
+
+```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>" ] 
+```
+
+### Item Configuration
+
+```java
+Group gSolaxInverter "Solax Inverter" <energy> (boilerRoom)
+Group solarPanels "Solar panels" <energy> (gSolaxInverter)
+
+Number solaxPowerWest "West [%.0f W]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:localConnectInverter:localInverter:pv1-power" }
+Number solaxPowerEast "East [%.0f W]" <solarplant> (gsolax_inverter,EveryChangePersist,solarPanels){ channel="solax:localConnectInverter:localInverter:pv2-power" }
+Number solaxBatteryPower "Battery power [%.0f W]" <energy> (gsolax_inverter,EveryChangePersist) { channel="solax:localConnectInverter:localInverter:battery-power" }
+Number solaxBatterySoc "Battery SoC [%.0f %%]" <batterylevel> (gsolax_inverter,EveryChangePersist) { channel="solax:localConnectInverter:localInverter:battery-state-of-charge" }
+
+Number solaxFeedInPower "Feed-in power (CEZ) [%.0f W]" <energy> (gsolax_inverter,EveryChangePersist) { channel="solax:localConnectInverter:localInverter:feed-in-power" }
+Number solaxAcPower "Invertor output power [%.0f W]" <energy> (gsolax_inverter,EveryChangePersist){ channel="solax:localConnectInverter:localInverter:inverter-output-power" }
+
+String solaxInverterType "Inverter Type [%s]" <energy> (gsolax_inverter) { channel="solax:localConnectInverter:localInverter:inverter-type"}
+String solaxUploadTime "Last update time [%s]" <calendar> (gsolax_inverter) { channel="solax:localConnectInverter:localInverter:last-update-time" }
+String solaxRawData "Raw data [%s]" <data> (gsolax_inverter) { channel="solax:localConnectInverter:localInverter:raw-data" }
+```
+
+### Sitemap Configuration
+
+```perl
+Frame label="Solar power strings" {
+    Text item=solaxPowerEast 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]       
+    }
+    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]   
+    }
+    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]   
+    }
+}
+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] 
+    }
+    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] 
+    }
+}
+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]         
+    }
+    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]       
+    }
+}
+```
+
+
diff --git a/bundles/org.openhab.binding.solax/pom.xml b/bundles/org.openhab.binding.solax/pom.xml
new file mode 100644 (file)
index 0000000..6e3d15e
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.openhab.addons.bundles</groupId>
+    <artifactId>org.openhab.addons.reactor.bundles</artifactId>
+    <version>4.1.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>org.openhab.binding.solax</artifactId>
+
+  <name>openHAB Add-ons :: Bundles :: Solax Binding</name>
+
+</project>
diff --git a/bundles/org.openhab.binding.solax/src/main/feature/feature.xml b/bundles/org.openhab.binding.solax/src/main/feature/feature.xml
new file mode 100644 (file)
index 0000000..ae1b59e
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="org.openhab.binding.solax-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
+       <repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
+
+       <feature name="openhab-binding-solax" description="solax Binding" version="${project.version}">
+               <feature>openhab-runtime-base</feature>
+               <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.solax/${project.version}</bundle>
+       </feature>
+</features>
diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java
new file mode 100644 (file)
index 0000000..74151b3
--- /dev/null
@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2010-2023 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.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link SolaxBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class SolaxBindingConstants {
+
+    private static final String BINDING_ID = "solax";
+    private static final String THING_LOCAL_CONNECT_INVERTER_ID = "local-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 Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_LOCAL_CONNECT_INVERTER);
+
+    // List of properties
+    public static final String PROPERTY_INVERTER_TYPE = "inverterType";
+
+    // List of all Channel ids
+    public static final String INVERTER_OUTPUT_POWER = "inverter-output-power";
+    public static final String INVERTER_OUTPUT_CURRENT = "inverter-current";
+    public static final String INVERTER_OUTPUT_VOLTAGE = "inverter-voltage";
+    public static final String INVERTER_OUTPUT_FREQUENCY = "inverter-frequency";
+
+    public static final String INVERTER_PV1_POWER = "pv1-power";
+    public static final String INVERTER_PV1_VOLTAGE = "pv1-voltage";
+    public static final String INVERTER_PV1_CURRENT = "pv1-current";
+
+    public static final String INVERTER_PV2_POWER = "pv2-power";
+    public static final String INVERTER_PV2_VOLTAGE = "pv2-voltage";
+    public static final String INVERTER_PV2_CURRENT = "pv2-current";
+
+    public static final String INVERTER_PV_TOTAL_POWER = "pv-total-power";
+    public static final String INVERTER_PV_TOTAL_CURRENT = "pv-total-current";
+
+    public static final String BATTERY_POWER = "battery-power";
+    public static final String BATTERY_VOLTAGE = "battery-voltage";
+    public static final String BATTERY_CURRENT = "battery-current";
+    public static final String BATTERY_TEMPERATURE = "battery-temperature";
+    public static final String BATTERY_STATE_OF_CHARGE = "battery-level";
+
+    public static final String FEED_IN_POWER = "feed-in-power";
+
+    public static final String TIMESTAMP = "last-update-time";
+    public static final String RAW_DATA = "raw-data";
+
+    // I18N Keys
+    protected static final String I18N_KEY_OFFLINE_COMMUNICATION_ERROR_JSON_CANNOT_BE_RETRIEVED = "@text/offline.communication-error.json-cannot-be-retrieved";
+}
diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxConfiguration.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxConfiguration.java
new file mode 100644 (file)
index 0000000..6114f74
--- /dev/null
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2010-2023 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 org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link SolaxConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class SolaxConfiguration {
+
+    public String hostname = "";
+    public String password = "";
+    public int refreshInterval = 10;
+}
diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxHandlerFactory.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxHandlerFactory.java
new file mode 100644 (file)
index 0000000..12c67ec
--- /dev/null
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2010-2023 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.openhab.binding.solax.internal.SolaxBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+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.Component;
+
+/**
+ * The {@link SolaxHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.solax", service = ThingHandlerFactory.class)
+public class SolaxHandlerFactory extends BaseThingHandlerFactory {
+
+    @Override
+    public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+        return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+    }
+
+    @Override
+    protected @Nullable ThingHandler createHandler(Thing thing) {
+        ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+        if (THING_TYPE_LOCAL_CONNECT_INVERTER.equals(thingTypeUID)) {
+            return new SolaxLocalAccessHandler(thing);
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java
new file mode 100644 (file)
index 0000000..7edcfaf
--- /dev/null
@@ -0,0 +1,171 @@
+/**
+ * Copyright (c) 2010-2023 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.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+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.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.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.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;
+
+    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() {
+        try {
+            String rawJsonData = localHttpConnector.retrieveData();
+            logger.debug("Raw data retrieved = {}", rawJsonData);
+
+            if (rawJsonData != null && !rawJsonData.isEmpty()) {
+                updateData(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());
+        }
+    }
+
+    private void updateData(String rawJsonData) {
+        try {
+            LocalConnectRawDataBean inverterParsedData = parseJson(rawJsonData);
+            updateThing(inverterParsedData);
+        } catch (JsonParseException e) {
+            logger.debug("Unable to deserialize from JSON.", e);
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+        }
+    }
+
+    private void updateThing(LocalConnectRawDataBean inverterParsedData) {
+        transferInverterDataToChannels(inverterParsedData);
+
+        if (getThing().getStatus() != ThingStatus.ONLINE) {
+            updateStatus(ThingStatus.ONLINE);
+        }
+    }
+
+    private LocalConnectRawDataBean parseJson(String rawJsonData) {
+        LocalConnectRawDataBean inverterParsedData = LocalConnectRawDataBean.fromJson(rawJsonData);
+        logger.debug("Received a new inverter data object. Data = {}", inverterParsedData.toStringDetailed());
+        return inverterParsedData;
+    }
+
+    private void transferInverterDataToChannels(InverterData data) {
+        updateProperty(Thing.PROPERTY_SERIAL_NUMBER, data.getWifiSerial());
+        updateProperty(SolaxBindingConstants.PROPERTY_INVERTER_TYPE, data.getInverterType().name());
+
+        updateState(SolaxBindingConstants.INVERTER_OUTPUT_POWER,
+                new QuantityType<>(data.getInverterOutputPower(), Units.WATT));
+        updateState(SolaxBindingConstants.INVERTER_OUTPUT_CURRENT,
+                new QuantityType<>(data.getInverterCurrent(), Units.AMPERE));
+        updateState(SolaxBindingConstants.INVERTER_OUTPUT_VOLTAGE,
+                new QuantityType<>(data.getInverterVoltage(), Units.VOLT));
+        updateState(SolaxBindingConstants.INVERTER_OUTPUT_FREQUENCY,
+                new QuantityType<>(data.getInverterFrequency(), Units.HERTZ));
+
+        updateState(SolaxBindingConstants.INVERTER_PV1_POWER, new QuantityType<>(data.getPV1Power(), Units.WATT));
+        updateState(SolaxBindingConstants.INVERTER_PV1_CURRENT, new QuantityType<>(data.getPV1Current(), Units.AMPERE));
+        updateState(SolaxBindingConstants.INVERTER_PV1_VOLTAGE, new QuantityType<>(data.getPV1Voltage(), Units.VOLT));
+
+        updateState(SolaxBindingConstants.INVERTER_PV2_POWER, new QuantityType<>(data.getPV2Power(), Units.WATT));
+        updateState(SolaxBindingConstants.INVERTER_PV2_CURRENT, new QuantityType<>(data.getPV2Current(), Units.AMPERE));
+        updateState(SolaxBindingConstants.INVERTER_PV2_VOLTAGE, new QuantityType<>(data.getPV2Voltage(), Units.VOLT));
+
+        updateState(SolaxBindingConstants.INVERTER_PV_TOTAL_POWER,
+                new QuantityType<>(data.getPVTotalPower(), Units.WATT));
+        updateState(SolaxBindingConstants.INVERTER_PV_TOTAL_CURRENT,
+                new QuantityType<>(data.getPVTotalCurrent(), Units.AMPERE));
+
+        updateState(SolaxBindingConstants.BATTERY_POWER, new QuantityType<>(data.getBatteryPower(), Units.WATT));
+        updateState(SolaxBindingConstants.BATTERY_CURRENT, new QuantityType<>(data.getBatteryCurrent(), Units.AMPERE));
+        updateState(SolaxBindingConstants.BATTERY_VOLTAGE, new QuantityType<>(data.getBatteryVoltage(), Units.VOLT));
+        updateState(SolaxBindingConstants.BATTERY_TEMPERATURE,
+                new QuantityType<>(data.getBatteryTemperature(), SIUnits.CELSIUS));
+        updateState(SolaxBindingConstants.BATTERY_STATE_OF_CHARGE,
+                new QuantityType<>(data.getBatterySoC(), Units.PERCENT));
+
+        updateState(SolaxBindingConstants.FEED_IN_POWER, new QuantityType<>(data.getFeedInPower(), Units.WATT));
+
+        updateState(SolaxBindingConstants.TIMESTAMP, new DateTimeType(ZonedDateTime.now()));
+        updateState(SolaxBindingConstants.RAW_DATA, new StringType(data.getRawData()));
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        // Nothing to do here as of now. Maybe implement a REFRESH command in the future.
+    }
+
+    @Override
+    public void dispose() {
+        super.dispose();
+        ScheduledFuture<?> schedule = this.schedule;
+        if (schedule != null) {
+            schedule.cancel(true);
+            this.schedule = null;
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/LocalHttpConnector.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/LocalHttpConnector.java
new file mode 100644 (file)
index 0000000..ab1525b
--- /dev/null
@@ -0,0 +1,73 @@
+/**
+ * Copyright (c) 2010-2023 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.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+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 LocalHttpConnector} class uses HttpUtil to retrieve the raw JSON data from Inverter's Wi-Fi module.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class LocalHttpConnector implements SolaxConnector {
+
+    private static final int HTTP_REQUEST_TIME_OUT = 5000;
+
+    private static final String CONTENT_TYPE = "text/html; charset=utf-8";
+
+    private final Logger logger = LoggerFactory.getLogger(LocalHttpConnector.class);
+
+    private static final String OPT_TYPE = "optType";
+    private static final String READ_REALTIME_DATA = "ReadRealTimeData";
+
+    private static final String PASSWORD = "pwd";
+    // The serial number of the Wifi dongle is the password for the connection (default)
+    private String passwordValue;
+    private String uri;
+
+    public LocalHttpConnector(String passwordValue, String host) {
+        this.passwordValue = passwordValue;
+        this.uri = "http://" + host;
+    }
+
+    @Override
+    public @Nullable String retrieveData() throws IOException {
+        String requestBody = createRequestBody();
+        logger.trace("Uri: {}, Request body: {}", uri, requestBody);
+        String result = HttpUtil.executeUrl(HttpMethod.POST.name(), uri,
+                new ByteArrayInputStream(requestBody.getBytes(StandardCharsets.UTF_8)), CONTENT_TYPE,
+                HTTP_REQUEST_TIME_OUT);
+        logger.trace("Retrieved content = {}", result);
+        return result;
+    }
+
+    private String createRequestBody() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append(OPT_TYPE).append("=").append(READ_REALTIME_DATA);
+        sb.append("&");
+        sb.append(PASSWORD).append("=").append(passwordValue);
+
+        return sb.toString();
+    }
+}
diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/SolaxConnector.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/SolaxConnector.java
new file mode 100644 (file)
index 0000000..25eda68
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2023 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;
+
+/**
+ * The {@link SolaxConnector} is interface for connecting to the Solax endpoints (cloud API or local IP)
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public interface SolaxConnector {
+
+    @Nullable
+    String retrieveData() throws IOException;
+}
diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/LocalConnectRawDataBean.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/LocalConnectRawDataBean.java
new file mode 100644 (file)
index 0000000..b6c9784
--- /dev/null
@@ -0,0 +1,255 @@
+/**
+ * Copyright (c) 2010-2023 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.model.InverterData;
+import org.openhab.binding.solax.internal.model.InverterType;
+import org.openhab.binding.solax.internal.util.GsonSupplier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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, InverterData {
+
+    private final Logger logger = LoggerFactory.getLogger(LocalConnectRawDataBean.class);
+
+    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;
+    }
+
+    // Parsed inverter data interface implementation starts here
+
+    @Override
+    public @Nullable String getWifiSerial() {
+        return getSn();
+    }
+
+    @Override
+    public @Nullable String getWifiVersion() {
+        return getVer();
+    }
+
+    @Override
+    public InverterType getInverterType() {
+        return InverterType.fromIndex(type);
+    }
+
+    @Override
+    public short getInverterVoltage() {
+        return (short) (getData(0) / 10);
+    }
+
+    @Override
+    public short getInverterCurrent() {
+        return (short) (getData(1) / 10);
+    }
+
+    @Override
+    public short getInverterOutputPower() {
+        return getData(2);
+    }
+
+    @Override
+    public short getInverterFrequency() {
+        return (short) (getData(3) / 100);
+    }
+
+    @Override
+    public short getPV1Voltage() {
+        return (short) (getData(4) / 10);
+    }
+
+    @Override
+    public short getPV1Current() {
+        return (short) (getData(6) / 10);
+    }
+
+    @Override
+    public short getPV1Power() {
+        return getData(8);
+    }
+
+    @Override
+    public short getPV2Voltage() {
+        return (short) (getData(5) / 10);
+    }
+
+    @Override
+    public short getPV2Current() {
+        return (short) (getData(7) / 10);
+    }
+
+    @Override
+    public short getPV2Power() {
+        return getData(9);
+    }
+
+    @Override
+    public short getBatteryVoltage() {
+        return (short) (getData(14) / 100);
+    }
+
+    @Override
+    public short getBatteryCurrent() {
+        return (short) (getData(15) / 100);
+    }
+
+    @Override
+    public short getBatteryPower() {
+        return getData(16);
+    }
+
+    @Override
+    public short getBatteryTemperature() {
+        return getData(17);
+    }
+
+    @Override
+    public short getBatterySoC() {
+        return getData(18);
+    }
+
+    @Override
+    public long getOnGridTotalYield() {
+        return packU16(11, 12) / 100;
+    }
+
+    @Override
+    public short getOnGridDailyYield() {
+        return (short) (getData(13) / 10);
+    }
+
+    @Override
+    public short getFeedInPower() {
+        return getData(32);
+    }
+
+    @Override
+    public long getTotalFeedInEnergy() {
+        return packU16(34, 35) / 100;
+    }
+
+    @Override
+    public long getTotalConsumption() {
+        return packU16(36, 37) / 100;
+    }
+
+    private short getData(int index) {
+        try {
+            short[] dataArray = data;
+            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;
+    }
+
+    private long packU16(int indexMajor, int indexMinor) {
+        short major = getData(indexMajor);
+        short minor = getData(indexMinor);
+        if (major == 0) {
+            return minor;
+        }
+
+        return ((major << 16) & 0xFFFF0000) | minor & 0xFFFF;
+    }
+}
diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/RawDataBean.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/RawDataBean.java
new file mode 100644 (file)
index 0000000..5edcddf
--- /dev/null
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2010-2023 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 org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link RawDataBean} is interface which should be implemented by all types of raw information that is retrieved
+ * (the idea is to retrieve a raw data from a Solax inverter locally or their cloud API)
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public interface RawDataBean {
+    @Nullable
+    String getRawData();
+}
diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterData.java
new file mode 100644 (file)
index 0000000..64d8375
--- /dev/null
@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2010-2023 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;
+import org.openhab.binding.solax.internal.connectivity.rawdata.RawDataBean;
+
+/**
+ * The {@link InverterData} interface should implement the interface that returns the parsed data in human readable code
+ * and format.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public interface InverterData extends RawDataBean {
+    @Nullable
+    String getWifiSerial();
+
+    @Nullable
+    String getWifiVersion();
+
+    InverterType getInverterType();
+
+    short getInverterVoltage();
+
+    short getInverterCurrent();
+
+    short getInverterOutputPower();
+
+    short getInverterFrequency();
+
+    short getPV1Voltage();
+
+    short getPV1Current();
+
+    short getPV1Power();
+
+    short getPV2Voltage();
+
+    short getPV2Current();
+
+    short getPV2Power();
+
+    default short getPVTotalPower() {
+        return (short) (getPV1Power() + getPV2Power());
+    }
+
+    default short getPVTotalCurrent() {
+        return (short) (getPV1Current() + getPV2Current());
+    }
+
+    short getBatteryVoltage(); // V / 100
+
+    short getBatteryCurrent(); // A / 100
+
+    short getBatteryPower(); // W
+
+    short getBatteryTemperature(); // temperature C
+
+    short getBatterySoC(); // % battery SoC
+
+    long getOnGridTotalYield(); // KWh total Yeld from the sun (to the grid?)
+
+    short getOnGridDailyYield(); // KWh daily Yeld from the sun (to the grid?)
+
+    long getTotalFeedInEnergy(); // KWh all times
+
+    long getTotalConsumption(); // KWh all times
+
+    short getFeedInPower();
+
+    default String toStringDetailed() {
+        return "WifiSerial = " + getWifiSerial() + ", WifiVersion = " + getWifiVersion() + ", InverterType = "
+                + getInverterType() + ", InverterVoltage = " + getInverterVoltage() + "V, InverterCurrent = "
+                + getInverterCurrent() + "A, InverterPower = " + getInverterOutputPower() + "W, BatteryPower = "
+                + getBatteryPower() + "W, Battery SoC = " + getBatterySoC() + "%, FeedIn Power = " + getFeedInPower()
+                + "W, Total PV Power = " + (getPV1Power() + getPV2Power()) + "W, Total Consumption = "
+                + getTotalConsumption() + "kWh, Total Feed-in Energy = " + getTotalFeedInEnergy()
+                + "kWh, Total On-Grid Yield = " + getOnGridTotalYield() + "kWh.";
+    }
+}
diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java
new file mode 100644 (file)
index 0000000..b8131fb
--- /dev/null
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2010-2023 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 java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link InverterType} class is enum representing the different inverter types with a simple logic to convert from
+ * int(coming from the JSON) to a more meaningful enum value.
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public enum InverterType {
+
+    X1_LX(1),
+    X_HYBRID(2),
+    X1_HYBRID_FIT(3),
+    X1_BOOST_AIR_MINI(4),
+    X3_HYBRID_FIT(5),
+    X3_20K_30K(6),
+    X3_MIC_PRO(7),
+    X1_SMART(8),
+    X1_AC(9),
+    A1_HYBRID(10),
+    A1_FIT(11),
+    A1_GRID(12),
+    J1_ESS(13),
+    X3_HYBRID_G4(14),
+    X1_HYBRID_G4(15),
+    UNKNOWN(-1);
+
+    private int typeIndex;
+
+    InverterType(int typeIndex) {
+        this.typeIndex = typeIndex;
+    }
+
+    public static InverterType fromIndex(int index) {
+        InverterType[] values = InverterType.values();
+        return Stream.of(values).filter(value -> value.typeIndex == index).findFirst().orElse(UNKNOWN);
+    }
+}
diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/util/GsonSupplier.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/util/GsonSupplier.java
new file mode 100644 (file)
index 0000000..283a6ce
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2010-2023 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.util;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.google.gson.Gson;
+
+/**
+ * The {@link GsonSupplier} provides a singleton instance of a Gson object
+ *
+ * @author Konstantin Polihronov - Initial contribution
+ */
+@NonNullByDefault
+public class GsonSupplier {
+    private static final Gson GSON = new Gson();
+
+    private GsonSupplier() {
+    };
+
+    public static Gson getInstance() {
+        return GSON;
+    }
+}
diff --git a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/addon/addon.xml
new file mode 100644 (file)
index 0000000..b455cf3
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<addon:addon id="solax" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
+
+       <type>binding</type>
+       <name>Solax Binding</name>
+       <description>This is the binding for Solax inverters.</description>
+       <connection>local</connection>
+
+</addon:addon>
diff --git a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties
new file mode 100644 (file)
index 0000000..3b13206
--- /dev/null
@@ -0,0 +1,67 @@
+# add-on
+
+addon.solax.name = Solax Binding
+addon.solax.description = This is the binding for Solax inverters.
+
+# thing types
+
+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-type.solax.local-connect-inverter.channel.battery-current.description = Electric current to/from the battery
+thing-type.solax.local-connect-inverter.channel.battery-level.label = Battery Level
+thing-type.solax.local-connect-inverter.channel.battery-level.description = The battery state of charge in percent
+thing-type.solax.local-connect-inverter.channel.battery-power.label = Battery Power
+thing-type.solax.local-connect-inverter.channel.battery-power.description = Power to/from the battery
+thing-type.solax.local-connect-inverter.channel.battery-temperature.label = Battery Temperature
+thing-type.solax.local-connect-inverter.channel.battery-temperature.description = Temperature of the battery
+thing-type.solax.local-connect-inverter.channel.battery-voltage.label = Battery Voltage
+thing-type.solax.local-connect-inverter.channel.battery-voltage.description = Electric voltage of the battery
+thing-type.solax.local-connect-inverter.channel.feed-in-power.label = Feed-in Power
+thing-type.solax.local-connect-inverter.channel.feed-in-power.description = Power to/from the electricity network.
+thing-type.solax.local-connect-inverter.channel.inverter-current.label = Inverter Input/Output Current
+thing-type.solax.local-connect-inverter.channel.inverter-current.description = Current to/from the inverter
+thing-type.solax.local-connect-inverter.channel.inverter-output-power.label = Inverter Input/Output Power
+thing-type.solax.local-connect-inverter.channel.inverter-output-power.description = Power to/from the inverter
+thing-type.solax.local-connect-inverter.channel.inverter-voltage.label = Inverter Voltage
+thing-type.solax.local-connect-inverter.channel.inverter-voltage.description = Voltage of the inverter
+thing-type.solax.local-connect-inverter.channel.pv-total-current.label = PV Total Current
+thing-type.solax.local-connect-inverter.channel.pv-total-current.description = The sum of PV currents from all strings
+thing-type.solax.local-connect-inverter.channel.pv-total-power.label = PV Total Power
+thing-type.solax.local-connect-inverter.channel.pv-total-power.description = The sum of PV powers from all strings
+thing-type.solax.local-connect-inverter.channel.pv1-current.label = PV 1 Current
+thing-type.solax.local-connect-inverter.channel.pv1-current.description = Electric current of PV String 1
+thing-type.solax.local-connect-inverter.channel.pv1-power.label = PV 1 Power
+thing-type.solax.local-connect-inverter.channel.pv1-power.description = Electric power of PV String 1
+thing-type.solax.local-connect-inverter.channel.pv1-voltage.label = PV 1 Voltage
+thing-type.solax.local-connect-inverter.channel.pv1-voltage.description = Electric voltage of PV String 1
+thing-type.solax.local-connect-inverter.channel.pv2-current.label = PV 2 Current
+thing-type.solax.local-connect-inverter.channel.pv2-current.description = Electric current of PV String 2
+thing-type.solax.local-connect-inverter.channel.pv2-power.label = PV 2 Power
+thing-type.solax.local-connect-inverter.channel.pv2-power.description = Electric power of PV String 2
+thing-type.solax.local-connect-inverter.channel.pv2-voltage.label = PV 2 Voltage
+thing-type.solax.local-connect-inverter.channel.pv2-voltage.description = Electric voltage of PV String 2
+
+# thing types config
+
+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
+thing-type.config.solax.local-connect-inverter.password.description = Password for accessing the Wi-Fi module (the serial number of the Wi-Fi module)
+thing-type.config.solax.local-connect-inverter.refreshInterval.label = Refresh Interval
+thing-type.config.solax.local-connect-inverter.refreshInterval.description = Specifies the refresh interval in seconds.
+
+# channel types
+
+channel-type.solax.battery-temperature.label = Battery Temperature
+channel-type.solax.battery-temperature.description = Battery Temperature
+channel-type.solax.frequency.label = Electric Frequency
+channel-type.solax.frequency.description = Frequency of the electricity to/from the inverter
+channel-type.solax.last-retrieve-time-stamp.label = Last Retrieve Time Stamp
+channel-type.solax.last-retrieve-time-stamp.description = Last time with a successful retrieval of data
+channel-type.solax.raw-data-type.label = Raw Data
+channel-type.solax.raw-data-type.description = The raw JSON data retrieved from the inverter's Wi-Fi module.
+
+# thing status descriptions
+
+offline.communication-error.json-cannot-be-retrieved = JSON data could not be retrieved.
diff --git a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/channel_types.xml b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/channel_types.xml
new file mode 100644 (file)
index 0000000..a1a8144
--- /dev/null
@@ -0,0 +1,41 @@
+<?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">
+
+       <channel-type id="frequency">
+               <item-type>Number:Frequency</item-type>
+               <label>Electric Frequency</label>
+               <description>Frequency of the electricity to/from the inverter</description>
+               <tags>
+                       <tag>Measurement</tag>
+                       <tag>Frequency</tag>
+               </tags>
+               <state pattern="%d %unit%" readOnly="true"/>
+       </channel-type>
+       <channel-type id="battery-temperature">
+               <item-type>Number:Temperature</item-type>
+               <label>Battery Temperature</label>
+               <description>Battery Temperature</description>
+               <tags>
+                       <tag>Measurement</tag>
+                       <tag>Temperature</tag>
+               </tags>
+               <state pattern="%d %unit%" readOnly="true"/>
+       </channel-type>
+       <channel-type id="last-retrieve-time-stamp">
+               <item-type>DateTime</item-type>
+               <label>Last Retrieve Time Stamp</label>
+               <description>Last time with a successful retrieval of data</description>
+               <category>Time</category>
+               <state pattern="yyyy-MM-dd HH:mm:ss" readOnly="true"/>
+       </channel-type>
+       <channel-type id="raw-data-type" advanced="true">
+               <item-type>String</item-type>
+               <label>Raw Data</label>
+               <description>The raw JSON data retrieved from the inverter's Wi-Fi module.</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/localConnectInverter.xml b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/localConnectInverter.xml
new file mode 100644 (file)
index 0000000..8a3c4f7
--- /dev/null
@@ -0,0 +1,109 @@
+<?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="local-connect-inverter">
+
+               <label>Local Connect Inverter</label>
+               <description>The inverter representation that supports local connections via HTTP</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="inverter-current" typeId="system.electric-current">
+                               <label>Inverter Input/Output Current</label>
+                               <description>Current to/from the inverter</description>
+                       </channel>
+                       <channel id="inverter-voltage" typeId="system.electric-voltage">
+                               <label>Inverter Voltage</label>
+                               <description>Voltage of the inverter</description>
+                       </channel>
+                       <channel id="inverter-frequency" typeId="frequency"/>
+
+                       <channel id="pv1-voltage" typeId="system.electric-voltage">
+                               <label>PV 1 Voltage</label>
+                               <description>Electric voltage of PV String 1</description>
+                       </channel>
+                       <channel id="pv2-voltage" typeId="system.electric-voltage">
+                               <label>PV 2 Voltage</label>
+                               <description>Electric voltage of PV String 2</description>
+                       </channel>
+                       <channel id="pv1-current" typeId="system.electric-current">
+                               <label>PV 1 Current</label>
+                               <description>Electric current of PV String 1</description>
+                       </channel>
+                       <channel id="pv2-current" typeId="system.electric-current">
+                               <label>PV 2 Current</label>
+                               <description>Electric current of PV String 2</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="pv-total-power" typeId="system.electric-power">
+                               <label>PV Total Power</label>
+                               <description>The sum of PV powers from all strings</description>
+                       </channel>
+                       <channel id="pv-total-current" typeId="system.electric-current">
+                               <label>PV Total Current</label>
+                               <description>The sum of PV currents from all strings</description>
+                       </channel>
+
+                       <channel id="battery-power" typeId="system.electric-power">
+                               <label>Battery Power</label>
+                               <description>Power to/from the battery</description>
+                       </channel>
+                       <channel id="battery-current" typeId="system.electric-current">
+                               <label>Battery Current</label>
+                               <description>Electric current to/from the battery</description>
+                       </channel>
+                       <channel id="battery-voltage" typeId="system.electric-voltage">
+                               <label>Battery Voltage</label>
+                               <description>Electric voltage of the battery</description>
+                       </channel>
+                       <channel id="battery-temperature" typeId="battery-temperature">
+                               <label>Battery Temperature</label>
+                               <description>Temperature of the battery</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="feed-in-power" typeId="system.electric-power">
+                               <label>Feed-in Power</label>
+                               <description>Power to/from the electricity network.</description>
+                       </channel>
+
+                       <channel id="last-update-time" typeId="last-retrieve-time-stamp"/>
+
+                       <channel id="raw-data" typeId="raw-data-type"/>
+               </channels>
+
+               <config-description>
+                       <parameter name="refreshInterval" type="integer" min="1" max="600">
+                               <label>Refresh Interval</label>
+                               <description>Specifies the refresh interval in seconds.</description>
+                               <default>10</default>
+                       </parameter>
+                       <parameter name="password" type="text" required="true">
+                               <label>Password</label>
+                               <description>Password for accessing the Wi-Fi module (the serial number of the Wi-Fi module)</description>
+                               <context>password</context>
+                       </parameter>
+                       <parameter name="hostname" type="text" required="true">
+                               <label>Network Address</label>
+                               <description>IP address or the host name of the Wi-Fi module</description>
+                               <context>network-address</context>
+                       </parameter>
+               </config-description>
+       </thing-type>
+</thing:thing-descriptions>
index dbaf85bd430fdf3f924eeada8d6ada0416d88e82..9c099d94e55fb26de183177f2dde2e59feabfb6e 100644 (file)
     <module>org.openhab.binding.solarlog</module>
     <module>org.openhab.binding.solarmax</module>
     <module>org.openhab.binding.solarwatt</module>
+    <module>org.openhab.binding.solax</module>
     <module>org.openhab.binding.somfymylink</module>
     <module>org.openhab.binding.somfytahoma</module>
     <module>org.openhab.binding.somneo</module>