]> git.basschouten.com Git - openhab-addons.git/commitdiff
[solarman] Initial contribution (#16835)
authorCatalin Sanda <catalin.sanda@gmail.com>
Tue, 30 Jul 2024 21:46:28 +0000 (00:46 +0300)
committerGitHub <noreply@github.com>
Tue, 30 Jul 2024 21:46:28 +0000 (23:46 +0200)
* Initial commit for the Solarman Binding.

Signed-off-by: Catalin Sanda <catalin.sanda@gmail.com>
55 files changed:
CODEOWNERS
bom/openhab-addons/pom.xml
bundles/org.openhab.binding.solarman/NOTICE [new file with mode: 0644]
bundles/org.openhab.binding.solarman/README.md [new file with mode: 0644]
bundles/org.openhab.binding.solarman/pom.xml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/feature/feature.xml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/DefinitionParser.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanHandlerFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanLoggerConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanLoggerHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/channel/BaseChannelConfig.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/channel/SolarmanChannelManager.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/InverterDefinition.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/Parameter.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/ParameterItem.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/Request.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/Validation.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/CRC16Modbus.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/SolarmanLoggerConnection.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/SolarmanLoggerConnector.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/SolarmanV5Protocol.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanAuthenticationException.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanConnectionException.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanException.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanProtocolException.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/typeprovider/ChannelUtils.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/typeprovider/SolarmanChannelTypeProvider.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/updater/SolarmanChannelUpdater.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/updater/SolarmanProcessResult.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/util/StreamUtils.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/addon/addon.xml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/datetime-channel-config.xml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/dynamic-channel-config.xml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/number-channel-config.xml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/string-channel-config.xml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/i18n/solarman.properties [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/thing/channels.xml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/thing/thing-types.xml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_2mppt.yaml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_4mppt.yaml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_hybrid.yaml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_sg04lp3.yaml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_string.yaml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/definitions/kstar_hybrid.yaml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_g3hyd.yaml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_hyd3k-6k-es.yaml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_lsw3.yaml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_wifikit.yaml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/definitions/solis_1p8k-5g.yaml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/definitions/solis_hybrid.yaml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/main/resources/definitions/zcs_azzurro-ktl-v3.yaml [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/test/java/org/openhab/binding/solarman/internal/DefinitionParserTest.java [new file with mode: 0644]
bundles/org.openhab.binding.solarman/src/test/java/org/openhab/binding/solarman/internal/modbus/SolarmanV5ProtocolTest.java [new file with mode: 0644]
bundles/pom.xml

index 0a4f724322f04f613bb51bdc7b578af97c2b577c..085876b5408405a7f202b841c0316d248a2ccd39 100755 (executable)
 /bundles/org.openhab.binding.solaredge/ @alexf2015
 /bundles/org.openhab.binding.solarforecast/ @weymann
 /bundles/org.openhab.binding.solarlog/ @johannrichard
+/bundles/org.openhab.binding.solarman/ @catalinsanda
 /bundles/org.openhab.binding.solarmax/ @jamietownsend
 /bundles/org.openhab.binding.solarwatt/ @sven-carstens
 /bundles/org.openhab.binding.solax/ @theater
index e95b1542ac8f542ef9501ecbe2420b0aac0e9167..e4acce9bd8d1e88fb7f252ab860b889ec1fd8670 100644 (file)
       <artifactId>org.openhab.binding.solarlog</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.openhab.addons.bundles</groupId>
+      <artifactId>org.openhab.binding.solarman</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>org.openhab.addons.bundles</groupId>
       <artifactId>org.openhab.binding.solarmax</artifactId>
diff --git a/bundles/org.openhab.binding.solarman/NOTICE b/bundles/org.openhab.binding.solarman/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.solarman/README.md b/bundles/org.openhab.binding.solarman/README.md
new file mode 100644 (file)
index 0000000..7676fd2
--- /dev/null
@@ -0,0 +1,290 @@
+# Solarman Logger Binding
+
+Binding used to communicate with Solarman (IGEN-Tech) v5 based solar inverter data loggers in direct-mode over the local network.
+More information about the different types of stick loggers is available on the [Solarman](https://www.solarmanpv.com/products/data-logger/stick-logger/) site.
+
+These data loggers are used by inverters from a lot of manufacturers, just to name a few:
+
+- Deye
+- Sofar
+- Solis
+- ZCS Azzurro
+- KStar
+
+## Supported Things
+
+The `solarman:logger` thing supports reading data from a Solarman LSW-3 Stick Logger (it might also work with LSE-3 and maybe others) when connected to a supported inverter.
+
+It was tested on a SUN-12K-SG04LP3-EU only, but because the implementation uses the inverter definitions created as part of Stephan Joubert's Home Assistant plugin it **might** work with the other inverters supported by the plugin.
+
+## Thing Configuration
+
+To connect the logger you need the IP address of the logger and its serial number.
+The IP address can be obtained from your router and the serial number can either be read from the label of the logger, or by connecting to the logger with a browser (default user/pass: admin/admin) and getting it from the Status page.
+*Please note* that you need the "Device serial number" from the "Device information" section, not the "Inverter serial number".
+
+### `logger` Thing Configuration
+
+| Name               | Type    | Description                                            | Default | Required | Advanced |
+|--------------------|---------|--------------------------------------------------------|---------|----------|----------|
+| hostname           | text    | Hostname or IP address of the Solarman logger          | N/A     | yes      | no       |
+| serialNumber       | text    | Serial number of the Solarman logger                   | N/A     | yes      | no       |
+| inverterType       | text    | The type of inverter connected to the logger           | N/A     | yes      | no       |
+| port               | integer | Port of the Solarman logger                            | 8899    | no       | yes      |
+| refreshInterval    | integer | Interval the device is polled in sec.                  | 60      | no       | yes      |
+| additionalRequests | text    | Additional requests besides the ones in the definition | N/A     | no       | yes      |
+
+The `inverterType` parameter governs what registers the binding will read from the logger and what channels it will expose.
+
+Possible values:
+
+| Inverter Type      | Inverters supported                         | Notes                                                            |
+|--------------------|---------------------------------------------|------------------------------------------------------------------|
+| deye_hybrid        | DEYE/Sunsynk/SolArk Hybrid inverters        | used when no lookup specified                                    |
+| deye_sg04lp3       | DEYE/Sunsynk/SolArk Hybrid 8/12K-SG04LP3    | e.g. 12K-SG04LP3-EU                                              |
+| deye_string        | DEYE/Sunsynk/SolArk String inverters        | e.g. SUN-4/5/6/7/8/10/12K-G03 Plus                               |
+| deye_2mppt         | DEYE Microinverter with 2 MPPT Trackers     | e.g. SUN600G3-EU-230 / SUN800G3-EU-230 / SUN1000G3-EU-230        |
+| deye_4mppt         | DEYE Microinverter with 4 MPPT Trackers     | e.g. SUN1300G3-EU-230 / SUN1600G3-EU-230 / SUN2000G3-EU-230      |
+| sofar_lsw3         | SOFAR Inverters                             |                                                                  |
+| sofar_g3hyd        | SOFAR Hybrid Three-Phase inverter           | HYD 6000 or rebranded (three-phase), ex. ZCS Azzurro 3PH HYD-ZSS |
+| sofar_hyd3k-6k-es  | SOFAR Hybrid Single-Phase inverter          | HYD 6000 or rebranded (single-phase), ex. ZCS Azzurro HYD-ZSS    |
+| solis_hybrid       | SOLIS Hybrid inverter                       |                                                                  |
+| solid_1p8k-5g      | SOLIS 1P8K-5G                               |                                                                  |
+| zcs_azzurro-ktl-v3 | ZCS Azzurro KTL-V3 inverters                | ZCS Azzurro 3.3/4.4/5.5/6.6 KTL-V3 (rebranded Sofar KTLX-G3)     |
+
+The `additionalRequests` allows the user to specify additional address ranges to be polled. The format of the value is `mb_functioncode1:start1-end1, mb_functioncode2:start2-end2,...`
+For example `"0x03:0x27D-0x27E"` will issue an additional read for Holding Registers between `0x27D` and `0x27E`.
+
+This is useful when coupled with user defined channels, for example a thing definition like the one below will also read the register for the AC frequency on a Deye inverter, besides the ones pre-defined in the `deye_sg04lp3` inverter definition.
+
+```java
+Thing solarman:logger:local [ hostname="x.x.x.x", inverterType="deye_sg04lp3", serialNumber="1234567890", additionalRequests="0x03:0x27D-0x27E" ] {
+        Channels:
+            Type number : Inverter_Frequency [scale="0.01", uom="Hz", rule="3", registers="0x27E"]
+}
+```
+
+**Please note** As of this writing inverter types besides the `deye_sg04lp3` were not tested to work. 
+If you have one of those inverters and it works, please drop me a message, if it doesn't work, please open an issue and I'll try to fix it.
+
+## Channels
+
+The list of channels is not static, it is generated dynamically based on the inverter type selected.
+
+This is the list you get for the `deye_sg04lp3` inverter type:
+
+| Channel                                  | Type   | Read/Write   | Description                                           |
+|------------------------------------------|--------|--------------|-------------------------------------------------------|
+| alert-alert                              | Number | R            | Alert \[0x0229,0x022A,0x022B,0x022C,0x022D,0x022E\]   |
+| battery-battery-current                  | Number | R            | Battery Current \[0x024F\]                            |
+| battery-battery-power                    | Number | R            | Battery Power \[0x024E\]                              |
+| battery-battery-soc                      | Number | R            | Battery SOC \[0x024C\]                                |
+| battery-battery-temperature              | Number | R            | Battery Temperature \[0x024A\]                        |
+| battery-battery-voltage                  | Number | R            | Battery Voltage \[0x024B\]                            |
+| battery-daily-battery-charge             | Number | R            | Daily Battery Charge \[0x0202\]                       |
+| battery-daily-battery-discharge          | Number | R            | Daily Battery Discharge \[0x0203\]                    |
+| battery-total-battery-charge             | Number | R            | Total Battery Charge \[0x0204,0x0205\]                |
+| battery-total-battery-discharge          | Number | R            | Total Battery Discharge \[0x0206,0x0207\]             |
+| grid-daily-energy-bought                 | Number | R            | Daily Energy Bought \[0x0208\]                        |
+| grid-daily-energy-sold                   | Number | R            | Daily Energy Sold \[0x0209\]                          |
+| grid-external-ct-l1-power                | Number | R            | External CT L1 Power \[0x0268\]                       |
+| grid-external-ct-l2-power                | Number | R            | External CT L2 Power \[0x0269\]                       |
+| grid-external-ct-l3-power                | Number | R            | External CT L3 Power \[0x026A\]                       |
+| grid-grid-voltage-l1                     | Number | R            | Grid Voltage L1 \[0x0256\]                            |
+| grid-grid-voltage-l2                     | Number | R            | Grid Voltage L2 \[0x0257\]                            |
+| grid-grid-voltage-l3                     | Number | R            | Grid Voltage L3 \[0x0258\]                            |
+| grid-internal-ct-l1-power                | Number | R            | Internal CT L1 Power \[0x025C\]                       |
+| grid-internal-ct-l2-power                | Number | R            | Internal CT L2 Power \[0x025D\]                       |
+| grid-internal-ct-l3-power                | Number | R            | Internal CT L3 Power \[0x025E\]                       |
+| grid-total-energy-bought                 | Number | R            | Total Energy Bought \[0x020A,0x020B\]                 |
+| grid-total-energy-sold                   | Number | R            | Total Energy Sold \[0x020C\]                          |
+| grid-total-grid-power                    | Number | R            | Total Grid Power \[0x0271\]                           |
+| grid-total-grid-production               | Number | R            | Total Grid Production \[0x020C,0x020D\]               |
+| inverter-ac-temperature                  | Number | R            | AC Temperature \[0x021D\]                             |
+| inverter-communication-board-version-no- | Number | R            | Communication Board Version No \[0x0011\]             |
+| inverter-control-board-version-no-       | Number | R            | Control Board Version No \[0x000D\]                   |
+| inverter-current-l1                      | Number | R            | Current L1 \[0x0276\]                                 |
+| inverter-current-l2                      | Number | R            | Current L2 \[0x0277\]                                 |
+| inverter-current-l3                      | Number | R            | Current L3 \[0x0278\]                                 |
+| inverter-dc-temperature                  | Number | R            | DC Temperature \[0x021C\]                             |
+| inverter-frequency                       | Number | R            | Number Value \[0x27E\]                                |
+| inverter-inverter-id                     | String | R            | Inverter ID \[0x0003,0x0004,0x0005,0x0006,0x0007\]    |
+| inverter-inverter-l1-power               | Number | R            | Inverter L1 Power \[0x0279\]                          |
+| inverter-inverter-l2-power               | Number | R            | Inverter L2 Power \[0x027A\]                          |
+| inverter-inverter-l3-power               | Number | R            | Inverter L3 Power \[0x027B\]                          |
+| solar-daily-production                   | Number | R            | Daily Production \[0x0211\]                           |
+| solar-pv1-current                        | Number | R            | PV1 Current \[0x02A5\]                                |
+| solar-pv1-power                          | Number | R            | PV1 Power \[0x02A0\]                                  |
+| solar-pv1-voltage                        | Number | R            | PV1 Voltage \[0x02A4\]                                |
+| solar-pv2-current                        | Number | R            | PV2 Current \[0x02A7\]                                |
+| solar-pv2-power                          | Number | R            | PV2 Power \[0x02A1\]                                  |
+| solar-pv2-voltage                        | Number | R            | PV2 Voltage \[0x02A6\]                                |
+| solar-total-production                   | Number | R            | Total Production \[0x0216,0x0217\]                    |
+| upload-daily-load-consumption            | Number | R            | Daily Load Consumption \[0x020E\]                     |
+| upload-load-l1-power                     | Number | R            | Load L1 Power \[0x028A\]                              |
+| upload-load-l2-power                     | Number | R            | Load L2 Power \[0x028B\]                              |
+| upload-load-l3-power                     | Number | R            | Load L3 Power \[0x028C\]                              |
+| upload-load-voltage-l1                   | Number | R            | Load Voltage L1 \[0x0284\]                            |
+| upload-load-voltage-l2                   | Number | R            | Load Voltage L2 \[0x0285\]                            |
+| upload-load-voltage-l3                   | Number | R            | Load Voltage L3 \[0x0286\]                            |
+| upload-total-load-consumption            | Number | R            | Total Load Consumption \[0x020F,0x0210\]              |
+| upload-total-load-power                  | Number | R            | Total Load Power \[0x028D\]                           |
+
+## Full Example
+
+This is an example for a DEYE 12kW (SUN-12K-SG04LP3-EU) hybrid inverter
+
+### `solarman.things`
+
+Please replace the `hostname` and `serialNumber` with the correct values for your logger.
+
+```java
+Thing solarman:logger:local [hostname="x.x.x.x",inverterType="deye_sg04lp3",serialNumber="1234567890"]
+```
+
+### `solarman.items`
+
+Items file example for a SUN-12K-SG04LP3-EU inverter
+
+```text
+Number:Dimensionless      Communication_Board_Version_No  "Communication Board Version No [%s]"  (solarman)  {channel="solarman:logger:local:inverter-communication-board-version-no-"}
+Number:Dimensionless      Control_Board_Version_No        "Control Board Version No [%s]"        (solarman)  {channel="solarman:logger:local:inverter-control-board-version-no-"}
+String                    Inverter_Id                     "Inverter Id [%s]"                     (solarman)  {channel="solarman:logger:local:inverter-inverter-id"}
+Number:Temperature        AC_Temperature                  "AC Temperature [%.1f Â°C]"             (solarman)  {channel="solarman:logger:local:inverter-ac-temperature", unit="°C"}
+Number:Temperature        DC_Temperature                  "DC Temperature [%.1f Â°C]"             (solarman)  {channel="solarman:logger:local:inverter-dc-temperature", unit="°C"}
+Number:Power              Inverter_L1_Power               "Inverter L1 Power [%d W]"             (solarman)  {channel="solarman:logger:local:inverter-inverter-l1-power", unit="W"}
+Number:Power              Inverter_L2_Power               "Inverter L2 Power [%d W]"             (solarman)  {channel="solarman:logger:local:inverter-inverter-l2-power", unit="W"}
+Number:Power              Inverter_L3_Power               "Inverter L3 Power [%d W]"             (solarman)  {channel="solarman:logger:local:inverter-inverter-l3-power", unit="W"}
+Number:ElectricCurrent    Current_L1                      "Current L1 [%.1f A]"                  (solarman)  {channel="solarman:logger:local:inverter-current-l1", unit="A"}
+Number:ElectricCurrent    Current_L2                      "Current L2 [%.1f A]"                  (solarman)  {channel="solarman:logger:local:inverter-current-l2", unit="A"}
+Number:ElectricCurrent    Current_L3                      "Current L3 [%.1f A]"                  (solarman)  {channel="solarman:logger:local:inverter-current-l3", unit="A"}
+Number:Power              External_CT_L1_Power            "External CT L1 Power [%d W]"          (solarman)  {channel="solarman:logger:local:grid-external-ct-l1-power", unit="W"}
+Number:Power              External_CT_L2_Power            "External CT L2 Power [%d W]"          (solarman)  {channel="solarman:logger:local:grid-external-ct-l2-power", unit="W"}
+Number:Power              External_CT_L3_Power            "External CT L3 Power [%d W]"          (solarman)  {channel="solarman:logger:local:grid-external-ct-l3-power", unit="W"}
+Number:Power              Internal_CT_L1_Power            "Internal CT L1 Power [%d W]"          (solarman)  {channel="solarman:logger:local:grid-internal-ct-l1-power", unit="W"}
+Number:Power              Internal_CT_L2_Power            "Internal CT L2 Power [%d W]"          (solarman)  {channel="solarman:logger:local:grid-internal-ct-l2-power", unit="W"}
+Number:Power              Internal_CT_L3_Power            "Internal CT L3 Power [%d W]"          (solarman)  {channel="solarman:logger:local:grid-internal-ct-l3-power", unit="W"}
+Number:ElectricPotential  Grid_Voltage_L1                 "Grid Voltage L1 [%d V]"               (solarman)  {channel="solarman:logger:local:grid-grid-voltage-l1", unit="V"}
+Number:ElectricPotential  Grid_Voltage_L2                 "Grid Voltage L2 [%d V]"               (solarman)  {channel="solarman:logger:local:grid-grid-voltage-l2", unit="V"}
+Number:ElectricPotential  Grid_Voltage_L3                 "Grid Voltage L3 [%d V]"               (solarman)  {channel="solarman:logger:local:grid-grid-voltage-l3", unit="V"}
+Number:Power              Total_Grid_Power                "Total Instant Grid Power [%d W]"      (solarman)  {channel="solarman:logger:local:grid-total-grid-power", unit="W"}
+Number:Energy             Total_Grid_Production           "Total Grid Feed-in [%.1f kWh]"        (solarman)  {channel="solarman:logger:local:grid-total-grid-production", unit="kWh"}
+Number:Energy             Daily_Energy_Sold               "Daily Energy Sold [%d Wh]"            (solarman)  {channel="solarman:logger:local:grid-daily-energy-sold", unit="Wh"}
+Number:Energy             Total_Energy_Sold               "Total Energy Sold [%d kWh]"           (solarman)  {channel="solarman:logger:local:grid-total-energy-sold", unit="kWh"}
+Number:Energy             Total_Energy_Bought             "Total Energy Bought [%d kWh]"         (solarman)  {channel="solarman:logger:local:grid-total-energy-bought", unit="kWh"}
+Number:Energy             Daily_Energy_Bought             "Daily Energy Bought [%d kWh]"         (solarman)  {channel="solarman:logger:local:grid-daily-energy-bought", unit="kWh"}
+Number:Energy             Daily_Production                "Daily Production [%.1f kWh]"          (solarman)  {channel="solarman:logger:local:solar-daily-production", unit="kWh"}
+Number:Energy             Total_Production                "Total Production [%d kWh]"            (solarman)  {channel="solarman:logger:local:solar-total-production", unit="kWh"}
+Number:Energy             Daily_Load_Consumption          "Daily Load Consumption [%.1f kWh]"    (solarman)  {channel="solarman:logger:local:upload-daily-load-consumption", unit="kWh"}
+Number:Energy             Total_Load_Consumption          "Total Load Consumption [%d kWh]"      (solarman)  {channel="solarman:logger:local:upload-total-load-consumption", unit="kWh"}
+Number:Power              Load_L1_Power                   "Load L1 Power [%d W]"                 (solarman)  {channel="solarman:logger:local:upload-load-l1-power", unit="W"}
+Number:Power              Load_L2_Power                   "Load L2 Power [%d W]"                 (solarman)  {channel="solarman:logger:local:upload-load-l2-power", unit="W"}
+Number:Power              Load_L3_Power                   "Load L3 Power [%d W]"                 (solarman)  {channel="solarman:logger:local:upload-load-l3-power", unit="W"}
+Number:Power              Total_Load_Power                "Total Load Power [%d W]"              (solarman)  {channel="solarman:logger:local:upload-total-load-power", unit="W"}
+Number:ElectricPotential  Load_Voltage_L1                 "Load Voltage L1 [%d V]"               (solarman)  {channel="solarman:logger:local:upload-load-voltage-l1", unit="V"}
+Number:ElectricPotential  Load_Voltage_L2                 "Load Voltage L2 [%d V]"               (solarman)  {channel="solarman:logger:local:upload-load-voltage-l2", unit="V"}
+Number:ElectricPotential  Load_Voltage_L3                 "Load Voltage L3 [%d V]"               (solarman)  {channel="solarman:logger:local:upload-load-voltage-l3", unit="V"}
+Number:Energy             Daily_Energy_Consumption        "Daily Energy Consumption [%d kWh]"    (solarman)  {channel="solarman:logger:local:upload-daily-load-consumption", unit="kWh"}
+Number:Energy             Total_Energy_Consumption        "Total Energy Consumption [%d kWh]"    (solarman)  {channel="solarman:logger:local:upload-total-load-consumption", unit="kWh"}
+Number:ElectricCurrent    PV1_Current                     "PV1 Current [%.1f A]"                 (solarman)  {channel="solarman:logger:local:solar-pv1-current", unit="A"}
+Number:Power              PV1_Power                       "PV1 Power [%d W]"                     (solarman)  {channel="solarman:logger:local:solar-pv1-power", unit="W"}
+Number:ElectricPotential  PV1_Voltage                     "PV1 Voltage [%d V]"                   (solarman)  {channel="solarman:logger:local:solar-pv1-voltage", unit="V"}
+Number:ElectricCurrent    PV2_Current                     "PV2 Current [%.1f A]"                 (solarman)  {channel="solarman:logger:local:solar-pv2-current", unit="A"}
+Number:Power              PV2_Power                       "PV2 Power [%d W]"                     (solarman)  {channel="solarman:logger:local:solar-pv2-power", unit="W"}
+Number:ElectricPotential  PV2_Voltage                     "PV2 Voltage [%d V]"                   (solarman)  {channel="solarman:logger:local:solar-pv2-voltage", unit="V"}
+Number:Dimensionless      Battery_SOC                     "Battery SOC [%d %%]"                  (solarman)  {channel="solarman:logger:local:battery-battery-soc", unit="%"}
+Number:ElectricCurrent    Battery_Current                 "Battery Current [%.1f A]"             (solarman)  {channel="solarman:logger:local:battery-battery-current", unit="A"}
+Number:Power              Battery_Power                   "Battery Power [%d W]"                 (solarman)  {channel="solarman:logger:local:battery-battery-power", unit="W"}
+Number:ElectricPotential  Battery_Voltage                 "Battery Voltage [%.2f V]"             (solarman)  {channel="solarman:logger:local:battery-battery-voltage", unit="V"}
+Number:Temperature        Battery_Temperature             "Battery Temperature [%.1f Â°C]"        (solarman)  {channel="solarman:logger:local:battery-battery-temperature", unit="°C"}
+Number:Energy             Daily_Battery_Charge            "Daily Battery Charge [%.1f kWh]"      (solarman)  {channel="solarman:logger:local:battery-daily-battery-charge", unit="kWh"}
+Number:Energy             Daily_Battery_Discharge         "Daily Battery Discharge [%.1f kWh]"   (solarman)  {channel="solarman:logger:local:battery-daily-battery-discharge", unit="kWh"}
+Number:Energy             Total_Battery_Charge            "Total Battery Charge [%d kWh]"        (solarman)  {channel="solarman:logger:local:battery-total-battery-charge", unit="kWh"}
+Number:Energy             Total_Battery_Discharge         "Total Battery Discharge [%d kWh]"     (solarman)  {channel="solarman:logger:local:battery-total-battery-discharge", unit="kWh"}
+Number                    Alert                           "Alert [%s]"                           (solarman)  {channel="solarman:logger:local:alert-alert"}
+```
+
+### `solarman.sitemap`
+
+Sitemap example for a SUN-12K-SG04LP3-EU inverter
+
+```perl
+sitemap solarman label="Solarman"
+{
+    Frame label="Inverter"{
+        Text item=Communication_Board_Version_No icon="solar"
+        Text item=Control_Board_Version_No icon="solar"
+        Text item=Inverter_Id icon="solar"
+        Text item=AC_Temperature icon="temperature"
+        Text item=DC_Temperature icon="temperature"
+        Text item=Inverter_L1_Power icon="poweroutlet"
+        Text item=Inverter_L2_Power icon="poweroutlet"
+        Text item=Inverter_L3_Power icon="poweroutlet"
+        Text item=Current_L1 icon="line"
+        Text item=Current_L2 icon="line"
+        Text item=Current_L3 icon="line"
+    }
+    
+    Frame label="Battery"{
+        Text item=Battery_SOC icon="battery"
+        Text item=Battery_Current icon="current"
+        Text item=Battery_Power icon="power"
+        Text item=Battery_Voltage icon="voltage"
+        Text item=Battery_Temperature icon="temperature"
+        Text item=Daily_Battery_Charge icon="renewable"
+        Text item=Daily_Battery_Discharge icon="battery"
+        Text item=Total_Battery_Charge icon="renewable"
+        Text item=Total_Battery_Discharge icon="battery"
+    }
+    
+    Frame label="Solar"{
+        Text item=Total_Production icon="solar"
+        Text item=Daily_Production icon="solar"
+        Text item=PV1_Current icon="solar"
+        Text item=PV1_Power icon="solar"
+        Text item=PV1_Voltage icon="solar"
+        Text item=PV2_Current icon="solar"
+        Text item=PV2_Power icon="solar"
+        Text item=PV2_Voltage icon="solar"
+    }
+    
+    Frame label="Grid"{
+        Text item=Total_Grid_Production icon="power"
+        Text item=Total_Grid_Power icon="power"
+        Text item=External_CT_L1_Power icon="power"
+        Text item=External_CT_L2_Power icon="power"
+        Text item=External_CT_L3_Power icon="power"
+        Text item=Internal_CT_L1_Power icon="power"
+        Text item=Internal_CT_L2_Power icon="power"
+        Text item=Internal_CT_L3_Power icon="power"
+        Text item=Grid_Voltage_L1 icon="power"
+        Text item=Grid_Voltage_L2 icon="power"
+        Text item=Grid_Voltage_L3 icon="power"
+        Text item=Daily_Energy_Sold icon="power"
+        Text item=Total_Energy_Sold icon="power"
+        Text item=Daily_Energy_Bought icon="power"
+        Text item=Total_Energy_Bought icon="power"
+    }
+    
+    Frame label="Load"{
+        Text item=Daily_Load_Consumption icon="power"
+        Text item=Total_Load_Consumption icon="power"
+        Text item=Load_L1_Power icon="power"
+        Text item=Load_L2_Power icon="power"
+        Text item=Load_L3_Power icon="power"
+        Text item=Load_Voltage_L1 icon="power"
+        Text item=Load_Voltage_L2 icon="power"
+        Text item=Load_Voltage_L3 icon="power"
+        Text item=Total_Load_Power icon="power"
+    }
+
+        Frame label="Alert"{
+            Text item=Alert icon="alert"
+    }
+}
+```
+
+## Acknowledgments
+
+The code's creation draws significant inspiration from [Stephan Joubert's Home Assistant plugin](https://github.com/StephanJoubert/home_assistant_solarman), which provides the inverter definitions used in the project.
+Additionally, the [pysolarmanv5 module](https://pysolarmanv5.readthedocs.io/en/latest/index.html) was a valuable resource, as it offers an excellent explanation of the Solarman V5 protocol.
diff --git a/bundles/org.openhab.binding.solarman/pom.xml b/bundles/org.openhab.binding.solarman/pom.xml
new file mode 100644 (file)
index 0000000..a344625
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  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.3.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>org.openhab.binding.solarman</artifactId>
+
+  <name>openHAB Add-ons :: Bundles :: Solarman Binding</name>
+
+</project>
diff --git a/bundles/org.openhab.binding.solarman/src/main/feature/feature.xml b/bundles/org.openhab.binding.solarman/src/main/feature/feature.xml
new file mode 100644 (file)
index 0000000..de6deb6
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="org.openhab.binding.solarman-${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-solarman" description="Solarman Binding" version="${project.version}">
+               <feature>openhab-runtime-base</feature>
+               <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.solarman/${project.version}</bundle>
+       </feature>
+</features>
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/DefinitionParser.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/DefinitionParser.java
new file mode 100644 (file)
index 0000000..93c1fcb
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * 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.solarman.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solarman.internal.defmodel.InverterDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+
+/**
+ * The {@link DefinitionParser} is parses inverter definitions
+ *
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+public class DefinitionParser {
+    private final Logger logger = LoggerFactory.getLogger(DefinitionParser.class);
+
+    private final ObjectMapper mapper;
+
+    public DefinitionParser() {
+        mapper = new ObjectMapper(new YAMLFactory());
+    }
+
+    @Nullable
+    public InverterDefinition parseDefinition(String definitionId) {
+        ClassLoader cl = Objects.requireNonNull(getClass().getClassLoader());
+        String definitionFileName = String.format("definitions/%s.yaml", definitionId);
+        try (InputStream is = cl.getResourceAsStream(definitionFileName)) {
+            if (is == null) {
+                logger.warn("Unable to read definition file {}", definitionFileName);
+                return null;
+            }
+
+            InverterDefinition inverterDefinition = mapper.readValue(is, InverterDefinition.class);
+            inverterDefinition.setInverterDefinitionId(definitionId);
+
+            return inverterDefinition;
+        } catch (IOException e) {
+            logger.warn("Error parsing definition with ID: {}", definitionId, e);
+            return null;
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanBindingConstants.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanBindingConstants.java
new file mode 100644 (file)
index 0000000..1152bde
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * 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.solarman.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link SolarmanBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+public class SolarmanBindingConstants {
+
+    public static final String SOLARMAN_BINDING_ID = "solarman";
+    public static final ThingTypeUID THING_TYPE_SOLARMAN_LOGGER = new ThingTypeUID(SOLARMAN_BINDING_ID, "logger");
+    public static final String DYNAMIC_CHANNEL = "dynamic-channel";
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanHandlerFactory.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanHandlerFactory.java
new file mode 100644 (file)
index 0000000..543df07
--- /dev/null
@@ -0,0 +1,53 @@
+/**
+ * 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.solarman.internal;
+
+import java.util.Set;
+
+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 SolarmanHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.solarman", service = ThingHandlerFactory.class)
+public class SolarmanHandlerFactory extends BaseThingHandlerFactory {
+    private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set
+            .of(SolarmanBindingConstants.THING_TYPE_SOLARMAN_LOGGER);
+
+    @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 (SolarmanBindingConstants.THING_TYPE_SOLARMAN_LOGGER.equals(thingTypeUID)) {
+            return new SolarmanLoggerHandler(thing);
+        }
+
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanLoggerConfiguration.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanLoggerConfiguration.java
new file mode 100644 (file)
index 0000000..4e8b082
--- /dev/null
@@ -0,0 +1,74 @@
+/**
+ * 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.solarman.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link SolarmanLoggerConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+public class SolarmanLoggerConfiguration {
+
+    /**
+     * Solarman Logger Thing Configuration Parameters
+     */
+    public String hostname = "";
+    public Integer port = 8899;
+    public String serialNumber = "";
+    public String inverterType = "sg04lp3";
+    public int refreshInterval = 30;
+    @Nullable
+    public String additionalRequests;
+
+    public SolarmanLoggerConfiguration() {
+    }
+
+    public SolarmanLoggerConfiguration(String hostname, Integer port, String serialNumber, String inverterType,
+            int refreshInterval, @Nullable String additionalRequests) {
+        this.hostname = hostname;
+        this.port = port;
+        this.serialNumber = serialNumber;
+        this.inverterType = inverterType;
+        this.refreshInterval = refreshInterval;
+        this.additionalRequests = additionalRequests;
+    }
+
+    public String getHostname() {
+        return hostname;
+    }
+
+    public Integer getPort() {
+        return port;
+    }
+
+    public String getSerialNumber() {
+        return serialNumber;
+    }
+
+    public String getInverterType() {
+        return inverterType;
+    }
+
+    public int getRefreshInterval() {
+        return refreshInterval;
+    }
+
+    @Nullable
+    public String getAdditionalRequests() {
+        return additionalRequests;
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanLoggerHandler.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanLoggerHandler.java
new file mode 100644 (file)
index 0000000..a5dfb68
--- /dev/null
@@ -0,0 +1,225 @@
+/**
+ * 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.solarman.internal;
+
+import static org.openhab.binding.solarman.internal.SolarmanBindingConstants.DYNAMIC_CHANNEL;
+
+import java.util.AbstractMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solarman.internal.channel.BaseChannelConfig;
+import org.openhab.binding.solarman.internal.channel.SolarmanChannelManager;
+import org.openhab.binding.solarman.internal.defmodel.InverterDefinition;
+import org.openhab.binding.solarman.internal.defmodel.ParameterItem;
+import org.openhab.binding.solarman.internal.defmodel.Request;
+import org.openhab.binding.solarman.internal.defmodel.Validation;
+import org.openhab.binding.solarman.internal.modbus.SolarmanLoggerConnector;
+import org.openhab.binding.solarman.internal.modbus.SolarmanV5Protocol;
+import org.openhab.binding.solarman.internal.updater.SolarmanChannelUpdater;
+import org.openhab.binding.solarman.internal.updater.SolarmanProcessResult;
+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.thing.binding.builder.ThingBuilder;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link SolarmanLoggerHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+public class SolarmanLoggerHandler extends BaseThingHandler {
+    private final Logger logger = LoggerFactory.getLogger(SolarmanLoggerHandler.class);
+
+    private final DefinitionParser definitionParser;
+    private final SolarmanChannelManager solarmanChannelManager;
+    @Nullable
+    private volatile ScheduledFuture<?> scheduledFuture;
+
+    public SolarmanLoggerHandler(Thing thing) {
+        super(thing);
+        this.definitionParser = new DefinitionParser();
+        this.solarmanChannelManager = new SolarmanChannelManager();
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+    }
+
+    @Override
+    public void initialize() {
+        updateStatus(ThingStatus.UNKNOWN);
+
+        SolarmanLoggerConfiguration config = getConfigAs(SolarmanLoggerConfiguration.class);
+        SolarmanLoggerConnector solarmanLoggerConnector = new SolarmanLoggerConnector(config);
+
+        List<Channel> staticChannels = thing.getChannels().stream()
+                .filter(channel -> !channel.getProperties().containsKey(DYNAMIC_CHANNEL)).toList();
+
+        InverterDefinition inverterDefinition = definitionParser.parseDefinition(config.inverterType);
+
+        if (inverterDefinition == null) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+                    "Unable to find a definition for the provided inverter type");
+            return;
+        } else {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Found definition for {}", config.inverterType);
+            }
+        }
+        SolarmanV5Protocol solarmanV5Protocol = new SolarmanV5Protocol(config);
+
+        String additionalRequests = Objects.requireNonNullElse(config.getAdditionalRequests(), "");
+
+        List<Request> mergedRequests = !additionalRequests.isBlank()
+                ? mergeRequests(inverterDefinition.getRequests(), extractAdditionalRequests(additionalRequests))
+                : inverterDefinition.getRequests();
+
+        Map<ParameterItem, ChannelUID> paramToChannelMapping = mergeMaps(
+                extractChannelMappingFromChannels(staticChannels),
+                setupChannelsForInverterDefinition(inverterDefinition));
+
+        SolarmanChannelUpdater solarmanChannelUpdater = new SolarmanChannelUpdater(this::updateState);
+
+        scheduledFuture = scheduler
+                .scheduleWithFixedDelay(
+                        () -> queryLoggerAndUpdateState(solarmanLoggerConnector, solarmanV5Protocol, mergedRequests,
+                                paramToChannelMapping, solarmanChannelUpdater),
+                        0, config.refreshInterval, TimeUnit.SECONDS);
+    }
+
+    private void queryLoggerAndUpdateState(SolarmanLoggerConnector solarmanLoggerConnector,
+            SolarmanV5Protocol solarmanV5Protocol, List<Request> mergedRequests,
+            Map<ParameterItem, ChannelUID> paramToChannelMapping, SolarmanChannelUpdater solarmanChannelUpdater) {
+        try {
+            SolarmanProcessResult solarmanProcessResult = solarmanChannelUpdater.fetchDataFromLogger(mergedRequests,
+                    solarmanLoggerConnector, solarmanV5Protocol, paramToChannelMapping);
+
+            if (solarmanProcessResult.hasSuccessfulResponses()) {
+                updateStatus(ThingStatus.ONLINE);
+            } else {
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                        solarmanProcessResult.toString());
+            }
+        } catch (Exception e) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+        }
+    }
+
+    private <K, V> Map<K, V> mergeMaps(Map<K, V> map1, Map<K, V> map2) {
+        return Stream.concat(map1.entrySet().stream(), map2.entrySet().stream())
+                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1));
+    }
+
+    private Map<ParameterItem, ChannelUID> extractChannelMappingFromChannels(List<Channel> channels) {
+        return channels.stream().map(channel -> {
+            BaseChannelConfig bcc = channel.getConfiguration().as(BaseChannelConfig.class);
+
+            @Nullable
+            String label = channel.getLabel();
+            if (label == null) {
+                throw new IllegalStateException("Channel label should not be null");
+            }
+
+            return new AbstractMap.SimpleEntry<>(new ParameterItem(label, "N/A", "N/A", bcc.uom, bcc.scale, bcc.rule,
+                    parseRegisters(bcc.registers), "N/A", new Validation(), bcc.offset, Boolean.FALSE),
+                    channel.getUID());
+        }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+    }
+
+    private List<Integer> parseRegisters(String registers) {
+        String[] tokens = registers.split(",");
+        Pattern pattern = Pattern.compile("\\s*(0x[\\da-fA-F]+|[\\d]+)\\s*");
+        return Stream.of(tokens).map(pattern::matcher).filter(Matcher::matches).map(matcher -> matcher.group(1))
+                .map(SolarmanLoggerHandler::parseNumber).toList();
+    }
+
+    // For now just concatenate the list, in the future, merge overlapping requests
+    private List<Request> mergeRequests(List<Request> requestList1, List<Request> requestList2) {
+        return Stream.concat(requestList1.stream(), requestList2.stream()).collect(Collectors.toList());
+    }
+
+    private List<Request> extractAdditionalRequests(String channels) {
+        String[] tokens = channels.split(",");
+        Pattern pattern = Pattern.compile(
+                "\\s*(0x[\\da-fA-F]+|[\\d]+)\\s*:\\s*(0x[\\da-fA-F]+|[\\d]+)\\s*-\\s*(0x[\\da-fA-F]+|[\\d]+)\\s*");
+
+        return Stream.of(tokens).map(pattern::matcher).filter(Matcher::matches).map(matcher -> {
+            try {
+                int functionCode = parseNumber(matcher.group(1));
+                int start = parseNumber(matcher.group(2));
+                int end = parseNumber(matcher.group(3));
+                return new Request(functionCode, start, end);
+            } catch (NumberFormatException e) {
+                logger.debug("Invalid number format in token: {} , ignoring additional requests", matcher.group(), e);
+                return new Request(-1, 0, 0);
+            }
+        }).filter(request -> request.getMbFunctioncode() > 0).collect(Collectors.toList());
+    }
+
+    private static int parseNumber(String number) {
+        return number.startsWith("0x") ? Integer.parseInt(number.substring(2), 16) : Integer.parseInt(number);
+    }
+
+    private Map<ParameterItem, ChannelUID> setupChannelsForInverterDefinition(InverterDefinition inverterDefinition) {
+        ThingBuilder thingBuilder = editThing();
+
+        List<Channel> oldDynamicChannels = thing.getChannels().stream()
+                .filter(channel -> channel.getProperties().containsKey(DYNAMIC_CHANNEL)).toList();
+
+        Map<ParameterItem, Channel> newDynamicItemChannelMap = solarmanChannelManager.generateItemChannelMap(thing,
+                inverterDefinition);
+
+        // Remove old dynamic channels
+        thingBuilder.withoutChannels(oldDynamicChannels);
+
+        // Add new dynamic channels
+        newDynamicItemChannelMap.values().forEach(thingBuilder::withChannel);
+
+        updateThing(thingBuilder.build());
+
+        logger.debug("Updated thing with id {} and {} channels", thing.getThingTypeUID(), thing.getChannels().size());
+
+        return newDynamicItemChannelMap.entrySet().stream()
+                .map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(), entry.getValue().getUID()))
+                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+    }
+
+    @Override
+    public void dispose() {
+        super.dispose();
+
+        ScheduledFuture<?> scheduledFuture = this.scheduledFuture;
+        if (scheduledFuture != null) {
+            scheduledFuture.cancel(false);
+            this.scheduledFuture = null;
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/channel/BaseChannelConfig.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/channel/BaseChannelConfig.java
new file mode 100644 (file)
index 0000000..1eb99cf
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * 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.solarman.internal.channel;
+
+import java.math.BigDecimal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+public class BaseChannelConfig {
+    public @Nullable String uom;
+    public BigDecimal scale = BigDecimal.ONE;
+    public Integer rule = 1;
+    public BigDecimal offset = BigDecimal.ZERO;
+    public String registers = "";
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/channel/SolarmanChannelManager.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/channel/SolarmanChannelManager.java
new file mode 100644 (file)
index 0000000..5fad543
--- /dev/null
@@ -0,0 +1,100 @@
+/**
+ * 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.solarman.internal.channel;
+
+import static org.openhab.binding.solarman.internal.SolarmanBindingConstants.DYNAMIC_CHANNEL;
+import static org.openhab.binding.solarman.internal.typeprovider.ChannelUtils.escapeName;
+
+import java.math.BigDecimal;
+import java.util.AbstractMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarman.internal.defmodel.InverterDefinition;
+import org.openhab.binding.solarman.internal.defmodel.ParameterItem;
+import org.openhab.binding.solarman.internal.typeprovider.ChannelUtils;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.binding.builder.ChannelBuilder;
+import org.openhab.core.thing.type.ChannelKind;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+public class SolarmanChannelManager {
+    private final ObjectMapper objectMapper;
+
+    public SolarmanChannelManager() {
+        objectMapper = new ObjectMapper();
+    }
+
+    public Map<ParameterItem, Channel> generateItemChannelMap(Thing thing, InverterDefinition inverterDefinition) {
+        return inverterDefinition.getParameters().stream().flatMap(parameter -> {
+            String groupName = escapeName(parameter.getGroup());
+
+            return parameter.getItems().stream().map(item -> {
+                String channelId = groupName + "-" + escapeName(item.getName());
+
+                Channel channel = ChannelBuilder.create(new ChannelUID(thing.getUID(), channelId))
+                        .withType(ChannelUtils.computeChannelTypeId(inverterDefinition.getInverterDefinitionId(),
+                                groupName, item.getName()))
+                        .withLabel(item.getName()).withKind(ChannelKind.STATE)
+                        .withAcceptedItemType(ChannelUtils.getItemType(item))
+                        .withProperties(Map.of(DYNAMIC_CHANNEL, Boolean.TRUE.toString()))
+                        .withConfiguration(buildConfigurationFromItem(item)).build();
+                return new AbstractMap.SimpleEntry<>(item, channel);
+            });
+        }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+    }
+
+    private Configuration buildConfigurationFromItem(ParameterItem item) {
+        Configuration configuration = new Configuration();
+
+        BaseChannelConfig baseChannelConfig = new BaseChannelConfig();
+
+        BigDecimal offset = item.getOffset();
+        if (offset != null) {
+            baseChannelConfig.offset = offset;
+        }
+
+        BigDecimal scale = item.getScale();
+        if (scale != null) {
+            baseChannelConfig.scale = scale;
+        }
+
+        baseChannelConfig.rule = item.getRule();
+        baseChannelConfig.registers = convertRegisters(item.getRegisters());
+        baseChannelConfig.uom = item.getUom();
+
+        Map<String, Object> configurationMap = objectMapper.convertValue(baseChannelConfig, new TypeReference<>() {
+        });
+
+        configurationMap.forEach(configuration::put);
+
+        return configuration;
+    }
+
+    private String convertRegisters(List<Integer> registers) {
+        return "["
+                + registers.stream().map(register -> String.format("0x%04X", register)).collect(Collectors.joining(","))
+                + "]";
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/InverterDefinition.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/InverterDefinition.java
new file mode 100644 (file)
index 0000000..b4c4f95
--- /dev/null
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.solarman.internal.defmodel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@NonNullByDefault
+public class InverterDefinition {
+    private String inverterDefinitionId = "";
+    private List<Request> requests = new ArrayList<>();
+    private List<Parameter> parameters = new ArrayList<>();
+
+    public String getInverterDefinitionId() {
+        return inverterDefinitionId;
+    }
+
+    public void setInverterDefinitionId(String inverterDefinitionId) {
+        this.inverterDefinitionId = inverterDefinitionId;
+    }
+
+    public List<Request> getRequests() {
+        return requests;
+    }
+
+    public void setRequests(List<Request> requests) {
+        this.requests = requests;
+    }
+
+    public List<Parameter> getParameters() {
+        return parameters;
+    }
+
+    public void setParameters(List<Parameter> parameters) {
+        this.parameters = parameters;
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/Parameter.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/Parameter.java
new file mode 100644 (file)
index 0000000..0e43f9e
--- /dev/null
@@ -0,0 +1,46 @@
+/**
+ * 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.solarman.internal.defmodel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@NonNullByDefault
+public class Parameter {
+    private String group = "";
+    private List<ParameterItem> items = new ArrayList<ParameterItem>();
+
+    public String getGroup() {
+        return group;
+    }
+
+    public void setGroup(String group) {
+        this.group = group;
+    }
+
+    public List<ParameterItem> getItems() {
+        return items;
+    }
+
+    public void setItems(List<ParameterItem> items) {
+        this.items = items;
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/ParameterItem.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/ParameterItem.java
new file mode 100644 (file)
index 0000000..bc5a9cb
--- /dev/null
@@ -0,0 +1,156 @@
+/**
+ * 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.solarman.internal.defmodel;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@NonNullByDefault
+public class ParameterItem {
+    private String name = "";
+    @Nullable
+    private String itemClass;
+    @Nullable
+    private String stateClass;
+    @Nullable
+    private String uom;
+    @Nullable
+    private BigDecimal scale;
+    private Integer rule = 1;
+    private List<Integer> registers = new ArrayList<>();
+    @Nullable
+    private String icon;
+    @Nullable
+    private Validation validation;
+    @Nullable
+    private BigDecimal offset;
+    @Nullable
+    private Boolean isstr;
+
+    public ParameterItem() {
+    }
+
+    public ParameterItem(String name, @Nullable String itemClass, @Nullable String stateClass, @Nullable String uom,
+            @Nullable BigDecimal scale, Integer rule, List<Integer> registers, @Nullable String icon,
+            @Nullable Validation validation, @Nullable BigDecimal offset, @Nullable Boolean isstr) {
+        this.name = name;
+        this.itemClass = itemClass;
+        this.stateClass = stateClass;
+        this.uom = uom;
+        this.scale = scale;
+        this.rule = rule;
+        this.registers = registers;
+        this.icon = icon;
+        this.validation = validation;
+        this.offset = offset;
+        this.isstr = isstr;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public @Nullable String getStateClass() {
+        return stateClass;
+    }
+
+    public void setStateClass(String stateClass) {
+        this.stateClass = stateClass;
+    }
+
+    public @Nullable String getUom() {
+        return uom;
+    }
+
+    public void setUom(String uom) {
+        this.uom = uom;
+    }
+
+    public @Nullable BigDecimal getScale() {
+        return scale;
+    }
+
+    public void setScale(BigDecimal scale) {
+        this.scale = scale;
+    }
+
+    public Integer getRule() {
+        return rule;
+    }
+
+    public void setRule(Integer rule) {
+        this.rule = rule;
+    }
+
+    public List<Integer> getRegisters() {
+        return registers;
+    }
+
+    public void setRegisters(List<Integer> registers) {
+        this.registers = registers;
+    }
+
+    public @Nullable String getIcon() {
+        return icon;
+    }
+
+    public void setIcon(String icon) {
+        this.icon = icon;
+    }
+
+    public @Nullable Validation getValidation() {
+        return validation;
+    }
+
+    public void setValidation(Validation validation) {
+        this.validation = validation;
+    }
+
+    public @Nullable BigDecimal getOffset() {
+        return offset;
+    }
+
+    public void setOffset(BigDecimal offset) {
+        this.offset = offset;
+    }
+
+    public @Nullable Boolean getIsstr() {
+        return isstr;
+    }
+
+    public void setIsstr(Boolean isstr) {
+        this.isstr = isstr;
+    }
+
+    public @Nullable String getItemClass() {
+        return itemClass;
+    }
+
+    public void setItemClass(String itemClass) {
+        this.itemClass = itemClass;
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/Request.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/Request.java
new file mode 100644 (file)
index 0000000..b6659cd
--- /dev/null
@@ -0,0 +1,74 @@
+/**
+ * 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.solarman.internal.defmodel;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@NonNullByDefault
+public class Request {
+    public static final Request NONE = new Request(-1, 0, 0);
+    private Integer start = 0;
+    private Integer end = 0;
+    @JsonProperty("mb_functioncode")
+    private Integer mbFunctioncode = 0x03;
+
+    public Request() {
+    }
+
+    public Request(Integer mbFunctioncode, Integer start, Integer end) {
+        this.mbFunctioncode = mbFunctioncode;
+        this.start = start;
+        this.end = end;
+    }
+
+    public Integer getStart() {
+        return start;
+    }
+
+    public void setStart(Integer start) {
+        this.start = start;
+    }
+
+    public Integer getEnd() {
+        return end;
+    }
+
+    public void setEnd(Integer end) {
+        this.end = end;
+    }
+
+    public Integer getMbFunctioncode() {
+        return mbFunctioncode;
+    }
+
+    public void setMbFunctioncode(Integer mbFunctioncode) {
+        this.mbFunctioncode = mbFunctioncode;
+    }
+
+    @Override
+    public String toString() {
+        if (this == NONE) {
+            return "N/A";
+        } else {
+            return "Request{" + "start=0x" + Integer.toHexString(start) + ", end=0x" + Integer.toHexString(end)
+                    + ", mbFunctioncode=0x" + Integer.toHexString(mbFunctioncode) + '}';
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/Validation.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/Validation.java
new file mode 100644 (file)
index 0000000..3ca171e
--- /dev/null
@@ -0,0 +1,54 @@
+/**
+ * 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.solarman.internal.defmodel;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@NonNullByDefault
+public class Validation {
+    private Integer max = 0;
+    @JsonProperty("invalidate_all")
+    private Object invalidateAll = new Object();
+    private Integer min = 0;
+
+    public Integer getMax() {
+        return max;
+    }
+
+    public void setMax(Integer max) {
+        this.max = max;
+    }
+
+    public Object getInvalidateAll() {
+        return invalidateAll;
+    }
+
+    public void setInvalidateAll(Object invalidateAll) {
+        this.invalidateAll = invalidateAll;
+    }
+
+    public Integer getMin() {
+        return min;
+    }
+
+    public void setMin(Integer min) {
+        this.min = min;
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/CRC16Modbus.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/CRC16Modbus.java
new file mode 100644 (file)
index 0000000..7bb9658
--- /dev/null
@@ -0,0 +1,45 @@
+/**
+ * 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.solarman.internal.modbus;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+public class CRC16Modbus {
+    private static final int[] CRC_TABLE = new int[256];
+
+    static {
+        for (int i = 0; i < 256; i++) {
+            int crc = i;
+            for (int j = 0; j < 8; j++) {
+                if ((crc & 0x0001) != 0) {
+                    crc = (crc >>> 1) ^ 0xA001;
+                } else {
+                    crc = crc >>> 1;
+                }
+            }
+            CRC_TABLE[i] = crc;
+        }
+    }
+
+    public static int calculate(byte[] data) {
+        int crc = 0xFFFF;
+        for (byte b : data) {
+            crc = (crc >>> 8) ^ CRC_TABLE[(crc ^ b) & 0xFF];
+        }
+        return crc;
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/SolarmanLoggerConnection.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/SolarmanLoggerConnection.java
new file mode 100644 (file)
index 0000000..cf31673
--- /dev/null
@@ -0,0 +1,131 @@
+/**
+ * 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.solarman.internal.modbus;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketTimeoutException;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solarman.internal.modbus.exception.SolarmanConnectionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+public class SolarmanLoggerConnection implements AutoCloseable {
+    private final Logger logger = LoggerFactory.getLogger(SolarmanLoggerConnection.class);
+    @Nullable
+    private Socket socket;
+
+    public SolarmanLoggerConnection(String hostName, int port) {
+        SocketAddress sockaddr = new InetSocketAddress(hostName, port);
+        Socket localSocket = connectSocket(sockaddr);
+
+        if (localSocket == null) {
+            logger.debug("Error creating socket");
+        } else {
+            socket = localSocket;
+        }
+    }
+
+    public byte[] sendRequest(byte[] reqFrame) throws SolarmanConnectionException {
+        // Will not be used by multiple threads, so not bothering making it thread safe for now
+        Socket localSocket = socket;
+
+        if (localSocket == null) {
+            throw new SolarmanConnectionException("Socket is null, not reading data this time");
+        }
+
+        try {
+            logger.trace("Request frame: {}", bytesToHex(reqFrame));
+            localSocket.getOutputStream().write(reqFrame);
+        } catch (IOException e) {
+            logger.debug("Unable to send frame to logger");
+            return new byte[0];
+        }
+
+        byte[] buffer = new byte[1024];
+        int attempts = 5;
+
+        while (attempts > 0) {
+            attempts--;
+            try {
+                int bytesRead = localSocket.getInputStream().read(buffer);
+                if (bytesRead < 0) {
+                    throw new SolarmanConnectionException("No data received");
+                } else {
+                    byte[] data = Arrays.copyOfRange(buffer, 0, bytesRead);
+                    if (logger.isDebugEnabled()) {
+                        logger.trace("Response frame: {}", bytesToHex(data));
+                    }
+                    return data;
+                }
+            } catch (SocketTimeoutException e) {
+                logger.debug("Connection timeout", e);
+                if (attempts == 0) {
+                    throw new SolarmanConnectionException("Too many socket timeouts", e);
+                }
+            } catch (IOException e) {
+                throw new SolarmanConnectionException("Error reading data from ", e);
+            }
+        }
+
+        return new byte[0];
+    }
+
+    private static String bytesToHex(byte[] bytes) {
+        return IntStream.range(0, bytes.length).mapToObj(i -> String.format("%02X", bytes[i]))
+                .collect(Collectors.joining());
+    }
+
+    private @Nullable Socket connectSocket(SocketAddress socketAddress) {
+        try {
+            Socket clientSocket = new Socket();
+
+            clientSocket.setSoTimeout(10_000);
+            clientSocket.connect(socketAddress, 10_000);
+
+            return clientSocket;
+        } catch (IOException e) {
+            logger.debug("Could not open socket on IP {}", socketAddress, e);
+            return null;
+        }
+    }
+
+    @Override
+    public void close() {
+        Socket localSocket = socket;
+        if (localSocket != null && !localSocket.isClosed()) {
+            try {
+                localSocket.close();
+            } catch (IOException e) {
+                logger.debug("Unable to close connection");
+            }
+        }
+        socket = null;
+    }
+
+    public boolean isConnected() {
+        Socket localSocket = socket;
+        return localSocket != null && localSocket.isConnected();
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/SolarmanLoggerConnector.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/SolarmanLoggerConnector.java
new file mode 100644 (file)
index 0000000..b3ef66b
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.solarman.internal.modbus;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarman.internal.SolarmanLoggerConfiguration;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+public class SolarmanLoggerConnector {
+    private final SolarmanLoggerConfiguration solarmanLoggerConfiguration;
+
+    public SolarmanLoggerConnector(SolarmanLoggerConfiguration solarmanLoggerConfiguration) {
+        this.solarmanLoggerConfiguration = solarmanLoggerConfiguration;
+    }
+
+    public SolarmanLoggerConnection createConnection() {
+        return new SolarmanLoggerConnection(solarmanLoggerConfiguration.getHostname(),
+                solarmanLoggerConfiguration.getPort());
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/SolarmanV5Protocol.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/SolarmanV5Protocol.java
new file mode 100644 (file)
index 0000000..16f484b
--- /dev/null
@@ -0,0 +1,252 @@
+/**
+ * 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.solarman.internal.modbus;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solarman.internal.SolarmanLoggerConfiguration;
+import org.openhab.binding.solarman.internal.modbus.exception.SolarmanAuthenticationException;
+import org.openhab.binding.solarman.internal.modbus.exception.SolarmanConnectionException;
+import org.openhab.binding.solarman.internal.modbus.exception.SolarmanException;
+import org.openhab.binding.solarman.internal.modbus.exception.SolarmanProtocolException;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+public class SolarmanV5Protocol {
+    private final SolarmanLoggerConfiguration solarmanLoggerConfiguration;
+
+    public SolarmanV5Protocol(SolarmanLoggerConfiguration solarmanLoggerConfiguration) {
+        this.solarmanLoggerConfiguration = solarmanLoggerConfiguration;
+    }
+
+    public Map<Integer, byte[]> readRegisters(SolarmanLoggerConnection solarmanLoggerConnection, byte mbFunctionCode,
+            int firstReg, int lastReg) throws SolarmanException {
+        byte[] solarmanV5Frame = buildSolarmanV5Frame(mbFunctionCode, firstReg, lastReg);
+        byte[] respFrame = solarmanLoggerConnection.sendRequest(solarmanV5Frame);
+        if (respFrame.length > 0) {
+            byte[] modbusRespFrame = extractModbusResponseFrame(respFrame, solarmanV5Frame);
+            return parseModbusReadHoldingRegistersResponse(modbusRespFrame, firstReg, lastReg);
+        } else {
+            throw new SolarmanConnectionException("Response frame was empty");
+        }
+    }
+
+    /**
+     * Builds a SolarMAN V5 frame to request data from firstReg to lastReg.
+     * Frame format is based on
+     * <a href="https://pysolarmanv5.readthedocs.io/en/latest/solarmanv5_protocol.html">Solarman V5 Protocol</a>
+     *
+     * @param mbFunctionCode
+     * @param firstReg - the start register
+     * @param lastReg - the end register
+     * @return byte array containing the Solarman V5 frame
+     */
+    protected byte[] buildSolarmanV5Frame(byte mbFunctionCode, int firstReg, int lastReg) {
+        byte[] requestPayload = buildSolarmanV5FrameRequestPayload(mbFunctionCode, firstReg, lastReg);
+        byte[] header = buildSolarmanV5FrameHeader(requestPayload.length);
+        byte[] trailer = buildSolarmanV5FrameTrailer(header, requestPayload);
+
+        return ByteBuffer.allocate(header.length + requestPayload.length + trailer.length).put(header)
+                .put(requestPayload).put(trailer).array();
+    }
+
+    private byte[] buildSolarmanV5FrameTrailer(byte[] header, byte[] requestPayload) {
+        byte[] headerAndPayload = ByteBuffer.allocate(header.length + requestPayload.length).put(header)
+                .put(requestPayload).array();
+        // (one byte) â€“ Denotes the V5 frame checksum. The checksum is computed on the entire V5 frame except for Start,
+        // Checksum (obviously!) and End.
+        // Note, that this field is completely separate to the Modbus RTU checksum, which coincidentally, is the two
+        // bytes immediately preceding this field.
+        byte[] checksum = new byte[] {
+                computeChecksum(Arrays.copyOfRange(headerAndPayload, 1, headerAndPayload.length)) };
+
+        // (one byte) â€“ Denotes the end of the V5 frame. Always 0x15.
+        byte[] end = new byte[] { (byte) 0x15 };
+
+        return ByteBuffer.allocate(checksum.length + end.length).put(checksum).put(end).array();
+    }
+
+    private byte computeChecksum(byte[] frame) {
+        // [-91, 23, 0, 16, 69, 0, 0, 46, -13, 90, 102, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 0, 0, 0, 39,
+        // 5, -48, 122, 21]
+        int checksumValue = 0;
+        for (byte b : frame) {
+            checksumValue += Byte.toUnsignedInt(b);
+        }
+        return (byte) (checksumValue & 0xFF);
+    }
+
+    private byte[] buildSolarmanV5FrameHeader(int payloadSize) {
+        // (one byte) Denotes the start of the V5 frame. Always 0xA5.
+        byte[] start = new byte[] { (byte) 0xA5 };
+
+        // (two bytes) Payload length
+        byte[] length = ByteBuffer.allocate(Short.BYTES).order(ByteOrder.LITTLE_ENDIAN).putShort((short) payloadSize)
+                .array();
+
+        // (two bytes) â€“ Describes the type of V5 frame. For Modbus RTU requests, the control code is 0x4510. For Modbus
+        // RTU responses, the control code is 0x1510.
+        byte[] controlCode = new byte[] { (byte) 0x10, (byte) 0x45 };
+
+        // (two bytes) â€“ This field acts as a two-way sequence number. On outgoing requests, the first byte of this
+        // field is echoed back in the same position on incoming responses.
+        // This is done by initialising this byte to a random value, and incrementing for each subsequent request.
+        // The second byte is incremented by the data logging stick for every response sent (either to Solarman Cloud or
+        // local requests).
+        // Note: the increment part is not implemented yet
+        byte[] serial = new byte[] { (byte) 0x00, (byte) 0x00 };
+
+        // (four bytes) â€“ Serial number of Solarman data logging stick
+        byte[] loggerSerial = ByteBuffer.allocate(Integer.BYTES).order(ByteOrder.LITTLE_ENDIAN)
+                .putInt((int) Long.parseUnsignedLong(solarmanLoggerConfiguration.getSerialNumber())).array();
+
+        // Append all fields into the header
+        return ByteBuffer
+                .allocate(start.length + length.length + controlCode.length + serial.length + loggerSerial.length)
+                .put(start).put(length).put(controlCode).put(serial).put(loggerSerial).array();
+    }
+
+    protected byte[] buildSolarmanV5FrameRequestPayload(byte mbFunctionCode, int firstReg, int lastReg) {
+        // (one byte) â€“ Denotes the frame type.
+        byte[] frameType = new byte[] { 0x02 };
+        // (two bytes) â€“ Denotes the sensor type.
+        byte[] sensorType = new byte[] { 0x00, 0x00 };
+        // (four bytes) â€“ Denotes the frame total working time. See corresponding response field of same name for
+        // further details.
+        byte[] totalWorkingTime = new byte[] { 0x00, 0x00, 0x00, 0x00 };
+        // (four bytes) â€“ Denotes the frame power on time.
+        byte[] powerOnTime = new byte[] { 0x00, 0x00, 0x00, 0x00 };
+        // Denotes the frame offset time.
+        byte[] offsetTime = new byte[] { 0x00, 0x00, 0x00, 0x00 };
+        // (variable length) â€“ Modbus RTU request frame.
+        byte[] requestFrame = buildModbusReadHoldingRegistersRequestFrame((byte) 0x01, mbFunctionCode, firstReg,
+                lastReg);
+
+        return ByteBuffer
+                .allocate(frameType.length + sensorType.length + totalWorkingTime.length + powerOnTime.length
+                        + offsetTime.length + requestFrame.length)
+                .put(frameType).put(sensorType).put(totalWorkingTime).put(powerOnTime).put(offsetTime).put(requestFrame)
+                .array();
+    }
+
+    /**
+     * Based on <a href="https://www.modbustools.com/modbus.html#function03">Function 03 (03hex) Read Holding
+     * Registers</a>
+     *
+     * @param slaveId - Slave Address
+     * @param mbFunctionCode -
+     * @param firstReg - Starting Address
+     * @param lastReg - Ending Address
+     * @return byte array containing the Modbus request frame
+     */
+    protected byte[] buildModbusReadHoldingRegistersRequestFrame(byte slaveId, byte mbFunctionCode, int firstReg,
+            int lastReg) {
+        int regCount = lastReg - firstReg + 1;
+        byte[] req = ByteBuffer.allocate(6).put(slaveId).put(mbFunctionCode).putShort((short) firstReg)
+                .putShort((short) regCount).array();
+        byte[] crc = ByteBuffer.allocate(Short.BYTES).order(ByteOrder.LITTLE_ENDIAN)
+                .putShort((short) CRC16Modbus.calculate(req)).array();
+
+        return ByteBuffer.allocate(req.length + crc.length).put(req).put(crc).array();
+    }
+
+    protected Map<Integer, byte[]> parseModbusReadHoldingRegistersResponse(byte @Nullable [] frame, int firstReg,
+            int lastReg) throws SolarmanProtocolException {
+        int regCount = lastReg - firstReg + 1;
+        Map<Integer, byte[]> registers = new HashMap<>();
+        int expectedFrameDataLen = 2 + 1 + regCount * 2;
+        if (frame == null || frame.length < expectedFrameDataLen + 2) {
+            throw new SolarmanProtocolException("Modbus frame is too short or empty");
+        }
+
+        int actualCrc = ByteBuffer.wrap(frame, expectedFrameDataLen, 2).order(ByteOrder.LITTLE_ENDIAN).getShort()
+                & 0xFFFF;
+        int expectedCrc = CRC16Modbus.calculate(Arrays.copyOfRange(frame, 0, expectedFrameDataLen));
+
+        if (actualCrc != expectedCrc) {
+            throw new SolarmanProtocolException(
+                    String.format("Modbus frame crc is not valid. Expected %04x, got %04x", expectedCrc, actualCrc));
+        }
+
+        for (int i = 0; i < regCount; i++) {
+            int p1 = 3 + (i * 2);
+            ByteBuffer order = ByteBuffer.wrap(frame, p1, 2).order(ByteOrder.BIG_ENDIAN);
+            byte[] array = new byte[] { order.get(), order.get() };
+            registers.put(i + firstReg, array);
+        }
+
+        return registers;
+    }
+
+    protected byte[] extractModbusResponseFrame(byte @Nullable [] responseFrame, byte[] requestFrame)
+            throws SolarmanException {
+        if (responseFrame == null || responseFrame.length == 0) {
+            throw new SolarmanProtocolException("No response frame");
+        } else if (responseFrame.length == 29) {
+            parseResponseErrorCode(responseFrame, requestFrame);
+            throw new IllegalStateException("This should never be reached as previous method should always throw");
+        } else if (responseFrame.length < (29 + 4)) {
+            throw new SolarmanProtocolException("Response frame is too short");
+        } else if (responseFrame[0] != (byte) 0xA5) {
+            throw new SolarmanProtocolException("Response frame has invalid starting byte");
+        } else if (responseFrame[responseFrame.length - 1] != (byte) 0x15) {
+            throw new SolarmanProtocolException("Response frame has invalid ending byte");
+        }
+
+        return Arrays.copyOfRange(responseFrame, 25, responseFrame.length - 2);
+    }
+
+    protected void parseResponseErrorCode(byte[] responseFrame, byte[] requestFrame) throws SolarmanException {
+        if (responseFrame[0] == (byte) 0xA5 && responseFrame[1] == (byte) 0x10
+                && !Arrays.equals(Arrays.copyOfRange(responseFrame, 7, 11), Arrays.copyOfRange(requestFrame, 7, 11))) {
+            String requestInverterId = parseInverterId(requestFrame);
+            String responseInverterId = parseInverterId(responseFrame);
+
+            String message = String
+                    .format("There was a mismatch between the request logger ID: %s and the response logger ID: %s. "
+                            + "Make sure you are using the logger ID and not the inverter ID. "
+                            + "If in doubt, try the one in the response", requestInverterId, responseInverterId);
+
+            throw new SolarmanAuthenticationException(message, requestInverterId, responseInverterId);
+        }
+
+        if (responseFrame[1] != (byte) 0x10 || responseFrame[2] != (byte) 0x45) {
+            throw new SolarmanProtocolException("Unexpected control code in error response frame");
+        }
+
+        int errorCode = responseFrame[25];
+
+        String message = switch (errorCode) {
+            case 0x01 -> "Error response frame: Illegal Function";
+            case 0x02 -> "Error response frame: Illegal Data Address";
+            case 0x03 -> "Error response frame: Illegal Data Value";
+            case 0x04 -> "Error response frame: Slave Device Failure";
+            default -> String.format("Error response frame: Unknown error code %02x", errorCode);
+        };
+        throw new SolarmanProtocolException(message);
+    }
+
+    private static String parseInverterId(byte[] requestFrame) {
+        byte[] inverterIdBytes = Arrays.copyOfRange(requestFrame, 7, 11);
+        int inverterIdInt = ByteBuffer.wrap(inverterIdBytes).order(ByteOrder.LITTLE_ENDIAN).getInt();
+        return String.valueOf(inverterIdInt & 0x00000000ffffffffL);
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanAuthenticationException.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanAuthenticationException.java
new file mode 100644 (file)
index 0000000..10442c9
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * 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.solarman.internal.modbus.exception;
+
+import java.io.Serial;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+public class SolarmanAuthenticationException extends SolarmanException {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    private final String requestInverterId;
+    private final String responseInverterId;
+
+    public SolarmanAuthenticationException(String message, String requestInverterId, String responseInverterId) {
+        super(message);
+        this.requestInverterId = requestInverterId;
+        this.responseInverterId = responseInverterId;
+    }
+
+    public String getRequestInverterId() {
+        return requestInverterId;
+    }
+
+    public String getResponseInverterId() {
+        return responseInverterId;
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanConnectionException.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanConnectionException.java
new file mode 100644 (file)
index 0000000..e88d3e0
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * 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.solarman.internal.modbus.exception;
+
+import java.io.Serial;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+public class SolarmanConnectionException extends SolarmanException {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    public SolarmanConnectionException(String message) {
+        super(message);
+    }
+
+    public SolarmanConnectionException(String message, Exception cause) {
+        super(message, cause);
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanException.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanException.java
new file mode 100644 (file)
index 0000000..06d5309
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * 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.solarman.internal.modbus.exception;
+
+import java.io.Serial;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+public class SolarmanException extends Exception {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    public SolarmanException(String message) {
+        super(message);
+    }
+
+    public SolarmanException(String message, Exception cause) {
+        super(message, cause);
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanProtocolException.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanProtocolException.java
new file mode 100644 (file)
index 0000000..4a57fb5
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * 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.solarman.internal.modbus.exception;
+
+import java.io.Serial;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+public class SolarmanProtocolException extends SolarmanException {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    public SolarmanProtocolException(String message) {
+        super(message);
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/typeprovider/ChannelUtils.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/typeprovider/ChannelUtils.java
new file mode 100644 (file)
index 0000000..6a49b99
--- /dev/null
@@ -0,0 +1,142 @@
+/**
+ * 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.solarman.internal.typeprovider;
+
+import javax.measure.Unit;
+import javax.measure.quantity.Dimensionless;
+import javax.measure.quantity.ElectricCurrent;
+import javax.measure.quantity.ElectricPotential;
+import javax.measure.quantity.Energy;
+import javax.measure.quantity.Frequency;
+import javax.measure.quantity.Power;
+import javax.measure.quantity.Temperature;
+import javax.measure.quantity.Time;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solarman.internal.SolarmanBindingConstants;
+import org.openhab.binding.solarman.internal.defmodel.ParameterItem;
+import org.openhab.core.library.CoreItemFactory;
+import org.openhab.core.library.unit.MetricPrefix;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.type.ChannelTypeUID;
+
+/**
+ * The {@link ChannelUtils} class provides utility functions for handling channel types and units in the Solarman
+ * binding.
+ * It includes methods for determining item types, units of measure, and channel type IDs.
+ *
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+public class ChannelUtils {
+
+    /**
+     * Determines the item type for a given parameter item.
+     *
+     * @param item The parameter item to determine the type for
+     * @return The item type as a string
+     */
+    public static String getItemType(ParameterItem item) {
+        @Nullable
+        Integer rule = item.getRule();
+
+        @Nullable
+        String uom = item.getUom();
+        if (uom == null) {
+            uom = "UNKN";
+        }
+
+        return switch (rule) {
+            case 5, 6, 7, 9 -> CoreItemFactory.STRING;
+            case 8 -> CoreItemFactory.DATETIME;
+            default -> {
+                yield computeNumberType(uom);
+            }
+        };
+    }
+
+    /**
+     * Computes the number type based on the unit of measure (UOM).
+     *
+     * @param uom The unit of measure as a string
+     * @return The number type as a string
+     */
+    private static String computeNumberType(String uom) {
+        return switch (uom.toUpperCase()) {
+            case "A" -> CoreItemFactory.NUMBER + ":" + ElectricCurrent.class.getSimpleName();
+            case "V" -> CoreItemFactory.NUMBER + ":" + ElectricPotential.class.getSimpleName();
+            case "°C" -> CoreItemFactory.NUMBER + ":" + Temperature.class.getSimpleName();
+            case "W", "KW", "VA", "KVA", "VAR", "KVAR" -> CoreItemFactory.NUMBER + ":" + Power.class.getSimpleName();
+            case "WH", "KWH" -> CoreItemFactory.NUMBER + ":" + Energy.class.getSimpleName();
+            case "S" -> CoreItemFactory.NUMBER + ":" + Time.class.getSimpleName();
+            case "HZ" -> CoreItemFactory.NUMBER + ":" + Frequency.class.getSimpleName();
+            case "%" -> CoreItemFactory.NUMBER + ":" + Dimensionless.class.getSimpleName();
+            default -> CoreItemFactory.NUMBER;
+        };
+    }
+
+    /**
+     * Retrieves the unit of measure (UOM) from a string definition.
+     *
+     * @param uom The unit of measure as a string
+     * @return The corresponding {@link Unit}, or null if not found
+     */
+    public static @Nullable Unit<?> getUnitFromDefinition(String uom) {
+        return switch (uom.toUpperCase()) {
+            case "A" -> Units.AMPERE;
+            case "V" -> Units.VOLT;
+            case "°C" -> SIUnits.CELSIUS;
+            case "W" -> Units.WATT;
+            case "KW" -> MetricPrefix.KILO(Units.WATT);
+            case "VA" -> Units.VOLT_AMPERE;
+            case "KVA" -> MetricPrefix.KILO(Units.VOLT_AMPERE);
+            case "VAR" -> Units.VAR;
+            case "KVAR" -> MetricPrefix.KILO(Units.VAR);
+            case "WH" -> Units.WATT_HOUR;
+            case "KWH" -> MetricPrefix.KILO(Units.WATT_HOUR);
+            case "S" -> Units.SECOND;
+            case "HZ" -> Units.HERTZ;
+            case "%" -> Units.PERCENT;
+            default -> null;
+        };
+    }
+
+    /**
+     * Escapes a name string by replacing specific characters with hyphens and converting to lowercase.
+     *
+     * @param name The name to escape
+     * @return The escaped name
+     */
+    public static String escapeName(String name) {
+        name = name.trim();
+        name = name.replace("+", "plus");
+        name = name.toLowerCase();
+        name = name.replaceAll("[ .()/\\\\&_]", "-");
+        return name;
+    }
+
+    /**
+     * Computes a channel type ID based on the inverter definition ID, group, and name.
+     *
+     * @param inverterDefinitionId The inverter definition ID
+     * @param group The group
+     * @param name The name
+     * @return The computed {@link ChannelTypeUID}
+     */
+    public static ChannelTypeUID computeChannelTypeId(String inverterDefinitionId, String group, String name) {
+        return new ChannelTypeUID(SolarmanBindingConstants.SOLARMAN_BINDING_ID,
+                String.format("%s-%s-%s", escapeName(inverterDefinitionId), escapeName(group), escapeName(name)));
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/typeprovider/SolarmanChannelTypeProvider.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/typeprovider/SolarmanChannelTypeProvider.java
new file mode 100644 (file)
index 0000000..fb27dd5
--- /dev/null
@@ -0,0 +1,131 @@
+/**
+ * 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.solarman.internal.typeprovider;
+
+import static org.openhab.binding.solarman.internal.typeprovider.ChannelUtils.getItemType;
+
+import java.math.BigDecimal;
+import java.net.URI;
+import java.net.URL;
+import java.util.AbstractMap;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solarman.internal.DefinitionParser;
+import org.openhab.binding.solarman.internal.defmodel.InverterDefinition;
+import org.openhab.binding.solarman.internal.defmodel.ParameterItem;
+import org.openhab.core.thing.type.ChannelType;
+import org.openhab.core.thing.type.ChannelTypeBuilder;
+import org.openhab.core.thing.type.ChannelTypeProvider;
+import org.openhab.core.thing.type.ChannelTypeUID;
+import org.openhab.core.thing.type.StateChannelTypeBuilder;
+import org.openhab.core.types.StateDescriptionFragmentBuilder;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@Component(service = { ChannelTypeProvider.class, SolarmanChannelTypeProvider.class })
+@NonNullByDefault
+public class SolarmanChannelTypeProvider implements ChannelTypeProvider {
+    private final Logger logger = LoggerFactory.getLogger(SolarmanChannelTypeProvider.class);
+    private static final DefinitionParser DEFINITION_PARSER = new DefinitionParser();
+    private static final Pattern INVERTER_DEFINITION_PATTERN = Pattern.compile("/definitions/([^.]+)\\.yaml");
+    private final Map<ChannelTypeUID, ChannelType> channelTypeMap = new ConcurrentHashMap<>();
+
+    @Activate
+    public SolarmanChannelTypeProvider(BundleContext bundleContext) {
+        Collections.list(bundleContext.getBundle().findEntries("/definitions", "*", false)).stream().map(URL::getFile)
+                .map(this::extractInverterDefinitionId).filter(Optional::isPresent).map(Optional::get)
+                .map(this::parseInverterDefinition).forEach(channelTypeMap::putAll);
+    }
+
+    private Map<ChannelTypeUID, ChannelType> parseInverterDefinition(String inverterDefinitionId) {
+        InverterDefinition inverterDefinition = DEFINITION_PARSER.parseDefinition(inverterDefinitionId);
+
+        if (inverterDefinition == null) {
+            logger.warn("Unable to parse inverter definition");
+            return Collections.emptyMap();
+        }
+
+        return inverterDefinition.getParameters().stream()
+                .flatMap(parameter -> parameter.getItems().stream().map(item -> {
+                    ChannelTypeUID channelTypeUID = ChannelUtils.computeChannelTypeId(inverterDefinitionId,
+                            parameter.getGroup(), item.getName());
+                    return new AbstractMap.SimpleEntry<>(channelTypeUID, buildChannelType(channelTypeUID, item));
+                })).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+    }
+
+    private Optional<String> extractInverterDefinitionId(String file) {
+        return Stream.of(file).map(INVERTER_DEFINITION_PATTERN::matcher).filter(Matcher::matches)
+                .map(matcher -> matcher.group(1)).findFirst();
+    }
+
+    public Collection<ChannelType> getChannelTypes(@Nullable Locale locale) {
+        return List.copyOf(this.channelTypeMap.values());
+    }
+
+    @Override
+    public @Nullable ChannelType getChannelType(ChannelTypeUID channelTypeUID, @Nullable Locale locale) {
+        return this.channelTypeMap.get(channelTypeUID);
+    }
+
+    public ChannelType buildChannelType(ChannelTypeUID channelTypeUID, ParameterItem item) {
+        String itemType = getItemType(item);
+
+        StateDescriptionFragmentBuilder stateDescriptionFragmentBuilder = StateDescriptionFragmentBuilder.create()
+                .withPattern(computePatternForItem(item)).withReadOnly(true);
+
+        StateChannelTypeBuilder stateChannelTypeBuilder = ChannelTypeBuilder
+                .state(channelTypeUID, item.getName(), itemType)
+                .withConfigDescriptionURI(URI.create("channel-type-config:solarman:dynamic-channel"))
+                .withDescription(String.format("%s %s", item.getName(), buildRegisterDescription(item)))
+                .withStateDescriptionFragment(stateDescriptionFragmentBuilder.build());
+
+        return stateChannelTypeBuilder.build();
+    }
+
+    private String computePatternForItem(ParameterItem item) {
+        long decimalPoints = 0;
+
+        BigDecimal scale = Objects.requireNonNullElse(item.getScale(), BigDecimal.ONE);
+        if (scale.compareTo(BigDecimal.ONE) < 0) {
+            decimalPoints = Math.abs(Math.round(Math.log10(scale.doubleValue())));
+        }
+
+        String uom = item.getUom();
+        String pattern = (decimalPoints > 0) ? "%." + decimalPoints + "f" : "%d";
+        return pattern + (uom != null && !uom.isBlank() ? " %unit%" : "");
+    }
+
+    private String buildRegisterDescription(ParameterItem item) {
+        return String.format("[%s]", item.getRegisters().stream().map(register -> String.format("0x%04X", register))
+                .collect(Collectors.joining(",")));
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/updater/SolarmanChannelUpdater.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/updater/SolarmanChannelUpdater.java
new file mode 100644 (file)
index 0000000..a9aa8cc
--- /dev/null
@@ -0,0 +1,238 @@
+/**
+ * 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.solarman.internal.updater;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import javax.measure.Unit;
+import javax.measure.format.MeasurementParseException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.solarman.internal.defmodel.ParameterItem;
+import org.openhab.binding.solarman.internal.defmodel.Request;
+import org.openhab.binding.solarman.internal.modbus.SolarmanLoggerConnection;
+import org.openhab.binding.solarman.internal.modbus.SolarmanLoggerConnector;
+import org.openhab.binding.solarman.internal.modbus.SolarmanV5Protocol;
+import org.openhab.binding.solarman.internal.modbus.exception.SolarmanConnectionException;
+import org.openhab.binding.solarman.internal.modbus.exception.SolarmanException;
+import org.openhab.binding.solarman.internal.typeprovider.ChannelUtils;
+import org.openhab.binding.solarman.internal.util.StreamUtils;
+import org.openhab.core.library.types.DateTimeType;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.types.State;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+public class SolarmanChannelUpdater {
+    private final Logger logger = LoggerFactory.getLogger(SolarmanChannelUpdater.class);
+    private final StateUpdater stateUpdater;
+
+    public SolarmanChannelUpdater(StateUpdater stateUpdater) {
+        this.stateUpdater = stateUpdater;
+    }
+
+    public SolarmanProcessResult fetchDataFromLogger(List<Request> requests,
+            SolarmanLoggerConnector solarmanLoggerConnector, SolarmanV5Protocol solarmanV5Protocol,
+            Map<ParameterItem, ChannelUID> paramToChannelMapping) {
+        try (SolarmanLoggerConnection solarmanLoggerConnection = solarmanLoggerConnector.createConnection()) {
+            logger.debug("Fetching data from logger");
+
+            if (!solarmanLoggerConnection.isConnected()) {
+                return SolarmanProcessResult.ofException(Request.NONE,
+                        new SolarmanConnectionException("Unable to connect to logger"));
+            }
+
+            SolarmanProcessResult solarmanProcessResult = requests.stream().map(request -> {
+                try {
+                    return SolarmanProcessResult.ofValue(request,
+                            solarmanV5Protocol.readRegisters(solarmanLoggerConnection,
+                                    (byte) request.getMbFunctioncode().intValue(), request.getStart(),
+                                    request.getEnd()));
+                } catch (SolarmanException e) {
+                    return SolarmanProcessResult.ofException(request, e);
+                }
+            }).reduce(new SolarmanProcessResult(), SolarmanProcessResult::merge);
+
+            if (solarmanProcessResult.hasSuccessfulResponses()) {
+                updateChannelsForReadRegisters(paramToChannelMapping, solarmanProcessResult.getReadRegistersMap());
+            }
+            return solarmanProcessResult;
+        }
+    }
+
+    private void updateChannelsForReadRegisters(Map<ParameterItem, ChannelUID> paramToChannelMapping,
+            Map<Integer, byte[]> readRegistersMap) {
+        paramToChannelMapping.forEach((parameterItem, channelUID) -> {
+            List<Integer> registers = parameterItem.getRegisters();
+            if (readRegistersMap.keySet().containsAll(registers)) {
+                switch (parameterItem.getRule()) {
+                    case 1, 3 -> updateChannelWithNumericValue(parameterItem, channelUID, registers, readRegistersMap,
+                            ValueType.UNSIGNED);
+                    case 2, 4 -> updateChannelWithNumericValue(parameterItem, channelUID, registers, readRegistersMap,
+                            ValueType.SIGNED);
+                    case 5 -> updateChannelWithStringValue(channelUID, registers, readRegistersMap);
+                    case 6 -> updateChannelWithRawValue(parameterItem, channelUID, registers, readRegistersMap);
+                    case 7 -> updateChannelWithVersion(channelUID, registers, readRegistersMap);
+                    case 8 -> updateChannelWithDateTime(channelUID, registers, readRegistersMap);
+                    case 9 -> updateChannelWithTime(channelUID, registers, readRegistersMap);
+                }
+            } else {
+                logger.warn("Unable to update channel {} because its registers were not read", channelUID.getId());
+            }
+        });
+    }
+
+    private void updateChannelWithTime(ChannelUID channelUID, List<Integer> registers,
+            Map<Integer, byte[]> readRegistersMap) {
+        String stringValue = registers.stream().map(readRegistersMap::get).map(v -> ByteBuffer.wrap(v).getShort())
+                .map(rawVal -> String.format("%02d", rawVal / 100) + ":" + String.format("%02d", rawVal % 100))
+                .collect(Collectors.joining());
+
+        stateUpdater.updateState(channelUID, new StringType(stringValue));
+    }
+
+    private void updateChannelWithDateTime(ChannelUID channelUID, List<Integer> registers,
+            Map<Integer, byte[]> readRegistersMap) {
+        String stringValue = StreamUtils.zip(IntStream.range(0, registers.size()).boxed(),
+                registers.stream().map(readRegistersMap::get).map(v -> ByteBuffer.wrap(v).getShort()),
+                StreamUtils.Tuple::new).map(t -> {
+                    int index = t.a();
+                    short rawVal = t.b();
+
+                    return switch (index) {
+                        case 0 -> (rawVal >> 8) + "/" + (rawVal & 0xFF) + "/";
+                        case 1 -> (rawVal >> 8) + " " + (rawVal & 0xFF) + ":";
+                        case 2 -> (rawVal >> 8) + ":" + (rawVal & 0xFF);
+                        default -> (rawVal >> 8) + "" + (rawVal & 0xFF);
+                    };
+                }).collect(Collectors.joining());
+
+        try {
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yy/M/d H:m:s");
+            LocalDateTime dateTime = LocalDateTime.parse(stringValue, formatter);
+
+            stateUpdater.updateState(channelUID, new DateTimeType(dateTime.atZone(ZoneId.systemDefault())));
+        } catch (DateTimeParseException e) {
+            logger.debug("Unable to parse string date {} to a DateTime object", stringValue);
+        }
+    }
+
+    private void updateChannelWithVersion(ChannelUID channelUID, List<Integer> registers,
+            Map<Integer, byte[]> readRegistersMap) {
+        String stringValue = registers.stream().map(readRegistersMap::get).map(v -> ByteBuffer.wrap(v).getShort())
+                .map(rawVal -> (rawVal >> 12) + "." + ((rawVal >> 8) & 0x0F) + "." + ((rawVal >> 4) & 0x0F) + "."
+                        + (rawVal & 0x0F))
+                .collect(Collectors.joining());
+
+        stateUpdater.updateState(channelUID, new StringType(stringValue));
+    }
+
+    private void updateChannelWithStringValue(ChannelUID channelUID, List<Integer> registers,
+            Map<Integer, byte[]> readRegistersMap) {
+        String stringValue = registers.stream().map(readRegistersMap::get).reduce(new StringBuilder(), (acc, val) -> {
+            short shortValue = ByteBuffer.wrap(val).order(ByteOrder.BIG_ENDIAN).getShort();
+            return acc.append((char) (shortValue >> 8)).append((char) (shortValue & 0xFF));
+        }, StringBuilder::append).toString();
+
+        stateUpdater.updateState(channelUID, new StringType(stringValue));
+    }
+
+    private void updateChannelWithNumericValue(ParameterItem parameterItem, ChannelUID channelUID,
+            List<Integer> registers, Map<Integer, byte[]> readRegistersMap, ValueType valueType) {
+        BigInteger value = extractNumericValue(registers, readRegistersMap, valueType);
+        BigDecimal convertedValue = convertNumericValue(value, parameterItem.getOffset(), parameterItem.getScale());
+        String uom = Objects.requireNonNullElse(parameterItem.getUom(), "");
+
+        State state;
+        if (!uom.isBlank()) {
+            try {
+                Unit<?> unitFromDefinition = ChannelUtils.getUnitFromDefinition(uom);
+                if (unitFromDefinition != null) {
+                    state = new QuantityType<>(convertedValue, unitFromDefinition);
+                } else {
+                    logger.debug("Unable to parse unit: {}", uom);
+                    state = new DecimalType(convertedValue);
+                }
+            } catch (MeasurementParseException e) {
+                state = new DecimalType(convertedValue);
+            }
+        } else {
+            state = new DecimalType(convertedValue);
+        }
+        stateUpdater.updateState(channelUID, state);
+    }
+
+    private void updateChannelWithRawValue(ParameterItem parameterItem, ChannelUID channelUID, List<Integer> registers,
+            Map<Integer, byte[]> readRegistersMap) {
+        String hexString = String.format("[%s]",
+                reversed(registers).stream().map(readRegistersMap::get).map(
+                        val -> String.format("0x%02X", ByteBuffer.wrap(val).order(ByteOrder.BIG_ENDIAN).getShort()))
+                        .collect(Collectors.joining(",")));
+
+        stateUpdater.updateState(channelUID, new StringType(hexString));
+    }
+
+    private BigDecimal convertNumericValue(BigInteger value, @Nullable BigDecimal offset, @Nullable BigDecimal scale) {
+        return new BigDecimal(value).subtract(offset != null ? offset : BigDecimal.ZERO)
+                .multiply(scale != null ? scale : BigDecimal.ONE);
+    }
+
+    private BigInteger extractNumericValue(List<Integer> registers, Map<Integer, byte[]> readRegistersMap,
+            ValueType valueType) {
+        return reversed(registers)
+                .stream().map(readRegistersMap::get).reduce(
+                        BigInteger.ZERO, (acc,
+                                val) -> acc.shiftLeft(Short.SIZE)
+                                        .add(BigInteger.valueOf(ByteBuffer.wrap(val).getShort()
+                                                & (valueType == ValueType.UNSIGNED ? 0xFFFF : 0xFFFFFFFF))),
+                        BigInteger::add);
+    }
+
+    private enum ValueType {
+        UNSIGNED,
+        SIGNED
+    }
+
+    @FunctionalInterface
+    public interface StateUpdater {
+        void updateState(ChannelUID channelUID, State state);
+    }
+
+    private <T> List<T> reversed(List<T> initialList) {
+        List<T> reversedList = new ArrayList<>(initialList);
+        Collections.reverse(reversedList);
+        return reversedList;
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/updater/SolarmanProcessResult.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/updater/SolarmanProcessResult.java
new file mode 100644 (file)
index 0000000..5bc66d9
--- /dev/null
@@ -0,0 +1,88 @@
+/**
+ * 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.solarman.internal.updater;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.solarman.internal.defmodel.Request;
+import org.openhab.binding.solarman.internal.modbus.exception.SolarmanException;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+public class SolarmanProcessResult {
+    private final Map<Request, Map<Integer, byte[]>> successfulRequestMap;
+    private final Map<Request, SolarmanException> exceptionRequestMap;
+
+    public SolarmanProcessResult() {
+        this(Collections.emptyMap(), Collections.emptyMap());
+    }
+
+    private SolarmanProcessResult(Map<Request, Map<Integer, byte[]>> successfulRequestMap,
+            Map<Request, SolarmanException> exceptionRequestMap) {
+        this.successfulRequestMap = successfulRequestMap;
+        this.exceptionRequestMap = exceptionRequestMap;
+    }
+
+    public static SolarmanProcessResult merge(SolarmanProcessResult result1, SolarmanProcessResult result2) {
+        return new SolarmanProcessResult(mergeMaps(result1.successfulRequestMap, result2.successfulRequestMap),
+                mergeMaps(result1.exceptionRequestMap, result2.exceptionRequestMap));
+    }
+
+    public static SolarmanProcessResult ofValue(Request request, Map<Integer, byte[]> readRegisters) {
+        return new SolarmanProcessResult(Collections.singletonMap(request, readRegisters), new HashMap<>());
+    }
+
+    public static SolarmanProcessResult ofException(Request request, SolarmanException solarmanException) {
+        return new SolarmanProcessResult(new HashMap<>(), Collections.singletonMap(request, solarmanException));
+    }
+
+    public boolean hasSuccessfulResponses() {
+        return !successfulRequestMap.isEmpty();
+    }
+
+    public Map<Integer, byte[]> getReadRegistersMap() {
+        return successfulRequestMap.values().stream().reduce(new HashMap<>(), SolarmanProcessResult::mergeMaps);
+    }
+
+    private static <K, V> Map<K, V> mergeMaps(Map<K, V> map1, Map<K, V> map2) {
+        return Stream.concat(map1.entrySet().stream(), map2.entrySet().stream())
+                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1));
+    }
+
+    @Override
+    public String toString() {
+        if (!successfulRequestMap.isEmpty() && exceptionRequestMap.isEmpty()) {
+            return String.format("Successfully executed %d requests", successfulRequestMap.size());
+        } else if (successfulRequestMap.isEmpty() && !exceptionRequestMap.isEmpty()) {
+            return String.format("Error fetching data from logger, here are the errors:\n%s",
+                    buildErrorReport(exceptionRequestMap));
+        } else if (!successfulRequestMap.isEmpty()) {
+            return String.format("Successfully executed %d requests, but %d requests failed with:\n%s",
+                    successfulRequestMap.size(), exceptionRequestMap.size(), buildErrorReport(exceptionRequestMap));
+        } else {
+            return "Empty SolarmanProcessResult";
+        }
+    }
+
+    private String buildErrorReport(Map<Request, SolarmanException> exceptionRequestMap) {
+        return exceptionRequestMap.entrySet().stream().map(entry -> String.format("\tRequest %s returned error: %s\n",
+                entry.getKey().toString(), entry.getValue().getMessage())).reduce("", String::concat);
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/util/StreamUtils.java b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/util/StreamUtils.java
new file mode 100644 (file)
index 0000000..91bae6c
--- /dev/null
@@ -0,0 +1,85 @@
+/**
+ * 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.solarman.internal.util;
+
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.function.BiFunction;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Utility class for Stream operations.
+ *
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+public class StreamUtils {
+
+    /**
+     * Zips two streams into one by applying a zipper function to each pair of elements.
+     *
+     * @param <A> The type of the first stream elements
+     * @param <B> The type of the second stream elements
+     * @param <C> The type of the resulting stream elements
+     * @param a The first stream to be zipped
+     * @param b The second stream to be zipped
+     * @param zipper The function to apply to each pair of elements
+     * @return A stream of zipped elements
+     */
+    public static <A, B, C> Stream<C> zip(Stream<? extends A> a, Stream<? extends B> b,
+            BiFunction<? super A, ? super B, ? extends C> zipper) {
+        Objects.requireNonNull(zipper);
+        Spliterator<? extends A> aSpliterator = Objects.requireNonNull(a).spliterator();
+        Spliterator<? extends B> bSpliterator = Objects.requireNonNull(b).spliterator();
+
+        // Zipping looses DISTINCT and SORTED characteristics
+        int characteristics = aSpliterator.characteristics() & bSpliterator.characteristics()
+                & ~(Spliterator.DISTINCT | Spliterator.SORTED);
+
+        long zipSize = ((characteristics & Spliterator.SIZED) != 0)
+                ? Math.min(aSpliterator.getExactSizeIfKnown(), bSpliterator.getExactSizeIfKnown())
+                : -1;
+
+        Iterator<A> aIterator = Spliterators.iterator(aSpliterator);
+        Iterator<B> bIterator = Spliterators.iterator(bSpliterator);
+        Iterator<C> cIterator = new Iterator<C>() {
+            @Override
+            public boolean hasNext() {
+                return aIterator.hasNext() && bIterator.hasNext();
+            }
+
+            @Override
+            public C next() {
+                return zipper.apply(aIterator.next(), bIterator.next());
+            }
+        };
+
+        Spliterator<C> split = Spliterators.spliterator(cIterator, zipSize, characteristics);
+        return (a.isParallel() || b.isParallel()) ? StreamSupport.stream(split, true)
+                : StreamSupport.stream(split, false);
+    }
+
+    /**
+     * A tuple class to hold two related objects.
+     *
+     * @param <A> The type of the first object
+     * @param <B> The type of the second object
+     */
+    public record Tuple<A, B> (A a, B b) {
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/addon/addon.xml
new file mode 100644 (file)
index 0000000..44bc7a0
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<addon:addon id="solarman" 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>Solarman Logger Binding</name>
+       <description>This is the binding for Solarman Logger</description>
+       <connection>local</connection>
+</addon:addon>
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/datetime-channel-config.xml b/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/datetime-channel-config.xml
new file mode 100644 (file)
index 0000000..435e457
--- /dev/null
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<config-description:config-descriptions
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
+
+       <config-description uri="channel-type-config:solarman:datetime-channel">
+               <parameter name="uom" type="text">
+                       <label>Unit of Measurement</label>
+                       <description>The unit of measurement used for this channel</description>
+                       <advanced>true</advanced>
+               </parameter>
+               <parameter name="scale" type="decimal">
+                       <label>Scale</label>
+                       <description>The scaling factor, the final value will be scaled by this</description>
+                       <advanced>true</advanced>
+               </parameter>
+               <parameter name="rule" type="integer">
+                       <label>Rule</label>
+                       <description>The type of measurement. See explanation for possible values</description>
+                       <advanced>true</advanced>
+                       <options>
+                               <option value="1">Unsigned Short</option>
+                               <option value="2">Signed Short</option>
+                               <option value="3">Unsigned Integer</option>
+                               <option value="4">Signed Integer</option>
+                               <option value="5">Text</option>
+                               <option value="6">Bytes</option>
+                               <option value="7">Version</option>
+                               <option value="8">Date Time</option>
+                               <option value="9">Time</option>
+                       </options>
+               </parameter>
+               <parameter name="offset" type="decimal">
+                       <label>Offset</label>
+                       <description>The offset subtracted from the measurement</description>
+                       <advanced>true</advanced>
+               </parameter>
+               <parameter name="registers" type="text">
+                       <label>Registers</label>
+                       <description>Comma separated list of registers to read for the measurement</description>
+                       <advanced>true</advanced>
+               </parameter>
+       </config-description>
+
+</config-description:config-descriptions>
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/dynamic-channel-config.xml b/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/dynamic-channel-config.xml
new file mode 100644 (file)
index 0000000..326437b
--- /dev/null
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<config-description:config-descriptions
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
+
+       <config-description uri="channel-type-config:solarman:dynamic-channel">
+               <parameter name="uom" type="text">
+                       <label>Unit of Measurement</label>
+                       <description>The unit of measurement used for this channel</description>
+                       <advanced>true</advanced>
+               </parameter>
+               <parameter name="scale" type="decimal">
+                       <label>Scale</label>
+                       <description>The scaling factor, the final value will be scaled by this</description>
+                       <advanced>true</advanced>
+               </parameter>
+               <parameter name="rule" type="integer">
+                       <label>Rule</label>
+                       <description>The type of measurement. See explanation for possible values</description>
+                       <advanced>true</advanced>
+                       <options>
+                               <option value="1">Unsigned Short</option>
+                               <option value="2">Signed Short</option>
+                               <option value="3">Unsigned Integer</option>
+                               <option value="4">Signed Integer</option>
+                               <option value="5">Text</option>
+                               <option value="6">Bytes</option>
+                               <option value="7">Version</option>
+                               <option value="8">Date Time</option>
+                               <option value="9">Time</option>
+                       </options>
+               </parameter>
+               <parameter name="offset" type="decimal">
+                       <label>Offset</label>
+                       <description>The offset subtracted from the measurement</description>
+                       <advanced>true</advanced>
+               </parameter>
+               <parameter name="registers" type="text">
+                       <label>Registers</label>
+                       <description>Comma separated list of registers to read for the measurement</description>
+                       <advanced>true</advanced>
+               </parameter>
+       </config-description>
+
+</config-description:config-descriptions>
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/number-channel-config.xml b/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/number-channel-config.xml
new file mode 100644 (file)
index 0000000..593853d
--- /dev/null
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<config-description:config-descriptions
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
+
+       <config-description uri="channel-type-config:solarman:number-channel">
+               <parameter name="uom" type="text">
+                       <label>Unit of Measurement</label>
+                       <description>The unit of measurement used for this channel</description>
+                       <advanced>true</advanced>
+               </parameter>
+               <parameter name="scale" type="decimal">
+                       <label>Scale</label>
+                       <description>The scaling factor, the final value will be scaled by this</description>
+                       <advanced>true</advanced>
+               </parameter>
+               <parameter name="rule" type="integer">
+                       <label>Rule</label>
+                       <description>The type of measurement. See explanation for possible values</description>
+                       <advanced>true</advanced>
+                       <options>
+                               <option value="1">Unsigned Short</option>
+                               <option value="2">Signed Short</option>
+                               <option value="3">Unsigned Integer</option>
+                               <option value="4">Signed Integer</option>
+                               <option value="5">Text</option>
+                               <option value="6">Bytes</option>
+                               <option value="7">Version</option>
+                               <option value="8">Date Time</option>
+                               <option value="9">Time</option>
+                       </options>
+               </parameter>
+               <parameter name="offset" type="decimal">
+                       <label>Offset</label>
+                       <description>The offset subtracted from the measurement</description>
+                       <advanced>true</advanced>
+               </parameter>
+               <parameter name="registers" type="text">
+                       <label>Registers</label>
+                       <description>Comma separated list of registers to read for the measurement</description>
+                       <advanced>true</advanced>
+               </parameter>
+       </config-description>
+
+</config-description:config-descriptions>
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/string-channel-config.xml b/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/string-channel-config.xml
new file mode 100644 (file)
index 0000000..92ba342
--- /dev/null
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<config-description:config-descriptions
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
+
+       <config-description uri="channel-type-config:solarman:string-channel">
+               <parameter name="uom" type="text">
+                       <label>Unit of Measurement</label>
+                       <description>The unit of measurement used for this channel</description>
+                       <advanced>true</advanced>
+               </parameter>
+               <parameter name="scale" type="decimal">
+                       <label>Scale</label>
+                       <description>The scaling factor, the final value will be scaled by this</description>
+                       <advanced>true</advanced>
+               </parameter>
+               <parameter name="rule" type="integer">
+                       <label>Rule</label>
+                       <description>The type of measurement. See explanation for possible values</description>
+                       <advanced>true</advanced>
+                       <options>
+                               <option value="1">Unsigned Short</option>
+                               <option value="2">Signed Short</option>
+                               <option value="3">Unsigned Integer</option>
+                               <option value="4">Signed Integer</option>
+                               <option value="5">Text</option>
+                               <option value="6">Bytes</option>
+                               <option value="7">Version</option>
+                               <option value="8">Date Time</option>
+                               <option value="9">Time</option>
+                       </options>
+               </parameter>
+               <parameter name="offset" type="decimal">
+                       <label>Offset</label>
+                       <description>The offset subtracted from the measurement</description>
+                       <advanced>true</advanced>
+               </parameter>
+               <parameter name="registers" type="text">
+                       <label>Registers</label>
+                       <description>Comma separated list of registers to read for the measurement</description>
+                       <advanced>true</advanced>
+               </parameter>
+       </config-description>
+
+</config-description:config-descriptions>
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/i18n/solarman.properties b/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/i18n/solarman.properties
new file mode 100644 (file)
index 0000000..58f7dfd
--- /dev/null
@@ -0,0 +1,123 @@
+# add-on
+
+addon.solarman.name = Solarman Logger Binding
+addon.solarman.description = This is the binding for Solarman Logger
+
+# thing types
+
+thing-type.solarman.logger.label = Solarman Logger
+thing-type.solarman.logger.description = This thing allows communication with Solarman (IGEN-Tech) v5 based solar inverter data loggers over the local network. Compatible with inverters from manufacturers such as Deye, Sofar, Solis, ZCS Azzurro, and KStar.
+
+# thing types config
+
+thing-type.config.solarman.logger.additionalRequests.label = Additional Requests
+thing-type.config.solarman.logger.additionalRequests.description = Additional requests besides the ones defined in the inverter definition. Format is mb_functioncode1:start1-end1, mb_functioncode2:start2-end2,... Example 0x03:0x0000-0x0100,0x03:0x0200-0x0300
+thing-type.config.solarman.logger.hostname.label = Hostname
+thing-type.config.solarman.logger.hostname.description = Hostname or IP address of the Solarman logger.
+thing-type.config.solarman.logger.inverterType.label = Inverter Type
+thing-type.config.solarman.logger.inverterType.description = The type of inverter connected to the logger (default deye_sg04lp3).
+thing-type.config.solarman.logger.inverterType.option.deye_2mppt = DEYE Microinverter with 2 MPPT Trackers (deye_2mppt)
+thing-type.config.solarman.logger.inverterType.option.deye_4mppt = DEYE Microinverter with 4 MPPT Trackers (deye_4mppt)
+thing-type.config.solarman.logger.inverterType.option.deye_hybrid = Generic DEYE/Sunsynk/SolArk Hybrid inverters (deye_hybrid)
+thing-type.config.solarman.logger.inverterType.option.deye_sg04lp3 = DEYE/Sunsynk/SolArk Hybrid 8/12K-SG04LP3 (deye_sg04lp3)
+thing-type.config.solarman.logger.inverterType.option.deye_string = Generic DEYE/Sunsynk/SolArk String inverters (deye_string)
+thing-type.config.solarman.logger.inverterType.option.kstar_hybrid = KSTAR Hybrid Inverter (kstar_hybrid)
+thing-type.config.solarman.logger.inverterType.option.sofar_g3hyd = SOFAR Hybrid Three-Phase Inverter (sofar_g3hyd)
+thing-type.config.solarman.logger.inverterType.option.sofar_hyd3k-6k-es = SOFAR Hybrid Single-Phase Inverter (sofar_hyd3k-6k-es)
+thing-type.config.solarman.logger.inverterType.option.sofar_lsw3 = SOFAR Inverters (sofar_lsw3)
+thing-type.config.solarman.logger.inverterType.option.sofar_wifikit = SOFAR WifiKit (sofar_wifikit)
+thing-type.config.solarman.logger.inverterType.option.solis_1p8k-5g = SOLIS 1P8K-5G (solis_1p8k-5g)
+thing-type.config.solarman.logger.inverterType.option.solis_hybrid = SOLIS Hybrid Inverter (solis_hybrid)
+thing-type.config.solarman.logger.inverterType.option.zcs_azzurro-ktl-v3 = ZCS Azzurro KTL-V3 Inverters (zcs_azzurro-ktl-v3)
+thing-type.config.solarman.logger.port.label = Port
+thing-type.config.solarman.logger.port.description = Port of the Solarman logger (default 8899).
+thing-type.config.solarman.logger.refreshInterval.label = Refresh Interval
+thing-type.config.solarman.logger.refreshInterval.description = Interval to query the logger (default 60).
+thing-type.config.solarman.logger.serialNumber.label = Serial Number
+thing-type.config.solarman.logger.serialNumber.description = Serial number of the Solarman logger.
+
+# channel types
+
+channel-type.solarman.datetime.label = Datetime Value
+channel-type.solarman.dynamic.label = Dynamic Channel
+channel-type.solarman.number.label = Number Value
+channel-type.solarman.string.label = Text Value
+
+# channel types config
+
+channel-type-config.config.solarman.datetime-channel.offset.label = Offset
+channel-type-config.config.solarman.datetime-channel.offset.description = The offset subtracted from the measurement
+channel-type-config.config.solarman.datetime-channel.registers.label = Registers
+channel-type-config.config.solarman.datetime-channel.registers.description = Comma separated list of registers to read for the measurement
+channel-type-config.config.solarman.datetime-channel.rule.label = Rule
+channel-type-config.config.solarman.datetime-channel.rule.description = The type of measurement. See explanation for possible values
+channel-type-config.config.solarman.datetime-channel.rule.option.1 = Unsigned Short
+channel-type-config.config.solarman.datetime-channel.rule.option.2 = Signed Short
+channel-type-config.config.solarman.datetime-channel.rule.option.3 = Unsigned Integer
+channel-type-config.config.solarman.datetime-channel.rule.option.4 = Signed Integer
+channel-type-config.config.solarman.datetime-channel.rule.option.5 = Text
+channel-type-config.config.solarman.datetime-channel.rule.option.6 = Bytes
+channel-type-config.config.solarman.datetime-channel.rule.option.7 = Version
+channel-type-config.config.solarman.datetime-channel.rule.option.8 = Date Time
+channel-type-config.config.solarman.datetime-channel.rule.option.9 = Time
+channel-type-config.config.solarman.datetime-channel.scale.label = Scale
+channel-type-config.config.solarman.datetime-channel.scale.description = The scaling factor, the final value will be scaled by this
+channel-type-config.config.solarman.datetime-channel.uom.label = Unit of Measurement
+channel-type-config.config.solarman.datetime-channel.uom.description = The unit of measurement used for this channel
+channel-type-config.config.solarman.dynamic-channel.offset.label = Offset
+channel-type-config.config.solarman.dynamic-channel.offset.description = The offset subtracted from the measurement
+channel-type-config.config.solarman.dynamic-channel.registers.label = Registers
+channel-type-config.config.solarman.dynamic-channel.registers.description = Comma separated list of registers to read for the measurement
+channel-type-config.config.solarman.dynamic-channel.rule.label = Rule
+channel-type-config.config.solarman.dynamic-channel.rule.description = The type of measurement. See explanation for possible values
+channel-type-config.config.solarman.dynamic-channel.rule.option.1 = Unsigned Short
+channel-type-config.config.solarman.dynamic-channel.rule.option.2 = Signed Short
+channel-type-config.config.solarman.dynamic-channel.rule.option.3 = Unsigned Integer
+channel-type-config.config.solarman.dynamic-channel.rule.option.4 = Signed Integer
+channel-type-config.config.solarman.dynamic-channel.rule.option.5 = Text
+channel-type-config.config.solarman.dynamic-channel.rule.option.6 = Bytes
+channel-type-config.config.solarman.dynamic-channel.rule.option.7 = Version
+channel-type-config.config.solarman.dynamic-channel.rule.option.8 = Date Time
+channel-type-config.config.solarman.dynamic-channel.rule.option.9 = Time
+channel-type-config.config.solarman.dynamic-channel.scale.label = Scale
+channel-type-config.config.solarman.dynamic-channel.scale.description = The scaling factor, the final value will be scaled by this
+channel-type-config.config.solarman.dynamic-channel.uom.label = Unit of Measurement
+channel-type-config.config.solarman.dynamic-channel.uom.description = The unit of measurement used for this channel
+channel-type-config.config.solarman.number-channel.offset.label = Offset
+channel-type-config.config.solarman.number-channel.offset.description = The offset subtracted from the measurement
+channel-type-config.config.solarman.number-channel.registers.label = Registers
+channel-type-config.config.solarman.number-channel.registers.description = Comma separated list of registers to read for the measurement
+channel-type-config.config.solarman.number-channel.rule.label = Rule
+channel-type-config.config.solarman.number-channel.rule.description = The type of measurement. See explanation for possible values
+channel-type-config.config.solarman.number-channel.rule.option.1 = Unsigned Short
+channel-type-config.config.solarman.number-channel.rule.option.2 = Signed Short
+channel-type-config.config.solarman.number-channel.rule.option.3 = Unsigned Integer
+channel-type-config.config.solarman.number-channel.rule.option.4 = Signed Integer
+channel-type-config.config.solarman.number-channel.rule.option.5 = Text
+channel-type-config.config.solarman.number-channel.rule.option.6 = Bytes
+channel-type-config.config.solarman.number-channel.rule.option.7 = Version
+channel-type-config.config.solarman.number-channel.rule.option.8 = Date Time
+channel-type-config.config.solarman.number-channel.rule.option.9 = Time
+channel-type-config.config.solarman.number-channel.scale.label = Scale
+channel-type-config.config.solarman.number-channel.scale.description = The scaling factor, the final value will be scaled by this
+channel-type-config.config.solarman.number-channel.uom.label = Unit of Measurement
+channel-type-config.config.solarman.number-channel.uom.description = The unit of measurement used for this channel
+channel-type-config.config.solarman.string-channel.offset.label = Offset
+channel-type-config.config.solarman.string-channel.offset.description = The offset subtracted from the measurement
+channel-type-config.config.solarman.string-channel.registers.label = Registers
+channel-type-config.config.solarman.string-channel.registers.description = Comma separated list of registers to read for the measurement
+channel-type-config.config.solarman.string-channel.rule.label = Rule
+channel-type-config.config.solarman.string-channel.rule.description = The type of measurement. See explanation for possible values
+channel-type-config.config.solarman.string-channel.rule.option.1 = Unsigned Short
+channel-type-config.config.solarman.string-channel.rule.option.2 = Signed Short
+channel-type-config.config.solarman.string-channel.rule.option.3 = Unsigned Integer
+channel-type-config.config.solarman.string-channel.rule.option.4 = Signed Integer
+channel-type-config.config.solarman.string-channel.rule.option.5 = Text
+channel-type-config.config.solarman.string-channel.rule.option.6 = Bytes
+channel-type-config.config.solarman.string-channel.rule.option.7 = Version
+channel-type-config.config.solarman.string-channel.rule.option.8 = Date Time
+channel-type-config.config.solarman.string-channel.rule.option.9 = Time
+channel-type-config.config.solarman.string-channel.scale.label = Scale
+channel-type-config.config.solarman.string-channel.scale.description = The scaling factor, the final value will be scaled by this
+channel-type-config.config.solarman.string-channel.uom.label = Unit of Measurement
+channel-type-config.config.solarman.string-channel.uom.description = The unit of measurement used for this channel
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/thing/channels.xml
new file mode 100644 (file)
index 0000000..b8e1d75
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="solarman"
+       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="string">
+               <item-type>String</item-type>
+               <label>Text Value</label>
+               <config-description-ref uri="channel-type-config:solarman:string-channel"/>
+       </channel-type>
+
+       <channel-type id="number">
+               <item-type>Number</item-type>
+               <label>Number Value</label>
+               <config-description-ref uri="channel-type-config:solarman:number-channel"/>
+       </channel-type>
+
+       <channel-type id="datetime">
+               <item-type>DateTime</item-type>
+               <label>Datetime Value</label>
+               <config-description-ref uri="channel-type-config:solarman:datetime-channel"/>
+       </channel-type>
+
+       <channel-type id="dynamic">
+               <item-type>String</item-type>
+               <label>Dynamic Channel</label>
+               <config-description-ref uri="channel-type-config:solarman:dynamic-channel"/>
+       </channel-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/thing/thing-types.xml
new file mode 100644 (file)
index 0000000..4db7f77
--- /dev/null
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="solarman"
+       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">
+
+       <!-- Sample Thing Type -->
+       <thing-type id="logger" extensible="true">
+               <label>Solarman Logger</label>
+               <description>This thing allows communication with Solarman (IGEN-Tech) v5 based solar inverter data loggers over the
+                       local network. Compatible with inverters from manufacturers such as Deye, Sofar, Solis, ZCS Azzurro, and KStar.</description>
+
+               <config-description>
+                       <parameter name="hostname" type="text" required="true">
+                               <context>network-address</context>
+                               <label>Hostname</label>
+                               <description>Hostname or IP address of the Solarman logger.</description>
+                               <advanced>false</advanced>
+                       </parameter>
+                       <parameter name="port" type="integer" required="false">
+                               <label>Port</label>
+                               <description>Port of the Solarman logger (default 8899).</description>
+                               <default>8899</default>
+                               <advanced>true</advanced>
+                       </parameter>
+                       <parameter name="inverterType" type="text" required="true">
+                               <label>Inverter Type</label>
+                               <description>The type of inverter connected to the logger (default deye_sg04lp3).</description>
+                               <advanced>false</advanced>
+                               <options>
+                                       <option value="deye_2mppt">DEYE Microinverter with 2 MPPT Trackers (deye_2mppt)</option>
+                                       <option value="deye_4mppt">DEYE Microinverter with 4 MPPT Trackers (deye_4mppt)</option>
+                                       <option value="deye_hybrid">Generic DEYE/Sunsynk/SolArk Hybrid inverters (deye_hybrid)</option>
+                                       <option value="deye_sg04lp3">DEYE/Sunsynk/SolArk Hybrid 8/12K-SG04LP3 (deye_sg04lp3)</option>
+                                       <option value="deye_string">Generic DEYE/Sunsynk/SolArk String inverters (deye_string)</option>
+                                       <option value="kstar_hybrid">KSTAR Hybrid Inverter (kstar_hybrid)</option>
+                                       <option value="sofar_g3hyd">SOFAR Hybrid Three-Phase Inverter (sofar_g3hyd)</option>
+                                       <option value="sofar_hyd3k-6k-es">SOFAR Hybrid Single-Phase Inverter (sofar_hyd3k-6k-es)</option>
+                                       <option value="sofar_lsw3">SOFAR Inverters (sofar_lsw3)</option>
+                                       <option value="sofar_wifikit">SOFAR WifiKit (sofar_wifikit)</option>
+                                       <option value="solis_1p8k-5g">SOLIS 1P8K-5G (solis_1p8k-5g)</option>
+                                       <option value="solis_hybrid">SOLIS Hybrid Inverter (solis_hybrid)</option>
+                                       <option value="zcs_azzurro-ktl-v3">ZCS Azzurro KTL-V3 Inverters (zcs_azzurro-ktl-v3)</option>
+                               </options>
+                       </parameter>
+                       <parameter name="serialNumber" type="text" required="true">
+                               <label>Serial Number</label>
+                               <description>Serial number of the Solarman logger.</description>
+                               <advanced>false</advanced>
+                       </parameter>
+                       <parameter name="refreshInterval" type="integer" required="false" unit="s" min="30">
+                               <label>Refresh Interval</label>
+                               <description>Interval to query the logger (default 60).</description>
+                               <default>60</default>
+                               <advanced>true</advanced>
+                       </parameter>
+                       <parameter name="additionalRequests" type="text" required="false">
+                               <label>Additional Requests</label>
+                               <description>Additional requests besides the ones defined in the inverter definition.
+                                       Format is
+                                       mb_functioncode1:start1-end1, mb_functioncode2:start2-end2,...
+                                       Example 0x03:0x0000-0x0100,0x03:0x0200-0x0300
+                               </description>
+                               <advanced>true</advanced>
+                       </parameter>
+               </config-description>
+       </thing-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_2mppt.yaml b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_2mppt.yaml
new file mode 100644 (file)
index 0000000..5ce0bd6
--- /dev/null
@@ -0,0 +1,137 @@
+# First version : 22.2.2023
+# Microinverter SUN600G3 (DEYE/VESDAS)
+# 2x MPPT, 2x inverter
+# 1x Logger, 2x Module, 
+
+requests:
+  - start: 0x0003
+    end:  0x0080
+    mb_functioncode: 0x03
+
+parameters:
+  - group: solar
+    items: 
+    - name: "PV1 Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x006D]
+      icon: 'mdi:solar-power'
+
+    - name: "PV2 Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x006F]
+      icon: 'mdi:solar-power'
+
+    - name: "PV1 Current"
+      class: "current"
+      uom: "A"
+      scale: 0.1
+      rule: 1
+      registers: [0x006E]
+      icon: 'mdi:solar-power'
+
+    - name: "PV2 Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.1
+      rule: 1
+      registers: [0x0070]
+      icon: 'mdi:solar-power'
+
+    - name: "Daily Production"
+      class: "energy"
+      state_class: "total"
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [0x003C]
+      icon: 'mdi:solar-power'
+
+    - name: "Total Production"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 3
+      registers: [0x003F,0x0040]
+      icon: 'mdi:solar-power'
+      validation:
+        min: 0.1
+
+  - group: Grid
+    items:
+    - name: "AC Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0049]
+      icon: 'mdi:transmission-tower'
+
+    - name: "AC Output Frequency"
+      class: "frequency"
+      state_class: "measurement"
+      uom: "Hz"
+      scale: 0.01
+      rule: 1
+      registers: [0x004F]
+      icon: 'mdi:home-lightning-bolt'
+
+  - group: Inverter
+    items:
+    - name: "Running Status"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x003B]
+      isstr: true
+      lookup:
+      - key: 0
+        value: "Stand-by"
+      - key: 1
+        value: "Self-check"
+      - key: 2
+        value: "Normal"
+      - key: 3
+        value: "Warning"
+      - key: 4
+        value: "Fault"
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Total AC Output Power (Active)"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 0.1
+      rule: 3
+      registers: [0x0056, 0x0057]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Radiator Temperature"
+      class: "temperature"
+      uom: "°C"
+      state_class: "measurement"
+      scale: 0.01
+      rule: 1
+      offset: 1000
+      registers: [0x005a]
+
+    - name: "Inverter ID"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 5
+      registers: [0x0003,0x0004,0x0005,0x0006,0x0007]
+      isstr: true
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_4mppt.yaml b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_4mppt.yaml
new file mode 100644 (file)
index 0000000..2b84b17
--- /dev/null
@@ -0,0 +1,470 @@
+#
+# Borrowed form https://github.com/StephanJoubert/home_assistant_solarman/
+# Additional info from https://github.com/kbialek/deye-inverter-mqtt/blob/19ace123339beec7a574b983f631309f8d285883/deye_sensor.py
+#
+# First version : 22.2.2023
+# Microinverter SUN600G3 (DEYE/VESDAS)
+# 2x MPPT, 2x inverter
+# 1x Logger, 2x Module,
+# Added info for 4x MPPT Microinverters on 2023-06-23
+
+requests:
+  - start: 0x0003
+    end:  0x0080
+    mb_functioncode: 0x03
+
+parameters:
+  - group: solar
+    items:
+      - name: "PV1 Voltage"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [0x006D]
+        icon: 'mdi:solar-power'
+
+      - name: "PV2 Voltage"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [0x006F]
+        icon: 'mdi:solar-power'
+
+      - name: "PV3 Voltage"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [0x0071]
+        icon: 'mdi:solar-power'
+
+      - name: "PV4 Voltage"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [0x0073]
+        icon: 'mdi:solar-power'
+
+      - name: "PV1 Current"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.1
+        rule: 1
+        registers: [0x006E]
+        icon: 'mdi:solar-power'
+
+      - name: "PV2 Current"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.1
+        rule: 1
+        registers: [0x0070]
+        icon: 'mdi:solar-power'
+
+      - name: "PV3 Current"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.1
+        rule: 1
+        registers: [0x0072]
+        icon: 'mdi:solar-power'
+
+      - name: "PV4 Current"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.1
+        rule: 1
+        registers: [0x0074]
+        icon: 'mdi:solar-power'
+
+      - name: "Daily Production"
+        class: "energy"
+        state_class: "total"
+        uom: "kWh"
+        scale: 0.1
+        rule: 1
+        registers: [0x003C]
+        icon: 'mdi:solar-power'
+
+      - name: "Daily Production 1"
+        class: "energy"
+        state_class: "total"
+        uom: "kWh"
+        scale: 0.1
+        rule: 1
+        registers: [0x0041]
+        icon: 'mdi:solar-power'
+
+      - name: "Daily Production 2"
+        class: "energy"
+        state_class: "total"
+        uom: "kWh"
+        scale: 0.1
+        rule: 1
+        registers: [0x0042]
+        icon: 'mdi:solar-power'
+
+      - name: "Daily Production 3"
+        class: "energy"
+        state_class: "total"
+        uom: "kWh"
+        scale: 0.1
+        rule: 1
+        registers: [0x0043]
+        icon: 'mdi:solar-power'
+
+      - name: "Daily Production 4"
+        class: "energy"
+        state_class: "total"
+        uom: "kWh"
+        scale: 0.1
+        rule: 1
+        registers: [0x0044]
+        icon: 'mdi:solar-power'
+
+      - name: "Total Production"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.1
+        rule: 3
+        registers: [0x003F,0x0040]
+        icon: 'mdi:solar-power'
+        validation:
+          min: 0.1
+
+      - name: "Total Production 1"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.1
+        rule: 3
+        registers: [0x0045]
+        icon: 'mdi:solar-power'
+
+      - name: "Total Production 2"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.1
+        rule: 3
+        registers: [0x0047]
+        icon: 'mdi:solar-power'
+
+      - name: "Total Production 3"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.1
+        rule: 3
+        registers: [0x0046]
+        icon: 'mdi:solar-power'
+
+      - name: "Total Production 4"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.1
+        rule: 3
+        registers: [0x0048]
+        icon: 'mdi:solar-power'
+
+      - name: "Active Power Regulations"
+        class: ""
+        state_class: ""
+        uom: "%"
+        scale: 1
+        rule: 1
+        registers: [0x0028]
+        icon: 'mdi:solar-power'
+
+  - group: Grid
+    items:
+      - name: "AC Voltage"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [0x0049]
+        icon: 'mdi:transmission-tower'
+
+      - name: "Grid Current"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.1
+        rule: 2
+        registers: [0x004C]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "AC Output Frequency"
+        class: "frequency"
+        state_class: "measurement"
+        uom: "Hz"
+        scale: 0.01
+        rule: 1
+        registers: [0x004F]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "Grid Voltage Upp Limit"
+        class: "voltage"
+        state_class: ""
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [0x001B]
+        icon: 'mdi:transmission-tower'
+
+      - name: "Grid Voltage Lower Limit"
+        class: "voltage"
+        state_class: ""
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [0x001C]
+        icon: 'mdi:transmission-tower'
+
+      - name: "Grid Frequency Upper Limit"
+        class: "frequency"
+        state_class: ""
+        uom: "Hz"
+        scale: 0.01
+        rule: 1
+        registers: [0x001D]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "Grid Frequency Lower Limit"
+        class: "frequency"
+        state_class: ""
+        uom: "Hz"
+        scale: 0.01
+        rule: 1
+        registers: [0x001E]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "Overfrequency And Load Reduction Starting Point"
+        class: "frequency"
+        state_class: ""
+        uom: "Hz"
+        scale: 0.01
+        rule: 1
+        registers: [0x0022]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "Overfrequency And Load Reduction Percentage"
+        class: ""
+        state_class: ""
+        uom: "%"
+        scale: 1
+        rule: 1
+        registers: [0x0023]
+        icon: ''
+
+      - name: "ON-OFF Enable"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 1
+        registers: [0x002B]
+        isstr: true
+        lookup:
+          - key: 0
+            value: "OFF"
+          - key: 1
+            value: "ON"
+        icon: 'mdi:toggle-switch'
+
+      - name: "Island Protection Enable"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 1
+        registers: [0x002E]
+        isstr: true
+        lookup:
+          - key: 0
+            value: "Disabled"
+          - key: 1
+            value: "Enabled"
+        icon: 'mdi:island'
+
+      - name: "Overfrequency&Load-shedding Enable"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 1
+        registers: [0x0031]
+        isstr: true
+        lookup:
+          - key: 0
+            value: "Disabled"
+          - key: 1
+            value: "Enabled"
+        icon: 'mdi:toggle-switch'
+
+  - group: Inverter
+    items:
+      - name: "Running Status"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 1
+        registers: [0x003B]
+        isstr: true
+        lookup:
+          - key: 0
+            value: "Stand-by"
+          - key: 1
+            value: "Self-check"
+          - key: 2
+            value: "Normal"
+          - key: 3
+            value: "Warning"
+          - key: 4
+            value: "Fault"
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "Total AC Output Power (Active)"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 0.1
+        rule: 3
+        registers: [0x0056, 0x0057]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "Radiator Temperature"
+        class: "temperature"
+        uom: "°C"
+        state_class: "measurement"
+        scale: 0.01
+        rule: 1
+        offset: 1000
+        registers: [0x005a]
+
+      - name: "Inverter ID"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 5
+        registers: [0x0003,0x0004,0x0005,0x0006,0x0007]
+        isstr: true
+
+      - name: "Hardware Version"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 7
+        registers: [0x000C]
+        isstr: true
+
+      - name: "DC Master Firmware Version"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 7
+        registers: [0x000D]
+        isstr: true
+
+      - name: "AC Version. Number"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 7
+        registers: [0x000E]
+        isstr: true
+
+      - name: "Rated Power"
+        class: "energy"
+        state_class: ""
+        uom: "W"
+        scale: 0.1
+        rule: 1
+        registers: [0x0010]
+        icon: 'mdi:solar-power'
+
+      - name: "Communication Protocol Version"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 7
+        registers: [0x0012]
+        isstr: true
+
+      - name: "Start-up Self-checking Time "
+        class: ""
+        state_class: ""
+        uom: "s"
+        scale: 1
+        rule: 1
+        registers: [0x0015]
+        icon: 'mdi:solar-power'
+
+      - name: "Update Time"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 8
+        registers: [0x0016,0x0017,0x0018]
+        isstr: true
+
+      - name: "Soft Start Enable"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 1
+        registers: [0x002F]
+        isstr: true
+        lookup:
+          - key: 0
+            value: "Disabled"
+          - key: 1
+            value: "Enabled"
+        icon: 'mdi:toggle-switch'
+
+      - name: "Power Factor Regulation"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 0.1
+        rule: 2
+        registers: [0x0032]
+        icon: ''
+
+      - name: "Restore Factory Settings"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 1
+        registers: [0x0036]
+        isstr: true
+        lookup:
+          - key: 0
+            value: "Disabled"
+          - key: 1
+            value: "Enabled"
+        icon: 'mdi:factory'
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_hybrid.yaml b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_hybrid.yaml
new file mode 100644 (file)
index 0000000..b1e25e5
--- /dev/null
@@ -0,0 +1,548 @@
+requests:
+  - start: 0x0003
+    end: 0x0070
+    mb_functioncode: 0x03
+  - start: 0x0096  
+    end: 0x00f8
+    mb_functioncode: 0x03
+
+parameters:
+ - group: solar
+   items: 
+    - name: "PV1 Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 1
+      registers: [0x00BA]
+      icon: 'mdi:solar-power'
+
+    - name: "PV2 Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 1
+      registers: [0x00BB]
+      icon: 'mdi:solar-power'
+
+    - name: "PV1 Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x006D]
+      icon: 'mdi:solar-power'
+
+    - name: "PV2 Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x006F]
+      icon: 'mdi:solar-power'
+
+    - name: "PV1 Current"
+      class: "current"
+      uom: "A"
+      scale: 0.1
+      rule: 1
+      registers: [0x006E]
+      icon: 'mdi:solar-power'
+
+    - name: "PV2 Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.1
+      rule: 1
+      registers: [0x0070]
+      icon: 'mdi:solar-power'
+
+    - name: "Daily Production"
+      class: "energy"
+      state_class: "measurement"
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [0x006C]
+      icon: 'mdi:solar-power'
+
+    - name: "Total Production"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 3
+      registers: [0x0060,0x0061]
+      icon: 'mdi:solar-power'
+
+    - name: "Micro-inverter Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 1
+      registers: [0x00A6]
+      icon: 'mdi:solar-power'
+
+ - group: Battery
+   items: 
+    - name: "Total Battery Charge"
+      class: "energy"
+      state_class: "total_increasing"      
+      uom: "kWh"
+      scale: 0.1
+      rule: 3
+      registers: [0x0048,0x0049]
+      icon: 'mdi:battery-plus'
+
+    - name: "Total Battery Discharge"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 3
+      registers: [0x004A,0x004B]
+      icon: 'mdi:battery-minus'
+
+    - name: "Battery Status"
+      class: ""
+      state_class: "measurement"
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x00BD]
+      isstr: true
+      lookup: 
+      -  key: 0
+         value: "Charge"
+      -  key: 1
+         value: "Stand-by"
+      -  key: 2
+         value: "Discharge"
+      icon: 'mdi:battery'
+
+    - name: "Battery Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x00BE]
+      icon: 'mdi:battery'
+
+    - name: "Battery Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.01
+      rule: 1
+      registers: [0x00B7]
+      icon: 'mdi:battery'
+
+    - name: "Battery SOC"
+      class: "battery"
+      state_class: "measurement"
+      uom: "%"
+      scale: 1
+      rule: 1
+      registers: [0x00B8]
+      icon: 'mdi:battery'
+
+    - name: "Battery Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.01
+      rule: 2
+      registers: [0x00BF]
+      icon: 'mdi:battery'
+
+    - name: "Battery Temperature"
+      class: "temperature"
+      state_class: "measurement"
+      uom: "°C"
+      scale: 0.1
+      rule: 1
+      offset: 1000      
+      registers: [0x00B6]
+      icon: 'mdi:battery'
+
+ - group: Grid
+   items: 
+    - name: "Total Grid Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x00A9]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Grid Voltage L1"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0096]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Grid Voltage L2"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0097]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Internal CT L1 Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x00A7]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Internal CT L2 Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x00A8]
+      icon: 'mdi:transmission-tower'
+
+    - name: "External CT L1 Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x00AA]
+      icon: 'mdi:transmission-tower'
+
+    - name: "External CT L2 Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x00AB]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Daily Energy Bought"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [0x004C]
+      icon: 'mdi:transmission-tower-export'
+
+    - name: "Total Energy Bought"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [0x004E,0x0050]
+      icon: 'mdi:transmission-tower-export'
+      
+    - name: "Daily Energy Sold"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [0x004D]
+      icon: 'mdi:transmission-tower-import'
+
+    - name: "Total Energy Sold"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 3
+      registers: [0x0051,0x0052]
+      icon: 'mdi:transmission-tower-import'
+
+
+    - name: "Total Grid Production"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 4
+      registers: [0x003F,0x0040]
+      icon: 'mdi:transmission-tower'
+
+ - group: Upload
+   items: 
+    - name: "Total Load Power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 1
+      rule: 1
+      registers: [0x00B2]
+      icon: 'mdi:lightning-bolt-outline'
+
+    - name: "Load L1 Power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 1
+      rule: 1
+      registers: [0x00B0]
+      icon: 'mdi:lightning-bolt-outline'
+
+    - name: "Load L2 Power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 1
+      rule: 1
+      registers: [0x00B1]
+      icon: 'mdi:lightning-bolt-outline'
+
+    - name: "Load Voltage"
+      class: "voltage"
+      state_class: "measurement"      
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x009D]
+      icon: 'mdi:lightning-bolt-outline'
+
+    - name: "Daily Load Consumption"
+      class: "energy"
+      state_class: "total_increasing"      
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [0x0054]
+      icon: 'mdi:lightning-bolt-outline'
+
+    - name: "Total Load Consumption"
+      class: "energy"
+      state_class: "total_increasing"      
+      uom: "kWh"
+      scale: 0.1
+      rule: 3
+      registers: [0x0055,0x0056]
+      icon: 'mdi:lightning-bolt-outline'
+
+    - name: "SmartLoad Enable Status"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x00C3]
+      isstr: true
+      lookup: 
+      -  key: 0
+         value: "OFF"
+      -  key: 1
+         value: "ON"
+      icon: 'mdi:lightning-bolt-outline'
+
+ - group: Inverter
+   items: 
+    - name: "Running Status"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x003B]
+      isstr: true
+      lookup: 
+      -  key: 0
+         value: "Stand-by"
+      -  key: 1
+         value: "Self-checking"
+      -  key: 2
+         value: "Normal"
+      -  key: 3
+         value: "FAULT"
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Total Power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x00AF]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Current L1"
+      class: "current"
+      state_class: "measurement"      
+      uom: "A"
+      scale: 0.01
+      rule: 2
+      registers: [0x00A4]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Current L2"
+      class: "current"
+      uom: "A"
+      scale: 0.01
+      rule: 2
+      registers: [0x00A5]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Inverter L1 Power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x00AD]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Inverter L2 Power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x00AE]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "DC Temperature"
+      class: "temperature"
+      state_class: "measurement"      
+      uom: "°C"
+      scale: 0.1
+      rule: 2
+      offset: 1000      
+      registers: [0x005A]
+      icon: 'mdi:thermometer'
+
+    - name: "AC Temperature"
+      class: "temperature"
+      state_class: "measurement"      
+      uom: "°C"
+      scale: 0.1
+      rule: 2
+      offset: 1000      
+      registers: [0x005B]
+      icon: 'mdi:thermometer'
+
+    - name: "Inverter ID"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 5
+      registers: [0x0003,0x0004,0x0005,0x0006,0x0007]
+      isstr: true
+      
+    - name: "Communication Board Version No."
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x000E]
+      isstr: true
+
+    - name: "Control Board Version No."
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x000D]
+      isstr: true
+
+    - name: "Grid-connected Status"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x00C2]
+      isstr: true
+      lookup: 
+      -  key: 0
+         value: "Off-Grid"
+      -  key: 1
+         value: "On-Grid"
+
+    - name: "Gen-connected Status"
+      class: ""
+      uom: ""
+      state_class: ""      
+      scale: 1
+      rule: 1
+      registers: [0x00A6]
+      isstr: true
+      lookup: 
+      -  key: 0
+         value: "none"
+      -  key: 1
+         value: "On"
+
+    - name: "Gen Power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 1
+      rule: 1
+      registers: [0x00A6]
+
+    - name: "Time of use"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x00F8]
+      isstr: true
+      lookup: 
+      -  key: 0
+         value: "Disable"
+      -  key: 1
+         value: "Enable"
+
+    - name: "Work Mode"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 3
+      registers: [0x00F4,0x00F7]
+      isstr: true
+      lookup: 
+      -  key: 0
+         value: "Selling First"
+      -  key: 1
+         value: "Zero-Export to Load&Solar Sell"
+      -  key: 2
+         value: "Zero-Export to Home&Solar Sell"
+      -  key: 3
+         value: "Zero-Export to Load"
+      -  key: 4
+         value: "Zero-Export to Home"
+      icon: 'mdi:home-lightning-bolt'
+
+ - group: Alert
+   items: 
+    - name: "Alert"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 6
+      registers: [0x0065,0x0066,0x0067,0x0068,0x0069,0x006A]
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_sg04lp3.yaml b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_sg04lp3.yaml
new file mode 100644 (file)
index 0000000..7d80fe4
--- /dev/null
@@ -0,0 +1,523 @@
+# SUN-8/12K-SG04LP3-EU | 8/12KW | Three Phase | 2 MPPT | Hybrid Inverter | Low Voltage Battery
+# tested with LSW3_15_FFFF_1.0.91R + LSW3_15_FFFF_1.0.84
+
+requests:
+  - start: 0x0003
+    end: 0x0059
+    mb_functioncode: 0x03
+  - start: 0x0202  
+    end: 0x022E
+    mb_functioncode: 0x03 
+  - start: 0x024A  
+    end: 0x024F
+    mb_functioncode: 0x03  
+  - start: 0x0256
+    end: 0x027C
+    mb_functioncode: 0x03    
+  - start: 0x0284  
+    end: 0x028D
+    mb_functioncode: 0x03   
+  - start: 0x02A0  
+    end: 0x02A7
+    mb_functioncode: 0x03
+  
+parameters:
+ - group: solar
+   items: 
+    - name: "PV1 Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 1
+      registers: [0x02A0]
+      icon: 'mdi:solar-power'
+
+    - name: "PV2 Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 1
+      registers: [0x02A1]
+      icon: 'mdi:solar-power'
+
+    - name: "PV1 Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x02A4]
+      icon: 'mdi:solar-power'
+
+    - name: "PV2 Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x02A6]
+      icon: 'mdi:solar-power'
+
+    - name: "PV1 Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.1
+      rule: 1
+      registers: [0x02A5]
+      icon: 'mdi:solar-power'
+
+    - name: "PV2 Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.1
+      rule: 1
+      registers: [0x02A7]
+      icon: 'mdi:solar-power'
+
+    - name: "Daily Production"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [0x0211]
+      icon: 'mdi:solar-power'
+      validation:
+        max: 100
+        invalidate_all:
+
+    - name: "Total Production"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 3
+      registers: [0x0216,0x0217]
+      icon: 'mdi:solar-power'
+
+ - group: Battery
+   items: 
+
+    - name: "Daily Battery Charge"
+      class: "energy"
+      state_class: "total_increasing"      
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [0x0202]
+      icon: 'mdi:battery-plus'
+    - name: "Daily Battery Discharge"
+      class: "energy"
+      state_class: "total_increasing"      
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [0x0203]
+      icon: 'mdi:battery-plus'
+
+    - name: "Total Battery Charge"
+      class: "energy"
+      state_class: "total_increasing"      
+      uom: "kWh"
+      scale: 0.1
+      rule: 3
+      registers: [0x0204,0x0205]
+      icon: 'mdi:battery-plus'
+
+    - name: "Total Battery Discharge"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 3
+      registers: [0x0206,0x0207]
+      icon: 'mdi:battery-minus'
+
+    - name: "Battery Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x024E]
+      icon: 'mdi:battery'
+
+    - name: "Battery Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.01
+      rule: 1
+      registers: [0x024B]
+      icon: 'mdi:battery'
+
+    - name: "Battery SOC"
+      class: "battery"
+      state_class: "measurement"
+      uom: "%"
+      scale: 1
+      rule: 1
+      registers: [0x024C]
+      icon: 'mdi:battery'
+      validation:
+        min: 0
+        max: 101
+
+    - name: "Battery Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.01
+      rule: 2
+      registers: [0x024F]
+      icon: 'mdi:battery'
+
+    - name: "Battery Temperature"
+      class: "temperature"
+      state_class: "measurement"
+      uom: "°C"
+      scale: 0.1
+      rule: 1
+      offset: 1000      
+      registers: [0x024A]
+      icon: 'mdi:battery'
+      validation:
+        min: 1
+        max: 99
+        invalidate_all:
+ - group: Grid
+   items: 
+    - name: "Total Grid Power"
+      class: "measurement"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x0271]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Grid Voltage L1"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0256]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Grid Voltage L2"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0257]
+      icon: 'mdi:transmission-tower'
+      
+    - name: "Grid Voltage L3"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0258]
+      icon: 'mdi:transmission-tower'  
+
+    - name: "Internal CT L1 Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x025C]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Internal CT L2 Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x025D]
+      icon: 'mdi:transmission-tower'
+      
+    - name: "Internal CT L3 Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x025E]
+      icon: 'mdi:transmission-tower'  
+
+    - name: "External CT L1 Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x0268]
+      icon: 'mdi:transmission-tower'
+
+    - name: "External CT L2 Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x0269]
+      icon: 'mdi:transmission-tower'
+      
+    - name: "External CT L3 Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x026A]
+      icon: 'mdi:transmission-tower'
+      
+    - name: "Daily Energy Bought"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [0x0208]
+      icon: 'mdi:transmission-tower-export'
+
+    - name: "Total Energy Bought"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [0x020A,0x020B]
+      icon: 'mdi:transmission-tower-export'
+      
+    - name: "Daily Energy Sold"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [0x0209]
+      icon: 'mdi:transmission-tower-import'
+
+    - name: "Total Energy Sold"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 3
+      registers: [0x020C,0x020D]
+      icon: 'mdi:transmission-tower-import'
+
+    - name: "Total Grid Production"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 4
+      registers: [0x020C,0x020D]
+      icon: 'mdi:transmission-tower'
+      
+ - group: Upload
+   items: 
+    - name: "Total Load Power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 1
+      rule: 1
+      registers: [0x028D]
+      icon: 'mdi:lightning-bolt-outline'
+
+    - name: "Load L1 Power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 1
+      rule: 1
+      registers: [0x028A]
+      icon: 'mdi:lightning-bolt-outline'
+
+    - name: "Load L2 Power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 1
+      rule: 1
+      registers: [0x028B]
+      icon: 'mdi:lightning-bolt-outline'
+      
+    - name: "Load L3 Power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 1
+      rule: 1
+      registers: [0x028C]
+      icon: 'mdi:lightning-bolt-outline'
+      
+    - name: "Load Voltage L1"
+      class: "voltage"
+      state_class: "measurement"      
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0284]
+      icon: 'mdi:lightning-bolt-outline'
+      
+    - name: "Load Voltage L2"
+      class: "voltage"
+      state_class: "measurement"      
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0285]
+      icon: 'mdi:lightning-bolt-outline'
+      
+    - name: "Load Voltage L3"
+      class: "voltage"
+      state_class: "measurement"      
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0286]
+      icon: 'mdi:lightning-bolt-outline'
+      
+    - name: "Daily Load Consumption"
+      class: "energy"
+      state_class: "total_increasing"      
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [0x020E]
+      icon: 'mdi:lightning-bolt-outline'
+
+    - name: "Total Load Consumption"
+      class: "energy"
+      state_class: "total_increasing"      
+      uom: "kWh"
+      scale: 0.1
+      rule: 3
+      registers: [0x020F,0x0210]
+      icon: 'mdi:lightning-bolt-outline'
+     
+ - group: Inverter
+   items:
+    - name: "Current L1"
+      class: "current"
+      state_class: "measurement"      
+      uom: "A"
+      scale: 0.01
+      rule: 2
+      registers: [0x0276]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Current L2"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.01
+      rule: 2
+      registers: [0x0277]
+      icon: 'mdi:home-lightning-bolt'
+      
+    - name: "Current L3"
+      class: "current"
+      uom: "A"
+      scale: 0.01
+      rule: 2
+      registers: [0x0278]
+      icon: 'mdi:home-lightning-bolt'
+      
+    - name: "Inverter L1 Power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x0279]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Inverter L2 Power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x027A]
+      icon: 'mdi:home-lightning-bolt'
+      
+    - name: "Inverter L3 Power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 1
+      rule: 2
+      registers: [0x027B]
+      icon: 'mdi:home-lightning-bolt'
+      
+    - name: "DC Temperature"
+      class: "temperature"
+      state_class: "measurement"      
+      uom: "°C"
+      scale: 0.1
+      rule: 2
+      offset: 1000      
+      registers: [0x021C]
+      icon: 'mdi:thermometer'
+
+    - name: "AC Temperature"
+      class: "temperature"
+      state_class: "measurement"      
+      uom: "°C"
+      scale: 0.1
+      rule: 2
+      offset: 1000      
+      registers: [0x021D]
+      icon: 'mdi:thermometer'
+
+    - name: "Inverter ID"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 5
+      registers: [0x0003,0x0004,0x0005,0x0006,0x0007]
+      isstr: true
+    
+    - name: "Communication Board Version No."
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0011]
+      isstr: true
+
+    - name: "Control Board Version No."
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x000D]
+      isstr: true
+
+ - group: Alert
+   items: 
+    - name: "Alert"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 6
+      registers: [0x0229,0x022A,0x22B,0x022C,0x022D,0x022E]
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_string.yaml b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_string.yaml
new file mode 100644 (file)
index 0000000..566d94f
--- /dev/null
@@ -0,0 +1,206 @@
+
+requests:
+  - start: 0x0003
+    end:  0x0070
+    mb_functioncode: 0x03
+
+parameters:
+ - group: solar
+   items: 
+    - name: "PV1 Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x006D]
+      icon: 'mdi:solar-power'
+
+    - name: "PV2 Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x006F]
+      icon: 'mdi:solar-power'
+
+    - name: "PV1 Current"
+      class: "current"
+      uom: "A"
+      scale: 0.1
+      rule: 1
+      registers: [0x006E]
+      icon: 'mdi:solar-power'
+
+    - name: "PV2 Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.1
+      rule: 1
+      registers: [0x0070]
+      icon: 'mdi:solar-power'
+
+    - name: "Daily Production"
+      class: "energy"
+      state_class: "total"
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [0x003C]
+      icon: 'mdi:solar-power'
+
+    - name: "Total Production"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 3
+      registers: [0x003F,0x0040]
+      icon: 'mdi:solar-power'
+      validation:
+        min: 0.1 
+        invalidate_all:
+
+
+
+ - group: Grid
+   items: 
+    - name: "Grid Voltage L-L(A)"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0049]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Grid Voltage L-L(B))"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x004A]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Grid Voltage L-L(C)"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x004B]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Grid Current A"
+      class: "current"
+      state_class: "measurement"      
+      uom: "A"
+      scale: 0.1
+      rule: 2
+      registers: [0x004C]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Grid Current B"
+      class: "current"
+      state_class: "measurement"      
+      uom: "A"
+      scale: 0.1
+      rule: 2
+      registers: [0x004D]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Grid Current C"
+      class: "current"
+      state_class: "measurement"      
+      uom: "A"
+      scale: 0.1
+      rule: 2
+      registers: [0x004E]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Grid Frequency"
+      class: "frequency"
+      state_class: "measurement"      
+      uom: "Hz"
+      scale: 0.01
+      rule: 1
+      registers: [0x004F]
+      icon: 'mdi:home-lightning-bolt'
+
+ - group: Inverter
+   items: 
+    - name: "Running Status"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x003B]
+      isstr: true
+      lookup: 
+      -  key: 0
+         value: "Stand-by"
+      -  key: 1
+         value: "Self-checking"
+      -  key: 2
+         value: "Normal"
+      -  key: 3
+         value: "FAULT"
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Total Output AC Power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 0.1
+      rule: 3
+      registers: [0x0050,0x0051]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Input Active Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 0.1
+      rule: 3
+      registers: [0x0052, 0x0053]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Output Apparent Power"
+      class: "apparent_power"
+      state_class: "measurement"
+      uom: "VA"
+      scale: 0.1
+      rule: 3
+      registers: [0x0054, 0x0055]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Output Active Power"
+      class: "energy"
+      state_class: "measurement"
+      uom: "W"
+      scale: 0.1
+      rule: 3
+      registers: [0x0056, 0x0057]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Output Reactive Power"
+      class: "reactive_power"
+      state_class: "measurement"
+      uom: "VAR"
+      rule: 3
+      scale: 0.1
+      registers: [0x0058, 0x0059]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Inverter ID"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 5
+      registers: [0x0003,0x0004,0x0005,0x0006,0x0007]
+      isstr: true
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/definitions/kstar_hybrid.yaml b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/kstar_hybrid.yaml
new file mode 100644 (file)
index 0000000..49ec622
--- /dev/null
@@ -0,0 +1,793 @@
+# KSTAR Hybrid Inverter
+# Modbus information taken from "MODBUS RS485 Communication Protocol V2.5" document provided by KSTAR
+
+#INPUT_REGISTERS = 3000 - 3660    # 0x0BB8 - 0x0E4C
+#HOLDING_REGISTERS = 3200 - 3237  # 0x0C80 - 0x0C9B
+
+requests:
+  # Input registers 3000 - 3667
+  - start: 3000
+    end: 3125
+    mb_functioncode: 0x04
+
+  # Input registers 3200 - 3228 not read as they would clash with holding registers
+  - start: 3125
+    end: 3200
+    mb_functioncode: 0x04
+
+  - start: 3228
+    end: 3250
+    mb_functioncode: 0x04
+
+  - start: 3250
+    end: 3375
+    mb_functioncode: 0x04
+
+  - start: 3375
+    end: 3500
+    mb_functioncode: 0x04
+
+  # Holding registers 3200 - 3237. Inverter system information.
+  - start: 3200
+    end: 3218
+    mb_functioncode: 0x03
+
+parameters:
+  - group: solar
+    items:
+      - name: "PV1 Voltage"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [ 3000 ]
+        icon: 'mdi:solar-power'
+
+      - name: "PV2 Voltage"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [ 3001 ]
+        icon: 'mdi:solar-power'
+
+      - name: "PV1 Current"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.01
+        rule: 2
+        registers: [ 3012 ]
+        icon: 'mdi:solar-power'
+
+      - name: "PV2 Current"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.01
+        rule: 2
+        registers: [ 3013 ]
+        icon: 'mdi:solar-power'
+
+      - name: "PV1 Power"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 1
+        rule: 2
+        registers: [ 3024 ]
+        icon: 'mdi:solar-power'
+
+      - name: "PV2 Power"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 1
+        rule: 2
+        registers: [ 3025 ]
+        icon: 'mdi:solar-power'
+
+      - name: "Daily Production"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.1
+        rule: 1
+        registers: [ 3036 ]
+        icon: 'mdi:solar-power'
+
+      - name: "Monthly Production"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 1
+        rule: 3
+        registers: [ 3038, 3037 ]
+        icon: 'mdi:solar-power'
+
+      - name: "Yearly Production"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 1
+        rule: 3
+        registers: [ 3040, 3039 ]
+        icon: 'mdi:solar-power'
+
+      - name: "Cumulative Production"
+        class: "energy"
+        state_class: "total"
+        uom: "kWh"
+        scale: 0.1
+        rule: 3
+        registers: [ 3042, 3041 ]
+        icon: 'mdi:solar-power'
+
+  - group: Power Grid
+    items:
+      # Should this be the sum of the 3 phases "Meter Power"?
+      - name: "Total Grid Power"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 1
+        rule: 2
+        registers: [ 3100 ]
+        icon: 'mdi:transmission-tower'
+
+      - name: "Daily Energy Purchased"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.1
+        rule: 1
+        registers: [ 3109 ]
+        icon: 'mdi:transmission-tower-import'
+
+      - name: "Monthly Energy Purchased"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 1
+        rule: 3
+        registers: [ 3111, 3110 ]
+        icon: 'mdi:transmission-tower-import'
+
+      - name: "Yearly Energy Purchased"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 1
+        rule: 3
+        registers: [ 3113, 3112 ]
+        icon: 'mdi:transmission-tower-import'
+
+      - name: "Cumulative Energy Purchased"
+        class: "energy"
+        state_class: "total"
+        uom: "kWh"
+        scale: 0.1
+        rule: 3
+        registers: [ 3115, 3114 ]
+        icon: 'mdi:transmission-tower-import'
+
+      - name: "Daily Energy Feed-In"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.1
+        rule: 1
+        registers: [ 3116 ]
+        icon: 'mdi:transmission-tower-export'
+
+      - name: "Monthly Energy Feed-In"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 1
+        rule: 3
+        registers: [ 3118, 3117 ]
+        icon: 'mdi:transmission-tower-export'
+
+      - name: "Yearly Energy Feed-In"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 1
+        rule: 3
+        registers: [ 3120, 3119 ]
+        icon: 'mdi:transmission-tower-export'
+
+      - name: "Cumulative Grid Feed-In"
+        class: "energy"
+        state_class: "total"
+        uom: "kWh"
+        scale: 0.1
+        rule: 3
+        registers: [ 3122, 3121 ]
+        icon: 'mdi:transmission-tower-export'
+
+  - group: Electricity Consumption
+    items:
+      # Should this be the sum of the 3 phases "Load Power"?
+      - name: "Total Consumption Power"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 1
+        rule: 1
+        registers: [ 3144 ]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "Daily Consumption"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.1
+        rule: 1
+        registers: [ 3147 ]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "Monthly Consumption"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 1
+        rule: 3
+        registers: [ 3149, 3148 ]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "Yearly Consumption"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 1
+        rule: 3
+        registers: [ 3151, 3150 ]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "Cumulative Consumption"
+        class: "energy"
+        state_class: "total"
+        uom: "kWh"
+        scale: 0.1
+        rule: 3
+        registers: [ 3153, 3152 ]
+        icon: 'mdi:home-lightning-bolt'
+
+  - group: Battery
+    items:
+      - name: "Battery Type"
+        class: "battery"
+        state_class: "measurement"
+        uom: ''
+        scale: 1
+        rule: 1
+        registers: [ 3062 ]
+        icon: 'mdi:battery'
+        lookup:
+        - key: 1
+          value: "Lead-Acid"
+        - key: 6
+          value: "LFP"
+
+      - name: "Battery Voltage"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.01
+        rule: 1
+        registers: [ 3063 ]
+        icon: 'mdi:battery-charging'
+
+      - name: "Battery Current"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.1
+        rule: 2
+        registers: [ 3064 ]
+        icon: 'mdi:battery-charging-10'
+
+      - name: "Battery Power"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 1
+        rule: 2
+        registers: [ 3065 ]
+        icon: 'mdi:battery-charging-high'
+
+      - name: "Battery SoC"
+        class: "battery"
+        state_class: "measurement"
+        uom: "%"
+        scale: 0.1
+        rule: 1
+        registers: [ 3066 ]
+        icon: 'mdi:battery'
+
+      - name: "Battery Temperature"
+        class: "temperature"
+        state_class: "measurement"
+        uom: "°C"
+        scale: 0.1
+        rule: 2
+        registers: [ 3067 ]
+        icon: 'mdi:battery-heart-outline'
+
+      - name: "Battery Discharge Capacity Depth"
+        class: "battery"
+        state_class: "measurement"
+        uom: "%"
+        scale: 1
+        rule: 1
+        registers: [ 3068 ]
+        icon: 'mdi:battery-20'
+        validation:
+          min: 10
+          max: 95
+
+      - name: "Battery Radiator Temperature"
+        class: "temperature"
+        state_class: "measurement"
+        uom: "°C"
+        scale: 0.1
+        rule: 2
+        registers: [ 3056 ]
+        icon: 'mdi:battery-heart-outline'
+
+      - name: "Battery Total Discharge"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 1
+        rule: 3
+        registers: [ 3293, 3292 ]
+        icon: 'mdi:battery-minus-variant'
+
+      - name: "Battery Daily Discharge"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.1
+        rule: 1
+        registers: [ 3294 ]
+        icon: 'mdi:battery-minus-variant'
+
+      - name: "Battery Total Charge"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 1
+        rule: 3
+        registers: [ 3300, 3299 ]
+        icon: 'mdi:battery-minus-variant'
+
+      - name: "Battery Daily Charge"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.1
+        rule: 1
+        registers: [ 3301 ]
+        icon: 'mdi:battery-plus-variant'
+
+  - group: Inverter Information
+    items:
+      - name: "Inverter Working Mode"
+        class: ""
+        state_class: "measurement"
+        uom: ""
+        scale: 1
+        rule: 1
+        registers: [3044]
+        lookup:
+        - key: 0
+          value: "Self Consumption"
+        - key: 1
+          value: "Peak Shift"
+        - key: 2
+          value: "Battery Priority"
+        icon: 'mdi:wrench'
+
+      - name: "Inverter Model"
+        class: ""
+        state_class: "measurement"
+        uom: ""
+        scale: 1
+        rule: 1
+        registers: [3045]
+        lookup:
+        # Single-phase models
+        - key: 0
+          value: "KSE-2K-048S"
+        - key: 1
+          value: "KSE-3K-048S"
+        - key: 2
+          value: "KSE-3.6K-048S"
+        - key: 3
+          value: "KSE-4.6K-048S"
+        - key: 4
+          value: "KSE-5K-048S"
+        - key: 5
+          value: "KSE-3.6K-048"
+        - key: 6
+          value: "KSE-4.6K-048"
+        - key: 7
+          value: "KSE-5K-048"
+        - key: 8
+          value: "KSE-6K-048"
+        - key: 9
+          value: "BluE-S 3680D"
+        - key: 11
+          value: "BluE-S 5000D"
+        - key: 12
+          value: "BluE-S 6000D"
+        - key: 14
+          value: "KSE-3K-048S M1"
+        - key: 15
+          value: "BluE-S 3680D M1"
+        - key: 17
+          value: "BluE-S 5000D M1"
+        - key: 18
+          value: "BluE-S 6000D M1"
+        # Three-phase models
+        - key: 32
+          value: "E10KT"
+        - key: 33
+          value: "E8KT"
+        - key: 34
+          value: "E12KT"
+        icon: 'mdi:wrench'
+
+      - name: "System status"
+        class: ""
+        state_class: "measurement"
+        uom: ""
+        scale: 1
+        rule: 1
+        registers: [3046]
+        lookup:
+        - key: 0
+          value: "Initialize"
+        - key: 1
+          value: "Stand-by"
+        - key: 2
+          value: "Hybrid Grid"
+        - key: 3
+          value: "Off-Network"
+        - key: 4
+          value: "Mains Charging"
+        - key: 5
+          value: "PV Charging"
+        - key: 6
+          value: "Mains Bypass"
+        - key: 7
+          value: "Fault"
+        - key: 8
+          value: "Debug"
+        - key: 9
+          value: "Forced Charge"
+        - key: 10
+          value: "Power on the device separately from the"
+        - key: 11
+          value: "DSP Burn"
+        - key: 12
+          value: "MCU Burn"
+        - key: 13
+          value: "Permanent Error"
+        icon: 'mdi:wrench'
+
+      - name: "Inverter status"
+        class: ""
+        state_class: "measurement"
+        uom: ""
+        scale: 1
+        rule: 1
+        registers: [3047]
+        lookup:
+        - key: 0
+          value: "Stand-by"
+        - key: 1
+          value: "Off-Grid"
+        - key: 2
+          value: "On-Grid"
+        - key: 3
+          value: "Off-Grid to On-Grid"
+        - key: 4
+          value: "On-Grid to Off-Grid"
+        icon: 'mdi:wrench'
+
+      - name: "DCDC status"
+        class: ""
+        state_class: "measurement"
+        uom: ""
+        scale: 1
+        rule: 1
+        registers: [3048]
+        lookup:
+        - key: 0
+          value: "Stand-by"
+        - key: 1
+          value: "Soft Boot"
+        - key: 2
+          value: "Charging Mode"
+        - key: 3
+          value: "Discharging Mode"
+        icon: 'mdi:wrench'
+
+      - name: "DSP Alarm Code"
+        class: ""
+        state_class: "measurement"
+        uom: ""
+        scale: 1
+        rule: 6
+        registers: [3050, 3049]
+        icon: 'mdi:wrench'
+
+      - name: "DSP Error Code"
+        class: ""
+        state_class: "measurement"
+        uom: ""
+        scale: 1
+        rule: 6
+        registers: [3052, 3051]
+        icon: 'mdi:wrench'
+
+      - name: "Grid Standard"
+        class: ""
+        state_class: "measurement"
+        uom: ""
+        scale: 1
+        rule: 1
+        registers: [3193]
+        lookup:
+        - key: 0
+          value: "China"
+        - key: 1
+          value: "Germany"
+        - key: 2
+          value: "Australia"
+        - key: 3
+          value: "Italy"
+        - key: 4
+          value: "Spain"
+        - key: 5
+          value: "UK"
+        - key: 6
+          value: "Hungary"
+        - key: 7
+          value: "Belgium"
+        - key: 8
+          value: "West Australia"
+        - key: 9
+          value: "Greece"
+        - key: 10
+          value: "France"
+        - key: 11
+          value: "Bangkok"
+        - key: 12
+          value: "Thailand"
+        - key: 13
+          value: "South Africa"
+        - key: 14
+          value: "EN50549"
+        - key: 15
+          value: "Brazil"
+        - key: 16
+          value: "VDE0126"
+        - key: 17
+          value: "Ireland"
+        - key: 18
+          value: "Israel"
+        - key: 19
+          value: "Poland"
+        - key: 20
+          value: "Chile"
+        - key: 21
+          value: "Local"
+        icon: 'mdi:wrench'
+
+      - name: "Inverter Model Name"
+        class: ""
+        state_class: "measurement"
+        uom: ""
+        scale: 1
+        rule: 5
+        registers: [3200, 3201, 3202, 3203, 3204, 3205, 3206, 3207]
+        icon: 'mdi:wrench'
+
+      - name: "Inverter Battery Name"
+        class: ""
+        state_class: "measurement"
+        uom: ""
+        scale: 1
+        rule: 5
+        registers: [3208, 3209, 3210, 3211, 3212, 3213, 3214, 3215]
+        icon: 'mdi:wrench'
+
+      # ARM AND DSP version numbers ("VX.Y.Z") are set in the two bytes on each register. The first byte contains the
+      # X.Y part (scale 0.1), and the second by contains the Z part. How should we transform these values from a number
+      # to a parsed string?
+      - name: "ARM Version Number"
+        class: ""
+        state_class: "measurement"
+        uom: ""
+        scale: 1
+        rule: 1
+        registers: [3216]
+        icon: 'mdi:wrench'
+
+      - name: "DSP Version Number"
+        class: ""
+        state_class: "measurement"
+        uom: ""
+        scale: 1
+        rule: 1
+        registers: [3217]
+        icon: 'mdi:wrench'
+
+      - name: "Inverter SN Number"
+        class: ""
+        state_class: "measurement"
+        uom: ""
+        scale: 1
+        rule: 5
+        registers: [3228, 3229, 3230, 3231, 3232, 3233, 3234, 3235, 3236, 3237, 3238]
+        icon: 'mdi:wrench'
+
+  - group: Inverter
+    items:
+      - name: "Inverter Bus Voltage"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [ 3053 ]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "Inverter DC Bus Voltage"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [ 3054 ]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "Inverter Radiator Temperature"
+        class: "temperature"
+        state_class: "measurement"
+        uom: "°C"
+        scale: 0.1
+        rule: 2
+        registers: [ 3055 ]
+        icon: 'mdi:thermometer'
+
+      - name: "Chassis Internal Temperature"
+        class: "temperature"
+        state_class: "measurement"
+        uom: "°C"
+        scale: 0.1
+        rule: 2
+        registers: [ 3057 ]
+        icon: 'mdi:battery-heart-outline'
+
+  # Different phases for 3-phase inverters. Only some models have 3 phases, see "Inverter Model" item
+  # - R: Referent
+  # - S: Secondary
+  # - T: Tertiary
+  - group: R Phase
+    items:
+      - name: "R-phase Grid Voltage"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [ 3097 ]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "R-phase Grid Frequency"
+        class: "frequency"
+        state_class: "measurement"
+        uom: "Hz"
+        scale: 0.01
+        rule: 1
+        registers: [ 3098 ]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "R-phase Meter Current"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.001
+        rule: 2
+        registers: [ 3099 ]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "R-phase Grid Power"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 1
+        rule: 2
+        registers: [ 3100 ]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "R-phase Inverter Voltage"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [ 3123 ]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "R-phase Inverter Current"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.01
+        rule: 2
+        registers: [ 3124 ]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "R-phase Inverter Frequency"
+        class: "frequency"
+        state_class: "measurement"
+        uom: "Hz"
+        scale: 0.01
+        rule: 1
+        registers: [ 3125 ]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "R-phase Inverter Power"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 1
+        rule: 2
+        registers: [ 3126 ]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "R-phase Backup Voltage"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [ 3135 ]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "R-phase Backup Current"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.01
+        rule: 1
+        registers: [ 3136 ]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "R-phase Backup Power"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 1
+        rule: 1
+        registers: [ 3137 ]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: "R-phase Load Power"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 1
+        rule: 1
+        registers: [ 3144 ]
+        icon: 'mdi:home-lightning-bolt'
+
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_g3hyd.yaml b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_g3hyd.yaml
new file mode 100644 (file)
index 0000000..ba30139
--- /dev/null
@@ -0,0 +1,1407 @@
+# Sofar G3 also HYD 5-20KTL-3PH
+# This works also for rebranded ZCS Azzurro 3-Phase inverters such as the 3PH HYD6000 ZSS
+# Note that this won't work if your ZCS inverter is connected via Connext, you have to be using a Wi-Fi or Ethernet Kit such as ZSM-WIFI-USB.
+requests:
+  - start: 0x0404
+    end: 0x0420
+    mb_functioncode: 0x03
+  - start: 0x0484
+    end: 0x04AF
+    mb_functioncode: 0x03
+# off - grid info
+#  - start: 0x0504
+#    end: 0x051F
+#    mb_functioncode: 0x03
+  - start: 0x0584
+    end: 0x0589
+    mb_functioncode: 0x03
+  - start: 0x0604
+    end: 0x060A # end of first battery after this continue battery pack 2,3,4
+    mb_functioncode: 0x03
+  - start: 0x0684
+    end: 0x069B
+    mb_functioncode: 0x03
+parameters:
+
+  - group: Inverter
+    items:
+      - name: "Inverter status"
+        class: ""
+        state_class: "measurement"
+        uom: ""
+        scale: 1
+        rule: 1
+        registers: [ 0x0404 ]
+        isstr: true
+        lookup:
+          - key: 0
+            value: "waiting"
+          - key: 1
+            value: "detection"
+          - key: 2
+            value: "grid-connected"
+          - key: 3
+            value: "emergency power supply"
+          - key: 4
+            value: "recoverable fault"
+          - key: 5
+            value: "permanent fault"
+          - key: 6
+            value: "upgrade"
+          - key: 7
+            value: "self-charging"
+        icon: 'mdi:wrench'
+      - name: "Ambient temperature 1"
+        class: "temperature"
+        uom: "°C"
+        scale: 1
+        rule: 2
+        registers: [ 0x0418 ]
+        icon: 'mdi:thermometer'
+      - name: "Ambient temperature 2"
+        class: "temperature"
+        uom: "°C"
+        scale: 1
+        rule: 2
+        registers: [ 0x0419 ]
+        icon: 'mdi:thermometer'
+      - name: "Radiator temperature 1"
+        class: "temperature"
+        uom: "°C"
+        scale: 1
+        rule: 2
+        registers: [ 0x041A ]
+        icon: 'mdi:thermometer'
+      - name: "Radiator temperature 2"
+        class: "temperature"
+        uom: "°C"
+        scale: 1
+        rule: 2
+        registers: [ 0x041B ]
+        icon: 'mdi:thermometer'
+      - name: "Radiator temperature 3"
+        class: "temperature"
+        uom: "°C"
+        scale: 1
+        rule: 2
+        registers: [ 0x041C ]
+        icon: 'mdi:thermometer'
+      - name: "Radiator temperature 4"
+        class: "temperature"
+        uom: "°C"
+        scale: 1
+        rule: 2
+        registers: [ 0x041D ]
+        icon: 'mdi:thermometer'
+      - name: "Radiator temperature 5"
+        class: "temperature"
+        uom: "°C"
+        scale: 1
+        rule: 2
+        registers: [ 0x041E ]
+        icon: 'mdi:thermometer'
+      - name: "Radiator temperature 6"
+        class: "temperature"
+        uom: "°C"
+        scale: 1
+        rule: 2
+        registers: [ 0x041F ]
+        icon: 'mdi:thermometer'
+      - name: "Module temperature 1"
+        class: "temperature"
+        uom: "°C"
+        scale: 1
+        rule: 2
+        registers: [ 0x0420 ]
+        icon: 'mdi:thermometer'
+      - name: "Module temperature 2"
+        class: "temperature"
+        uom: "°C"
+        scale: 1
+        rule: 2
+        registers: [ 0x0421 ]
+        icon: 'mdi:thermometer'
+      - name: "Module temperature 3"
+        class: "temperature"
+        uom: "°C"
+        scale: 1
+        rule: 2
+        registers: [ 0x0422 ]
+        icon: 'mdi:thermometer'
+
+  - group: InverterDC
+    items:
+      - name: "PV1 Voltage"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [ 0x0584 ]
+        icon: 'mdi:solar-power'
+      - name: "PV1 Current"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.01
+        rule: 1
+        registers: [ 0x0585 ]
+        icon: 'mdi:solar-power'
+      - name: "PV1 Power"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 1
+        registers: [ 0x0586 ]
+        icon: 'mdi:solar-power'
+      - name: "PV2 Voltage"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [ 0x0587 ]
+        icon: 'mdi:solar-power'
+      - name: "PV2 Current"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.01
+        rule: 1
+        registers: [ 0x0588 ]
+        icon: 'mdi:solar-power'
+      - name: "PV2 Power"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 1
+        registers: [ 0x0589 ]
+        icon: 'mdi:solar-power'
+
+  - group: Battery
+    items:
+      - name: "Battery 1 Voltage"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [ 0x0604 ]
+        icon: 'mdi:battery'
+      - name: "Battery 1 Current"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.01
+        rule: 2
+        registers: [ 0x0605 ]
+        icon: 'mdi:current-dc'
+      - name: "Battery 1 Power"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x0606 ]
+        icon: 'mdi:battery-charging'
+      - name: "Battery 1 Temperature"
+        class: "temperature"
+        state_class: "measurement"
+        uom: "°C"
+        scale: 1
+        rule: 2
+        registers: [ 0x0607 ]
+        icon: 'mdi:battery'
+      - name: "Battery 1 SOC"
+        class: "battery"
+        state_class: "measurement"
+        uom: "%"
+        scale: 1
+        rule: 1
+        registers: [ 0x0608 ]
+        icon: 'mdi:battery'
+      - name: "Battery 1 SOH"
+        class: "battery"
+        state_class: "measurement"
+        uom: "%"
+        scale: 1
+        rule: 1
+        registers: [ 0x0609 ]
+        icon: 'mdi:battery'
+      - name: "Battery 1 Number of Cycles"
+        class: ""
+        state_class: "measurement"
+        uom: "cycle"
+        scale: 1
+        rule: 1
+        registers: [ 0x060A ]
+        icon: 'mdi:battery'
+      - name: "Battery 2 Voltage"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [ 0x060B ]
+        icon: 'mdi:battery'
+      - name: "Battery 2 Current"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.01
+        rule: 2
+        registers: [ 0x060C ]
+        icon: 'mdi:current-dc'
+      - name: "Battery 2 Power"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x060D ]
+        icon: 'mdi:battery-charging'
+      - name: "Battery 2 Temperature"
+        class: "temperature"
+        state_class: "measurement"
+        uom: "°C"
+        scale: 1
+        rule: 2
+        registers: [ 0x060E ]
+        icon: 'mdi:battery'
+      - name: "Battery 2 SOC"
+        class: "battery"
+        state_class: "measurement"
+        uom: "%"
+        scale: 1
+        rule: 1
+        registers: [ 0x060F ]
+        icon: 'mdi:battery'
+      - name: "Battery 2 SOH"
+        class: "battery"
+        state_class: "measurement"
+        uom: "%"
+        scale: 1
+        rule: 1
+        registers: [ 0x0610 ]
+        icon: 'mdi:battery'
+      - name: "Battery 2 Number of Cycles"
+        class: ""
+        state_class: "measurement"
+        uom: "cycle"
+        scale: 1
+        rule: 1
+        registers: [ 0x0611 ]
+        icon: 'mdi:battery'
+
+  - group: GridAC
+    items:
+      - name: "Grid Frequency"
+        class: "current"
+        state_class: "measurement"
+        uom: "Hz"
+        scale: 0.01
+        rule: 1
+        registers: [ 0x0484 ]
+        icon: 'mdi:home-lightning-bolt'
+      - name: "ActivePower_Output_Total"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x0485 ]
+        icon: 'mdi:home-lightning-bolt'
+      - name: "ReactivePower_Output_Total"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x0486 ]
+        icon: 'mdi:home-lightning-bolt'
+      - name: "ApparentPower_Output_Total"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x0487 ]
+        icon: 'mdi:home-lightning-bolt'
+      - name: "ActivePower_PCC_Total"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x0488 ]
+        icon: 'mdi:home-lightning-bolt'
+      - name: "ReactivePower_PCC_Total"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x0489 ]
+        icon: 'mdi:home-lightning-bolt'
+      - name: "ApparentPower_PCC_Total"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x048A ]
+        icon: 'mdi:home-lightning-bolt'
+      - name: "Voltage_Phase_R"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [ 0x048D ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "Current_Output_R"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.01
+        rule: 1
+        registers: [ 0x048E ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ActivePower_Output_R"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x048F ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ReactivePower_Output_R"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x0490 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "PowerFactor_Output_R"
+        class: "powerfactor"
+        state_class: "measurement"
+        uom: "p.u."
+        scale: 0.001
+        rule: 2
+        registers: [ 0x0491 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "Current_PCC_R"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.01
+        rule: 1
+        registers: [ 0x0492 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ActivePower_PCC_R"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x0493 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ReactivePower_PCC_R"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x0494 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "PowerFactor_PCC_R"
+        class: "powerfactor"
+        state_class: "measurement"
+        uom: "p.u."
+        scale: 0.001
+        rule: 2
+        registers: [ 0x0495 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "Voltage_Phase_S"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [ 0x0498 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "Current_Output_S"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.01
+        rule: 1
+        registers: [ 0x0499 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ActivePower_Output_S"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x049A ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ReactivePower_Output_S"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x049B ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "PowerFactor_Output_S"
+        class: "powerfactor"
+        state_class: "measurement"
+        uom: "p.u."
+        scale: 0.001
+        rule: 2
+        registers: [ 0x049C ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "Current_PCC_S"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.01
+        rule: 1
+        registers: [ 0x049D ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ActivePower_PCC_S"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x049E ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ReactivePower_PCC_S"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x049F ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "PowerFactor_PCC_S"
+        class: "powerfactor"
+        state_class: "measurement"
+        uom: "p.u."
+        scale: 0.001
+        rule: 2
+        registers: [ 0x04A0 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "Voltage_Phase_T"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [ 0x04A3 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "Current_Output_T"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.01
+        rule: 1
+        registers: [ 0x04A4 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ActivePower_Output_T"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x04A5 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ReactivePower_Output_T"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x04A6 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "PowerFactor_Output_T"
+        class: "powerfactor"
+        state_class: "measurement"
+        uom: "p.u."
+        scale: 0.001
+        rule: 2
+        registers: [ 0x04A7 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "Current_PCC_T"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.01
+        rule: 1
+        registers: [ 0x04A8 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ActivePower_PCC_T"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x04A9 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ReactivePower_PCC_T"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x04AA ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "PowerFactor_PCC_T"
+        class: "powerfactor"
+        state_class: "measurement"
+        uom: "p.u."
+        scale: 0.001
+        rule: 2
+        registers: [ 0x04AB ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ActivePower_PV_Ext"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 1
+        registers: [ 0x04AE ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ActivePower_Load_Sys"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x04AF ]
+        icon: 'mdi:lightning-bolt-outline'
+
+  - group: GridEPS
+    items:
+      - name: "ActivePower_Load_Total_EPS"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x0504 ]
+        icon: 'mdi:home-lightning-bolt'
+      - name: "ReactivePower_Load_Total_EPS"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x0505 ]
+        icon: 'mdi:home-lightning-bolt'
+      - name: "ApparentPower_Load_Total_EPS"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x0506 ]
+        icon: 'mdi:home-lightning-bolt'
+      - name: "Frequency_Output_EPS"
+        class: "current"
+        state_class: "measurement"
+        uom: "Hz"
+        scale: 0.01
+        rule: 1
+        registers: [ 0x0507 ]
+        icon: 'mdi:home-lightning-bolt'
+      - name: "Voltage_Output_R_EPS"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [ 0x050A ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "Current_Load_R_EPS"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.01
+        rule: 1
+        registers: [ 0x050B ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ActivePower_Load_R_EPS"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x050C ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ReactivePower_Load_R_EPS"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x050D ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ApparentPower_Load_R_EPS"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x050E ]
+        icon: 'mdi:home-lightning-bolt'
+      - name: "LoadPeakRatio_R_EPS"
+        class: "powerfactor"
+        state_class: "measurement"
+        uom: "p.u."
+        scale: 0.01
+        rule: 2
+        registers: [ 0x050F ]
+        icon: 'mdi:lightning-bolt-outline'  
+      - name: "Voltage_Output_S_EPS"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [ 0x0512 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "Current_Load_S_EPS"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.01
+        rule: 1
+        registers: [ 0x0513 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ActivePower_Load_S_EPS"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x0514 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ReactivePower_Load_S_EPS"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x0515 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ApparentPower_Load_S_EPS"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x0516 ]
+        icon: 'mdi:home-lightning-bolt'
+      - name: "LoadPeakRatio_S_EPS"
+        class: "powerfactor"
+        state_class: "measurement"
+        uom: "p.u."
+        scale: 0.01
+        rule: 2
+        registers: [ 0x0517 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "Voltage_Output_T_EPS"
+        class: "voltage"
+        state_class: "measurement"
+        uom: "V"
+        scale: 0.1
+        rule: 1
+        registers: [ 0x051A ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "Current_Load_T_EPS"
+        class: "current"
+        state_class: "measurement"
+        uom: "A"
+        scale: 0.01
+        rule: 1
+        registers: [ 0x051B ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ActivePower_Load_T_EPS"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x051C ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ReactivePower_Load_T_EPS"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x051D ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "ApparentPower_Load_T_EPS"
+        class: "power"
+        state_class: "measurement"
+        uom: "W"
+        scale: 10
+        rule: 2
+        registers: [ 0x051E ]
+        icon: 'mdi:home-lightning-bolt'
+      - name: "LoadPeakRatio_T_EPS"
+        class: "powerfactor"
+        state_class: "measurement"
+        uom: "p.u."
+        scale: 0.01
+        rule: 2
+        registers: [ 0x051F ]
+        icon: 'mdi:lightning-bolt-outline'
+
+  - group: Generation
+    items:
+      - name: "Daily PV Generation"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.01
+        rule: 3
+        registers: [ 0x0685,0x0684 ]
+        icon: 'mdi:solar-power'
+      - name: "Total PV Generation"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.1
+        rule: 3
+        registers: [ 0x0687,0x0686 ]
+        icon: 'mdi:solar-power'
+      - name: "Daily Load Consumption"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.01
+        rule: 3
+        registers: [ 0x0689,0x0688 ]
+        icon: 'mdi:lightning-bolt-outline'
+      - name: "Total Load Consumption"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.1
+        rule: 3
+        registers: [ 0x068B,0x068A ]
+        icon: 'mdi:solar-power'
+      - name: "Daily Energy Bought"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.01
+        rule: 3
+        registers: [ 0x068D,0x068C ]
+        icon: 'mdi:transmission-tower-export'
+      - name: "Total Energy Bought"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.1
+        rule: 3
+        registers: [ 0x068F,0x068E ]
+        icon: 'mdi:transmission-tower-export'
+      - name: "Daily Energy Sold"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.01
+        rule: 3
+        registers: [ 0x0691,0x0690 ]
+        icon: 'mdi:transmission-tower-import'
+      - name: "Total Energy Sold"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.1
+        rule: 3
+        registers: [ 0x0693,0x0692 ]
+        icon: 'mdi:transmission-tower-import'
+      - name: "Daily Battery Charge"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.01
+        rule: 3
+        registers: [ 0x0695,0x0694 ]
+        icon: 'mdi:battery-plus'
+      - name: "Total Battery Charge"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.1
+        rule: 3
+        registers: [ 0x0697,0x0696 ]
+        icon: 'mdi:battery-plus'
+      - name: "Daily Battery Discharge"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.01
+        rule: 3
+        registers: [ 0x0699,0x0698 ]
+        icon: 'mdi:battery-minus'
+      - name: "Total Battery Discharge"
+        class: "energy"
+        state_class: "total_increasing"
+        uom: "kWh"
+        scale: 0.1
+        rule: 3
+        registers: [ 0x069b,0x069A ]
+        icon: 'mdi:battery-minus'
+
+  - group: Alert
+    items:
+      - name: "Alert"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 6
+        registers: [ 0x0405,0x0406,0x0407,0x0408,0x0409,0x040A,0x040B,0x040C,0x040D,0x040E,0x040F,0x0410 ]
+
+      - name: "Fault 1"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 1
+        registers: [ 0x0405 ]
+        isstr: true
+        icon: 'mdi:wrench'
+        lookup:
+          - key: 0
+            value: "No error"
+          - key: 1
+            value: "ID01 Grid Over Voltage Protection"
+          - key: 2
+            value: "ID02 Grid Under Voltage Protection"
+          - key: 4
+            value: "ID03 Grid Over Frequency Protection"
+          - key: 8
+            value: "ID04 Grid Under Frequency Protection"
+          - key: 16
+            value: "ID05 Leakage current fault"
+          - key: 32
+            value: "ID06 High penetration error"
+          - key: 64
+            value: "ID07 Low penetration error"
+          - key: 128
+            value: "ID08 Islanding error"
+          - key: 256
+            value: "ID09 Grid voltage transient value overvoltage 1"
+          - key: 512
+            value: "ID10 Grid voltage transient value overvoltage 2"
+          - key: 1024
+            value: "ID11 Grid line voltage error"
+          - key: 2048
+            value: "ID12 Inverter voltage error"
+          - key: 4096
+            value: "ID13 Anti-backflow overload"
+          - key: 8192
+            value: "ID14"
+          - key: 16384
+            value: "ID15"
+          - key: 32768
+            value: "ID16"
+      - name: "Fault 2"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        isstr: true
+        registers: [ 0x0406 ]
+        lookup:
+          - key: 0
+            value: "No error"
+          - key: 1
+            value: "ID17 Grid current sampling error"
+          - key: 2
+            value: "ID18 Grid current DC component sampling error (AC side)"
+          - key: 4
+            value: "ID19 Grid voltage sampling error (DC side)"
+          - key: 8
+            value: "ID20 Grid voltage sampling error (AC side)"
+          - key: 16
+            value: "ID21 Leakage current sampling error (DC side)"
+          - key: 32
+            value: "ID22 Leakage current sampling error (AC side)"
+          - key: 64
+            value: "ID23 Load voltage DC component sampling error"
+          - key: 128
+            value: "ID24 DC input current sampling error"
+          - key: 256
+            value: "ID25 DC component sampling error of grid current (DC side)"
+          - key: 512
+            value: "ID26 DC input branch current sampling error"
+          - key: 1024
+            value: "ID27"
+          - key: 2048
+            value: "ID28"
+          - key: 4096
+            value: "ID29 Leakage current consistency error"
+          - key: 8192
+            value: "ID30 Grid voltage consistency error"
+          - key: 16384
+            value: "ID31 DCI consistency error"
+          - key: 32768
+            value: "ID32"
+      - name: "Fault 3"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        isstr: true
+
+        registers: [ 0x0407 ]
+        lookup:
+          - key: 0
+            value: "No error"
+          - key: 1
+            value: 'ID033 SPI communication error (DC side)'
+          - key: 2
+            value: 'ID034 SPI communication error (AC side)'
+          - key: 4
+            value: 'ID035 Chip error (DC side)'
+          - key: 8
+            value: 'ID036 Chip error (AC side)'
+          - key: 16
+            value: 'ID037 Auxiliary power error'
+          - key: 32
+            value: 'ID038 Inverter soft start failure'
+          - key: 64
+            value: 'ID039 '
+          - key: 128
+            value: 'ID040 '
+          - key: 256
+            value: 'ID041 Relay detection failure'
+          - key: 512
+            value: 'ID042 Low insulation impedance'
+          - key: 1024
+            value: 'ID043 Grounding error'
+          - key: 2048
+            value: 'ID044 Input mode setting error'
+          - key: 4096
+            value: 'ID045 CT error'
+          - key: 8192
+            value: 'ID046 Input reversal error'
+          - key: 16384
+            value: 'ID047 Parallel error'
+          - key: 32768
+            value: 'ID048 Serial number error'
+      - name: "Fault 4"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        registers: [ 0x0408 ]
+        isstr: true
+        lookup:
+          - key: 0
+            value: "No error"
+          - key: 1
+            value: 'ID049 Battery temperature protection'
+          - key: 2
+            value: 'ID050 Heat sink 1 temperature protection'
+          - key: 4
+            value: 'ID051 Heater 2 temperature protection'
+          - key: 8
+            value: 'ID052 Heater 3 temperature protection'
+          - key: 16
+            value: 'ID053 Heatsink 4 temperature protection'
+          - key: 32
+            value: 'ID054 Heatsink 5 temperature protection'
+          - key: 64
+            value: 'ID055 Radiator 6 temperature protection'
+          - key: 128
+            value: 'ID056 '
+          - key: 256
+            value: 'ID057 Ambient temperature 1 protection'
+          - key: 512
+            value: 'ID058 Ambient temperature 2 protection'
+          - key: 1024
+            value: 'ID059 Module 1 temperature protection'
+          - key: 2048
+            value: 'ID060 Module 2 temperature protection'
+          - key: 4096
+            value: 'ID061 Module 3 temperature protection'
+          - key: 8192
+            value: 'ID062 Module temperature difference is too large'
+          - key: 16384
+            value: 'ID063 '
+          - key: 32768
+            value: 'ID064 '
+      - name: "Fault 5"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        registers: [ 0x0409 ]
+        isstr: true
+        lookup:
+          - key: 0
+            value: "No error"
+          - key: 1
+            value: 'ID065 Bus voltage RMS unbalance'
+          - key: 2
+            value: 'ID066 Bus voltage transient   value unbalance'
+          - key: 4
+            value: 'ID067 Undervoltage of busbar during grid connection'
+          - key: 8
+            value: 'ID068 Bus bar low voltage'
+          - key: 16
+            value: 'ID069 PV overvoltage'
+          - key: 32
+            value: 'ID070 Battery over-voltage'
+          - key: 64
+            value: 'ID071 LLCBus overvoltage protection'
+          - key: 128
+            value: 'ID072 Inverter bus voltage RMS software overvoltage'
+          - key: 256
+            value: 'ID073 Inverter bus voltage transient value software overvoltage'
+          - key: 512
+            value: 'ID074 Flying Cross Capacitor Overvoltage Protection'
+          - key: 1024
+            value: 'ID075 Flying Cross capacitor undervoltage protection'
+          - key: 2048
+            value: 'ID076 '
+          - key: 4096
+            value: 'ID077 '
+          - key: 8192
+            value: 'ID078 '
+          - key: 16384
+            value: 'ID079 '
+          - key: 32768
+            value: 'ID080 '
+      - name: "Fault 6"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        isstr: true
+        registers: [ 0x040A ]
+        lookup:
+          - key: 0
+            value: "No error"
+          - key: 1
+            value: 'ID081 Battery overcurrent software protection'
+          - key: 2
+            value: 'ID082 Dci overcurrent protection'
+          - key: 4
+            value: 'ID083 Output transient current protection'
+          - key: 8
+            value: 'ID084 BuckBoost software overcurrent'
+          - key: 16
+            value: 'ID085 Output RMS current protection'
+          - key: 32
+            value: 'ID086 PV instantaneous current overcurrent software protection'
+          - key: 64
+            value: 'ID087 PV parallel uneven current'
+          - key: 128
+            value: 'ID088 Output current unbalance'
+          - key: 256
+            value: 'ID089 PV software overcurrent protection'
+          - key: 512
+            value: 'ID090 Balanced circuit overcurrent protection'
+          - key: 1024
+            value: 'ID091 Resonance protection'
+          - key: 2048
+            value: 'ID092 '
+          - key: 4096
+            value: 'ID093 '
+          - key: 8192
+            value: 'ID094 '
+          - key: 16384
+            value: 'ID095 '
+          - key: 32768
+            value: 'ID096 '
+      - name: "Fault 7"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        isstr: true
+        registers: [ 0x040B ]
+        lookup:
+          - key: 0
+            value: "No error"
+          - key: 1
+            value: 'ID097 LLC bus hardware overvoltage'
+          - key: 2
+            value: 'ID098 Inverter bus hardware overvoltage'
+          - key: 4
+            value: 'ID099 BuckBoost hardware overcurrent'
+          - key: 8
+            value: 'ID100 Battery hardware overcurrent'
+          - key: 16
+            value: 'ID101 '
+          - key: 32
+            value: 'ID102 PV hardware overcurrent'
+          - key: 64
+            value: 'ID103 AC output hardware overcurrent'
+          - key: 128
+            value: 'ID104 '
+          - key: 256
+            value: 'ID105 Power meter error'
+          - key: 512
+            value: 'ID106 Serial number model error'
+          - key: 1024
+            value: 'ID107 '
+          - key: 2048
+            value: 'ID108 '
+          - key: 4096
+            value: 'ID109 '
+          - key: 8192
+            value: 'ID110 Overload protection 1'
+          - key: 16384
+            value: 'ID111 Overload protection 2'
+          - key: 32768
+            value: 'ID112 Overload protection 3'
+
+      - name: "Fault 8"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        registers: [ 0x040C ]
+        isstr: true
+        lookup:
+          - key: 0
+            value: "No error"
+          - key: 1
+            value: 'ID113 Overtemperature derating'
+          - key: 2
+            value: 'ID114 Frequency down load'
+          - key: 4
+            value: 'ID115 Frequency loading'
+          - key: 8
+            value: 'ID116 Voltage down load'
+          - key: 16
+            value: 'ID117 Voltage loading'
+          - key: 32
+            value: 'ID118 '
+          - key: 64
+            value: 'ID119 '
+          - key: 128
+            value: 'ID120 '
+          - key: 256
+            value: 'ID121 Lightning protection failure (DC)'
+          - key: 512
+            value: 'ID122 Lightning protection failure (AC)'
+          - key: 1024
+            value: 'ID123 '
+          - key: 2048
+            value: 'ID124 Battery low voltage protection'
+          - key: 4096
+            value: 'ID125 Battery low voltage shutdown'
+          - key: 8192
+            value: 'ID126 Battery low voltage pre-alarm'
+          - key: 16384
+            value: 'ID127 '
+          - key: 32768
+            value: 'ID128 '
+      - name: "Fault 9"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        isstr: true
+
+        registers: [ 0x040D ]
+        lookup:
+          - key: 0
+            value: "No error"
+          - key: 1
+            value: 'ID129 Output hardware overcurrent permanent fault'
+          - key: 2
+            value: 'ID130 Bus overvoltage permanent fault'
+          - key: 4
+            value: 'ID131 Bus hardware over-voltage permanent fault'
+          - key: 8
+            value: 'ID132 PV uneven flow permanent fault'
+          - key: 16
+            value: 'ID133 Battery overcurrent permanent fault in EPS mode'
+          - key: 32
+            value: 'ID134 Output transient overcurrent permanent fault'
+          - key: 64
+            value: 'ID135 Output current unbalance permanent fault'
+          - key: 128
+            value: 'ID136 '
+          - key: 256
+            value: 'ID137 Input mode setting error permanent fault'
+          - key: 512
+            value: 'ID138 Input overcurrent permanent fault'
+          - key: 1024
+            value: 'ID139 Input hardware overcurrent permanent fault'
+          - key: 2048
+            value: 'ID140 Relay permanent fault'
+          - key: 4096
+            value: 'ID141 Bus unbalance permanent fault'
+          - key: 8192
+            value: 'ID142 Lightning protection permanent fault - DC side'
+          - key: 16384
+            value: 'ID143 Lightning protection permanent fault - AC side'
+          - key: 32768
+            value: 'ID144 '
+      - name: "Fault 10"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        isstr: true
+        registers: [ 0x040E ]
+        lookup:
+          - key: 0
+            value: "No error"
+          - key: 1
+            value: 'ID145 USB fault'
+          - key: 2
+            value: 'ID146 WIFI fault'
+          - key: 4
+            value: 'ID147 Bluetooth fault'
+          - key: 8
+            value: 'ID148 RTC clock fault'
+          - key: 16
+            value: 'ID149 Communication board EEPROM error'
+          - key: 32
+            value: 'ID150 Communication board FLASH error'
+          - key: 64
+            value: 'ID151 '
+          - key: 128
+            value: 'ID152 Safety regulation version error'
+          - key: 256
+            value: 'ID153 SCI communication error (DC side)'
+          - key: 512
+            value: 'ID154 SCI communication error (AC side)'
+          - key: 1024
+            value: 'ID155 SCI communication error (convergence board side)'
+          - key: 2048
+            value: 'ID156 Software version inconsistency'
+          - key: 4096
+            value: 'ID157 Lithium battery 1 communication error'
+          - key: 8192
+            value: 'ID158 Li-ion battery 2 communication error'
+          - key: 16384
+            value: 'ID159 Lithium battery 3 communication error'
+          - key: 32768
+            value: 'ID160 Lithium battery 4 communication failure'
+      - name: "Fault 11"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        registers: [ 0x040F ]
+        isstr: true
+        lookup:
+          - key: 0
+            value: "No error"
+          - key: 1
+            value: 'ID161 Forced shutdown'
+          - key: 2
+            value: 'ID162 Remote shutdown'
+          - key: 4
+            value: 'ID163 Drms0 shutdown'
+          - key: 8
+            value: 'ID164 '
+          - key: 16
+            value: 'ID165 Remote down load'
+          - key: 32
+            value: 'ID166 Logic interface down load'
+          - key: 64
+            value: 'ID167 Anti-Reverse Flow Downgrade'
+          - key: 128
+            value: 'ID168 '
+          - key: 256
+            value: 'ID169 Fan 1 failure'
+          - key: 512
+            value: 'ID170 Fan 2 failure'
+          - key: 1024
+            value: 'ID171 Fan 3 failure'
+          - key: 2048
+            value: 'ID172 Fan 4 failure'
+          - key: 4096
+            value: 'ID173 Fan 5 failure'
+          - key: 8192
+            value: 'ID174 Fan 6 failure'
+          - key: 16384
+            value: 'ID175 Fan 7 fault'
+          - key: 32768
+            value: 'ID176 Meter communication failure'
+      - name: "Fault 12"
+        class: ""
+        state_class: ""
+        uom: ""
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        registers: [ 0x0410 ]
+        isstr: true
+        lookup:
+          - key: 0
+            value: "No error"
+          - key: 1
+            value: 'ID177 BMS over-voltage alarm'
+          - key: 2
+            value: 'ID178 BMS undervoltage alarm'
+          - key: 4
+            value: 'ID179 BMS high temperature alarm'
+          - key: 8
+            value: 'ID180 BMS low temperature alarm'
+          - key: 16
+            value: 'ID181 BMS charge/discharge overload alarm'
+          - key: 32
+            value: 'ID182 BMS short circuit alarm'
+          - key: 64
+            value: 'ID183 BMS version inconsistency'
+          - key: 128
+            value: 'ID184 BMS CAN version inconsistency'
+          - key: 256
+            value: 'ID185 BMS CAN version is too low'
+          - key: 512
+            value: 'ID186 '
+          - key: 1024
+            value: 'ID187 '
+          - key: 2048
+            value: 'ID188 '
+          - key: 4096
+            value: 'ID189 Arc device communication failure'
+          - key: 8192
+            value: 'ID190 DC arc alarm fault'
+          - key: 16384
+            value: 'ID191 PID repair failed'
+          - key: 32768
+            value: 'ID192 PLC module heartbeat loss'
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_hyd3k-6k-es.yaml b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_hyd3k-6k-es.yaml
new file mode 100644 (file)
index 0000000..1b379df
--- /dev/null
@@ -0,0 +1,1147 @@
+# Configuration file for Sofar HYD3000/4000/5000/6000-ES
+# inverter family.
+
+requests:
+  - start: 0x0200
+    end:  0x0255
+    mb_functioncode: 0x03
+  - start: 0x10B0
+    end: 0x10BC
+    mb_functioncode: 0x04
+
+
+parameters:
+ - group: solar
+   items:
+    - name: "PV Instant Generated PW"
+      class: "energy"
+      state_class: "measurement"
+      uom: "kW"
+      scale: 0.01
+      rule: 1
+      registers: [0x0215]
+      icon: 'mdi:solar-power'
+
+
+    - name: "PV1 Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "kW"
+      scale: 0.01
+      rule: 1
+      registers: [0x0252]
+      icon: 'mdi:solar-power'
+
+    - name: "PV2 Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "kW"
+      scale: 0.01
+      rule: 1
+      registers: [0x0255]
+      icon: 'mdi:solar-power'
+
+    - name: "PV1 Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0250]
+      icon: 'mdi:solar-power'
+
+    - name: "PV2 Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0253]
+      icon: 'mdi:solar-power'
+
+    - name: "PV1 Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.01
+      rule: 1
+      registers: [0x0251]
+      icon: 'mdi:solar-power'
+
+    - name: "PV2 Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.01
+      rule: 1
+      registers: [0x0254]
+      icon: 'mdi:solar-power'
+
+    - name: "Daily Production"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.01
+      rule: 1
+      registers: [0x0218]
+      icon: 'mdi:solar-power'
+
+    - name: "Total Production"
+      class: "energy"
+      state_class: "total"
+      uom: "kWh"
+      scale: 1
+      rule: 3
+      registers: [0x021D,0x021C]
+      icon: 'mdi:solar-power'
+
+    - name: "Total generation time"
+      class: ""
+      state_class: "measurement"
+      uom: "h"
+      scale: 1
+      rule: 3
+      registers: [0x0245,0x0244]
+      icon: 'mdi:clock-outline'
+
+    - name: "Today generation time"
+      class: ""
+      state_class: "total_increasing"
+      uom: "min"
+      scale: 1
+      rule: 1
+      registers: [0x0243]
+      icon: 'mdi:clock-outline'
+
+    - name: "Today Grid Return"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.01
+      rule: 1
+      registers: [0x0219]
+      icon: 'mdi:transmission-tower-export'
+
+    - name: "Today Grid Consumption"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.01
+      rule: 1
+      registers: [0x021A]
+      icon: 'mdi:transmission-tower-import'
+
+    - name: "Today Power Consumption"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.01
+      rule: 1
+      registers: [0x021B]
+      icon: 'mdi:lightning-bolt'
+
+    - name: "Total Grid Return"
+      class: "energy"
+      state_class: "total"
+      uom: "KWh"
+      scale: 1
+      rule: 3
+      registers: [0x021F,0x021E]
+      icon: 'mdi:transmission-tower-export'
+
+    - name: "Total Grid Consumption"
+      class: "Energy"
+      state_class: "total"
+      uom: "KWh"
+      scale: 1
+      rule: 3
+      registers: [0x0221,0x0220]
+      icon: 'mdi:transmission-tower-import'
+
+    - name: "Total Power Consumption"
+      class: "energy"
+      state_class: "total"
+      uom: "KWh"
+      scale: 1
+      rule: 3
+      registers: [0x0223,0x0222]
+      icon: 'mdi:lightning-bolt'
+
+ - group: Output
+   items:
+
+    - name: "Power Consumption"
+      class: ""
+      state_class: ""
+      uom: "KW"
+      scale: 0.01
+      rule: 1
+      registers: [0x0213]
+      icon: ''
+
+#    - name: "Output active power"
+#      class: "power"
+#      state_class: "measurement"
+#      uom: "W"
+#      scale: 10
+#      rule: 1
+#      registers: [0x000C]
+#      icon: 'mdi:home-lightning-bolt'
+
+#    - name: "Output reactive power"
+#      class: ""
+#      state_class: "measurement"
+#      uom: "kVar"
+#      scale: 0.01
+#      rule: 1
+#      registers: [0x000D]
+#      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Grid frequency"
+      class: "frequency"
+      state_class: "measurement"
+      uom: "Hz"
+      scale: 0.01
+      rule: 1
+      registers: [0x020C]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Grid Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0206]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Grid Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.01
+      rule: 2
+      registers: [0x0207]
+      icon: 'mdi:home-lightning-bolt'
+
+
+ - group: batteries
+   items:
+    - name: "Battery Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x020E]
+      icon: 'mdi:battery-charging'
+
+    - name: "Battery Charge / Discharge current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.01
+      rule: 2
+      registers: [0x020F]
+      icon: 'mdi:battery-charging-10'
+
+    - name: "Battery Percentage"
+      class: "battery"
+      state_class: "measurement"
+      uom: "%"
+      scale: 1
+      rule: 1
+      registers: [0x0210]
+      icon: 'mdi:battery'
+
+    - name: "Battery Temperature"
+      class: "temperature"
+      state_class: "measurement"
+      uom: "°C"
+      scale: 1
+      #se non funziona cambia questo in 2
+      rule: 1
+      registers: [0x0211]
+      icon: 'mdi:battery-heart-outline'
+
+    - name: "Battery Daily Charge"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.01
+      rule: 1
+      registers: [0x0224]
+      icon: 'mdi:battery-clock'
+
+    - name: "Battery Total Energy Charged"
+      class: "energy"
+      state_class: "total"
+      uom: "kWh"
+      scale: 1
+      rule: 1
+      registers: [0x0227]
+      icon: 'mdi:battery-clock'
+
+    - name: "Battery Total Energy Dischaged"
+      class: "energy"
+      state_class: "total"
+      uom: "kWh"
+      scale: 1
+      rule: 1
+      registers: [0x0229]
+      icon: 'mdi:battery-clock-outline'
+
+    - name: "Battery Cicles"
+      class: ""
+      state_class: ""
+      uom: "Charges"
+      scale: 1
+      rule: 1
+      registers: [0x022C]
+      icon: 'mdi:battery-check-outline'
+
+    - name: "Battery Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "KW"
+      scale: 0.01
+      rule: 2
+      registers: [0x0237]
+      icon: 'mdi:battery-charging-high'
+
+    - name: "Battery Type"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x10B0]
+      icon: 'mdi:battery'
+
+    - name: "Battery Capacity"
+      class: ""
+      state_class: ""
+      uom: "Ah"
+      scale: 1
+      rule: 1
+      registers: [0x10B1]
+      icon: 'mdi:battery'
+
+    - name: "Battery daily Discharge"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.01
+      rule: 1
+      registers: [0x0225]
+      icon: 'mdi:battery'
+
+    - name: "Battery Total Charge"
+      class: "energy"
+      state_class: "total"
+      uom: "KWh"
+      scale: 1
+      rule: 3
+      registers: [0x0227,0x0226]
+      icon: 'mdi:battery'
+
+    - name: "Battery Total Discharge"
+      class: "energy"
+      state_class: "total"
+      uom: "KWh"
+      scale: 1
+      rule: 3
+      registers: [0x0229,0x0228]
+      icon: 'mdi:battery'
+
+    - name: "Max Charge Voltage"
+      class: ""
+      state_class: ""
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x10B3]
+      icon: 'mdi:battery'
+
+    - name: "Max Charge Current"
+      class: ""
+      state_class: ""
+      uom: "A"
+      scale: 0.01
+      rule: 1
+      registers: [0x10B4]
+      icon: 'mdi:battery'
+
+    - name: "Over Voltage Protection"
+      class: ""
+      state_class: ""
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x10B5]
+      icon: 'mdi:battery'
+
+    - name: "Min Discharge Voltage"
+      class: ""
+      state_class: ""
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x10B6]
+      icon: 'mdi:battery'
+
+    - name: "Max Discharge Current"
+      class: ""
+      state_class: ""
+      uom: "A"
+      scale: 0.01
+      rule: 1
+      registers: [0x10B7]
+      icon: 'mdi:battery'
+
+    - name: "Undervoltage Protection"
+      class: ""
+      state_class: ""
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x10B8]
+      icon: 'mdi:battery'
+
+    - name: "Discharge Depth"
+      class: ""
+      state_class: ""
+      uom: "%"
+      scale: 1
+      rule: 1
+      registers: [0x10B9]
+      icon: 'mdi:battery'
+
+    - name: "Periods Of Discharge Time"
+      class: ""
+      state_class: ""
+      uom: "h"
+      scale: 1
+      rule: 1
+      registers: [0x10BA]
+      icon: 'mdi:battery'
+
+    - name: "Empty Battery Voltage"
+      class: ""
+      state_class: ""
+      uom: "V"
+      scale: 0.01
+      rule: 1
+      registers: [0x10BB]
+      icon: 'mdi:battery'
+
+    - name: "Full Battery Voltage"
+      class: ""
+      state_class: ""
+      uom: "V"
+      scale: 0.01
+      rule: 1
+      registers: [0x10BC]
+      icon: 'mdi:battery'
+
+ - group: Inverter
+   items:
+    - name: "Inverter status"
+      class: ""
+      state_class: "measurement"
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0200]
+      lookup:
+      -  key: 0
+         value: "Stand-by"
+      -  key: 1
+         value: "Self-Checking"
+      -  key: 2
+         value: "Normal"
+      -  key: 3
+         value: "Discharging Check State"
+      -  key: 4
+         value: "Discharging State"
+      -  key: 5
+         value: "EPS State"
+      -  key: 6
+         value: "Fault State"
+      -  key: 7
+         value: "Permanent State"
+      icon: 'mdi:state-machine'
+
+    - name: "Inverter module temperature"
+      class: "temperature"
+      uom: "°C"
+      scale: 1
+      rule: 2
+      registers: [0x0239]
+      icon: 'mdi:thermometer'
+
+    - name: "Inverter inner temperature"
+      class: "temperature"
+      state_class: "measurement"
+      uom: "°C"
+      scale: 1
+      rule: 2
+      registers: [0x0238]
+      icon: 'mdi:thermometer'
+
+    - name: "Inverter bus voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 2
+      registers: [0x022D]
+      icon: 'mdi:home-lightning-bolt'
+
+#    - name: "PV1 voltage sample by slave CPU"
+#      class: "voltage"
+#      state_class: "measurement"
+#      uom: "V"
+#      scale: 0.1
+#      rule: 1
+#      registers: [0x001E]
+#      icon: 'mdi:home-lightning-bolt'
+
+#    - name: "PV1 current sample by slave CPU"
+#      class: "current"
+#      state_class: "measurement"
+#      uom: "A"
+#      scale: 0.1
+#      rule: 1
+#      registers: [0x001F]
+#      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Countdown time"
+      class: ""
+      state_class: "measurement"
+      uom: "s"
+      scale: 1
+      rule: 1
+      registers: [0x022A]
+      icon: ''
+
+#    - name: "Input mode"
+#      class: ""
+#      state_class: ""
+#      uom: ""
+#      scale: 1
+#      rule: 1
+#      registers: [0x0022]
+#      icon: ''
+
+    - name: "Communication Board inner message"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0242]
+      icon: ''
+
+    - name: "Insulation of PV1+ to ground"
+      class: ""
+      state_class: "measurement"
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0246]
+      icon: ''
+
+    - name: "Insulation of PV2+ to ground"
+      class: ""
+      state_class: "measurement"
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0247]
+      icon: ''
+
+    - name: "Insulation of PV- to ground"
+      class: ""
+      state_class: "measurement"
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0248]
+      icon: ''
+
+    - name: "Country"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x023A]
+      lookup:
+      -  key: 0
+         value: "Germany"
+      -  key: 1
+         value: "CEI0-21 Internal"
+      -  key: 2
+         value: "Australia"
+      -  key: 3
+         value: "Spain RD1699"
+      -  key: 4
+         value: "Turkey"
+      -  key: 5
+         value: "Denmark"
+      -  key: 6
+         value: "Greece"
+      -  key: 7
+         value: "Netherland"
+      -  key: 8
+         value: "Belgium"
+      -  key: 9
+         value: "UK-G59"
+      -  key: 10
+         value: "China"
+      -  key: 11
+         value: "France"
+      -  key: 12
+         value: "Poland"
+      -  key: 13
+         value: "Germany BDEW"
+      -  key: 14
+         value: "Germany VDE0126"
+      -  key: 15
+         value: "Italy CEI0-16"
+      -  key: 16
+         value: "UK-G83"
+      -  key: 17
+         value: "Greece Islands"
+      -  key: 18
+         value: "EU EN50438"
+      -  key: 19
+         value: "EU EN61727"
+      -  key: 20
+         value: "Korea"
+      -  key: 21
+         value: "Sweden"
+      -  key: 22
+         value: "Europe General"
+      -  key: 23
+         value: "CEI0-21 External"
+      -  key: 24
+         value: "Cyprus"
+      -  key: 25
+         value: "India"
+      -  key: 26
+         value: "Philippines"
+      -  key: 27
+         value: "New Zeland"
+      -  key: 28
+         value: "Reserve"
+      -  key: 29
+         value: "Reserve"
+      icon: ''
+
+ - group: Alert
+   items:
+    - name: "Inverter alert message"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x022B]
+      lookup:
+      -  key: 0
+         value: "No error"
+      -  key: 1
+         value: "ID01 The power grid voltage is too high"
+      -  key: 2
+         value: "ID02 The power grid voltage is too low"
+      -  key: 3
+         value: "ID03 The power grid frequency is too high"
+      -  key: 4
+         value: "ID04 The power grid frequency is too low"
+      -  key: 5
+         value: "ID05 The battery voltage is too high"
+      -  key: 7
+         value: "ID07 GridLVRT fault"
+      -  key: 8
+         value: "ID08 The PV voltage is too high"
+      -  key: 9
+         value: "ID09 LLCBus voltage is too high and has triggered hardware protection"
+      -  key: 10
+         value: "ID10 Boost voltage is too high and has triggered hardware protection"
+      -  key: 11
+         value: "ID11 BuckBoost current is too high and has triggered hardware protection"
+      -  key: 12
+         value: "ID12 The battery current is too high and has triggered hardware protection"
+      -  key: 13
+         value: "ID13 The GFCI sampling value between the master DSP and slave DSP is not consistent"
+      -  key: 14
+         value: "ID14 The PV current is too high and has triggered hardware protection"
+      -  key: 15
+         value: "ID15 The grid current is too high and has triggered hardware protection"
+      -  key: 16
+         value: "ID16 Input current is not balanced"
+      -  key: 17
+         value: "ID17 Grid current sampling error"
+      -  key: 18
+         value: "ID18 DCI sampling error"
+      -  key: 19
+         value: "ID19 Grid voltage sampling error"
+      -  key: 20
+         value: "ID20 GFCI sampling error"
+      -  key: 21
+         value: "ID21 Master chip fault"
+      -  key: 22
+         value: "ID22 Auxiliary voltage error"
+      -  key: 25
+         value: "ID25 LLCBus voltage is too high"
+      -  key: 26
+         value: "ID26 Bus voltage is too high and has triggered software protection"
+      -  key: 27
+         value: "ID27 Battery current is too high"
+      -  key: 28
+         value: "ID28 The DCI is too high"
+      -  key: 29
+         value: "ID29 The grid current is too high"
+      -  key: 30
+         value: "ID30 Bunk current is too high"
+      -  key: 31
+         value: "ID31 The output current is too high"
+      -  key: 32
+         value: "ID32 The input current is too high"
+      -  key: 33
+         value: "ID33 Incorrect input mode"
+      -  key: 48
+         value: "ID48 The GFCI sampling value between the master DSP and slave DSP is not consistent"
+      -  key: 49
+         value: "ID49 The grid voltage sampling value between the master DSP and slave DSP is no consistent"
+      -  key: 50
+         value: "ID50 The grid frequency sampling value between the master DSP and slave DSP is no consistent"
+      -  key: 51
+         value: "ID51 The DCI sampling value between the master DSP and slave DSP is no consistent"
+      -  key: 52
+         value: "ID52 HYD-ES inverter can't communicate with Lithium battery BMS correctly"
+      -  key: 53
+         value: "ID53 SPI communication fault"
+      -  key: 54
+         value: "ID54 SCI communication fault"
+      -  key: 55
+         value: "ID55 Relays fault"
+      -  key: 56
+         value: "ID56 Insulation resistance is too low"
+      -  key: 57
+         value: "ID57 Battery temperature is too high"
+      -  key: 58
+         value: "ID58 Heat sink temperature is too high"
+      -  key: 59
+         value: "ID59 Environment temperature is too high"
+      -  key: 60
+         value: "ID60 PE connectFault"
+      -  key: 65
+         value: "ID65 The grid current is too high and has caused unrecoverable hardware fault"
+      -  key: 66
+         value: "ID66 The bus voltage is too high and has caused unrecoverable fault"
+      -  key: 67
+         value: "ID67 Unrecoverable fault of battery overcurrent in EPS mode"
+      -  key: 68
+         value: "ID68 The input current is unbalanced and has triggered an unrecoverable fault"
+      -  key: 70
+         value: "ID70 The grid current is too high and has triggered an unrecoverable fault"
+      -  key: 73
+         value: "ID73 The input current is too high and has triggered an unrecoverable fault"
+      -  key: 74
+         value: "ID74 Incorrect input mode"
+      -  key: 75
+         value: "ID75 Unrecoverable EEPROM write"
+      -  key: 76
+         value: "ID76 Unrecoverable EEPROM read"
+      -  key: 77
+         value: "ID77 Relay has triggered permanent fault"
+      -  key: 81
+         value: "ID81 Internal temperature is too high"
+      -  key: 82
+         value: "ID82 AC frequency is too high"
+      -  key: 83
+         value: "ID83 Remote power derate"
+      -  key: 84
+         value: "ID84 Switch OFF HYD series inverter remotely"
+      -  key: 85
+         value: "ID85 SOC <= 1 - DOD or Low battery voltage"
+      -  key: 86
+         value: "ID86 Battery voltage is too low and caused HYD series inverter to switch OFF"
+      -  key: 94
+         value: "ID94 Software version is not consistent"
+      -  key: 95
+         value: "ID95 The communication board EEPROM is faulty"
+      -  key: 96
+         value: "ID96 RTC clock chip fault"
+      -  key: 98
+         value: "ID98 SD card fault"
+      -  key: 100
+         value: "ID100 Battery overcurrent discharging protection"
+      -  key: 101
+         value: "ID101 Discharging short circuit protection"
+      -  key: 102
+         value: "ID102 Battery high voltage protection"
+      -  key: 103
+         value: "ID103 Battery low voltage protection"
+      -  key: 104
+         value: "ID104 Battery high temperature protection while discharging"
+      -  key: 105
+         value: "ID105 Battery high temperature protection while charging"
+      -  key: 106
+         value: "ID106 Battery low temperature protection while discharging"
+      -  key: 107
+         value: "ID107 Battery low temperature protection while charging"
+      icon: 'mdi:alert'
+
+    - name: "Fault 1"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0201]
+      lookup:
+      -  key: 0
+         value: "No error"
+      -  key: 1
+         value: "ID01 Grid Over Voltage Protection"
+      -  key: 2
+         value: "ID02 Grid Under Voltage Protection"
+      -  key: 4
+         value: "ID03 Grid Over Frequency Protection"
+      -  key: 8
+         value: "ID04 Grid Under Frequency Protection"
+      -  key: 16
+         value: "ID05 Battery Over Voltage"
+      -  key: 32
+         value: "ID06"
+      -  key: 64
+         value: "ID07"
+      -  key: 128
+         value: "ID08"
+      -  key: 256
+         value: "ID09 LLCBus Over Voltage Hardware"
+      -  key: 512
+         value: "ID10 Bus Over Voltage Hardware"
+      -  key: 1024
+         value: "ID11 BuckBoost over Current Hardware"
+      -  key: 2048
+         value: "ID12 Battery over Current Hardware"
+      -  key: 4096
+         value: "ID13"
+      -  key: 8192
+         value: "ID14"
+      -  key: 16384
+         value: "ID15 Output Current Hardware"
+      -  key: 32768
+         value: "ID16"
+      icon: 'mdi:wrench'
+
+
+    - name: "Fault 2"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0202]
+      lookup:
+      -  key: 0
+         value: "No error"
+      -  key: 1
+         value: "ID17 Grid current sampling error"
+      -  key: 2
+         value: "ID18 DCI sampling error"
+      -  key: 4
+         value: "ID19 Grid voltage sampling error"
+      -  key: 8
+         value: "ID20"
+      -  key: 16
+         value: "ID21 Main chip fault"
+      -  key: 32
+         value: "ID22 Hardware auxiliary power fault"
+      -  key: 64
+         value: "ID23"
+      -  key: 128
+         value: "ID24"
+      -  key: 256
+         value: "ID25 LLCBus Over Current protection"
+      -  key: 512
+         value: "ID26 Bus over voltage protection"
+      -  key: 1024
+         value: "ID27 Battery Over Current protection"
+      -  key: 2048
+         value: "ID28 Dci Over Current Protection"
+      -  key: 4096
+         value: "ID29 Output over current software"
+      -  key: 8192
+         value: "ID30 Buck Over Current"
+      -  key: 16384
+         value: "ID31 Output over current protection"
+      -  key: 32768
+         value: "ID32 The input current is too high"
+      icon: 'mdi:wrench'
+
+    - name: "Fault 3"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0203]
+      lookup:
+      -  key: 0
+         value: "No error"
+      -  key: 1
+         value: "ID33 Reserved"
+      -  key: 2
+         value: "ID34 Reserved"
+      -  key: 4
+         value: "ID35 Reserved"
+      -  key: 8
+         value: "ID36 Reserved"
+      -  key: 16
+         value: "ID37 Reserved"
+      -  key: 32
+         value: "ID38 Reserved"
+      -  key: 64
+         value: "ID39 Reserved"
+      -  key: 128
+         value: "ID40 Reserved"
+      -  key: 256
+         value: "ID41 Reserved"
+      -  key: 512
+         value: "ID42 Reserved"
+      -  key: 1024
+         value: "ID43 Reserved"
+      -  key: 2048
+         value: "ID44 Reserved"
+      -  key: 4096
+         value: "ID45 Reserved"
+      -  key: 8192
+         value: "ID46 Reserved"
+      -  key: 16384
+         value: "ID47 Reserved"
+      -  key: 32768
+         value: "ID48 Reserved"
+      icon: 'mdi:wrench'
+
+    - name: "Fault 4"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0204]
+      lookup:
+      -  key: 0
+         value: "No error"
+      -  key: 1
+         value: "ID49 Grid voltage sampling value between master and slave DSP vary widely"
+      -  key: 2
+         value: "ID50 Grid frequency sampling value between master and slave DSP vary widely"
+      -  key: 4
+         value: "ID51 DCI sampling value between master and slave DSP vary widely"
+      -  key: 8
+         value: "ID52 GFCI sampling value between master and slave DSP vary widely"
+      -  key: 16
+         value: "ID53 Communication failure between master and slave DSP failure"
+      -  key: 32
+         value: "ID53 Communication failure between slave and communication board"
+      -  key: 64
+         value: "ID55 Relay fault"
+      -  key: 128
+         value: "ID56"
+      -  key: 256
+         value: "ID57 Inverter temp is too high"
+      -  key: 512
+         value: "ID58 Boost temp is too high"
+      -  key: 1024
+         value: "ID59 Environment temp is too high"
+      -  key: 2048
+         value: "ID60"
+      -  key: 4096
+         value: "ID61 Reserved"
+      -  key: 8192
+         value: "ID62 Reserved"
+      -  key: 16384
+         value: "ID63 Reserved"
+      -  key: 32768
+         value: "ID64 Reserved"
+      icon: 'mdi:wrench'
+
+    - name: "Fault 5"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0205]
+      lookup:
+      -  key: 0
+         value: "No error"
+      -  key: 1
+         value: "ID65 Grid current is too high and causes unrecoverable fault"
+      -  key: 2
+         value: "ID66 Bus voltage is too high and causes unrecoverable fault"
+      -  key: 4
+         value: "ID67 EPS Mode Battery Over current,and has cause unrecoverable fault"
+      -  key: 8
+         value: "ID68"
+      -  key: 16
+         value: "ID69"
+      -  key: 32
+         value: "ID70 The Output current is too high,and has cause unrecoverable fault"
+      -  key: 64
+         value: "ID71"
+      -  key: 128
+         value: "ID72 Reserved"
+      -  key: 256
+         value: "ID73 Reserved"
+      -  key: 512
+         value: "ID74"
+      -  key: 1024
+         value: "ID75 Error writing from EEPROM"
+      -  key: 2048
+         value: "ID76 Error reading to EEPROM"
+      -  key: 4096
+         value: "ID77 Relay fauilure causes unrecoverable fault"
+      -  key: 8192
+         value: "ID78 Reserved"
+      -  key: 16384
+         value: "ID79 Reserved"
+      -  key: 32768
+         value: "ID80 Reserved"
+      icon: 'mdi:wrench'
+
+ - group: Other energy
+   items:
+    - name: "Buck Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.01
+      rule: 1
+      registers: [0x022F]
+      icon: 'mdi:lightning-bolt'
+
+    - name: "Grid R Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0230]
+      icon: 'mdi:lightning-bolt'
+
+    - name: "Grid R Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.01
+      rule: 1
+      registers: [0x0231]
+      icon: 'mdi:lightning-bolt'
+
+    - name: "Generation Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.01
+      rule: 1
+      registers: [0x0236]
+      icon: 'mdi:lightning-bolt'
+
+
+    - name: "Charge / Discharge Power"
+      class: ""
+      state_class: ""
+      uom: "KW"
+      scale: 0.01
+      rule: 2
+      registers: [0x020D]
+      icon: ''
+
+    - name: "Feed in / out power"
+      class: ""
+      state_class: ""
+      uom: "KW"
+      scale: 0.01
+      rule: 2
+      registers: [0x0212]
+      icon: ''
+
+    - name: "Input/Output Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "KW"
+      scale: 0.01
+      rule: 2
+      registers: [0x0214]
+      icon: 'mdi:lightning-bolt'
+
+    - name: "Energy Management Model"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x10B2]
+      icon: ''
+
+    - name: "DCI Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "mA"
+      scale: 1
+      rule: 1
+      registers: [0x023B]
+      icon: 'mdi:lightning-bolt'
+
+    - name: "DCI Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x023C]
+      icon: 'mdi:lightning-bolt'
+
+    - name: "Grid S Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0232]
+      icon: 'mdi:lightning-bolt'
+
+    - name: "Grid S Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.01
+      rule: 1
+      registers: [0x0233]
+      icon: 'mdi:lightning-bolt'
+
+    - name: "Grid T voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0234]
+      icon: 'mdi:lightning-bolt'
+
+    - name: "Grid T current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.01
+      rule: 1
+      registers: [0x0235]
+      icon: 'mdi:lightning-bolt'
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_lsw3.yaml b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_lsw3.yaml
new file mode 100644 (file)
index 0000000..7edc3eb
--- /dev/null
@@ -0,0 +1,602 @@
+requests:
+  - start: 0x0000
+    end:  0x0027
+    mb_functioncode: 0x03
+
+
+parameters:
+ - group: solar
+   items: 
+    - name: "PV1 Power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 10
+      rule: 1
+      registers: [0x000A]
+      icon: 'mdi:solar-power'
+
+    - name: "PV2 Power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 10
+      rule: 1
+      registers: [0x000B]
+      icon: 'mdi:solar-power'
+
+    - name: "PV1 Voltage"
+      class: "voltage"
+      state_class: "measurement"      
+      uom: "V"
+      scale: 0.1
+      rule: 1 
+      registers: [0x0006]
+      icon: 'mdi:solar-power'
+
+    - name: "PV2 Voltage"
+      class: "voltage"    
+      state_class: "measurement"      
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0008]
+      icon: 'mdi:solar-power'      
+
+    - name: "PV1 Current"
+      class: "current"
+      state_class: "measurement"      
+      uom: "A"
+      scale: 0.01
+      rule: 1
+      registers: [0x0007]
+      icon: 'mdi:solar-power'      
+
+    - name: "PV2 Current"
+      class: "current"
+      state_class: "measurement"      
+      uom: "A"
+      scale: 0.01
+      rule: 1
+      registers: [0x0009]
+      icon: 'mdi:solar-power'      
+
+    - name: "Daily Production"
+      class: "energy"
+      state_class: "total"      
+      uom: "kWh"
+      scale: 0.01
+      rule: 1
+      registers: [0x0019]
+      icon: 'mdi:solar-power'      
+
+    - name: "Total Production"
+      class: "energy"
+      state_class: "total_increasing"      
+      uom: "kWh"
+      scale: 1
+      rule: 3
+      registers: [0x0016,0x0015]
+      icon: 'mdi:solar-power'      
+
+    - name: "Total generation time"
+      class: ""
+      state_class: "measurement"      
+      uom: "h"
+      scale: 1
+      rule: 3
+      registers: [0x0018,0x0017]
+      icon: 'mdi:clock-outline'   
+
+    - name: "Today generation time"
+      class: ""
+      state_class: "measurement"      
+      uom: "min"
+      scale: 1
+      rule: 1
+      registers: [0x001A]
+      icon: 'mdi:clock-outline'      
+
+ - group: Output
+   items: 
+    - name: "Output active power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 10
+      rule: 1
+      registers: [0x000C]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Output reactive power"
+      class: ""
+      state_class: "measurement"      
+      uom: "kVar"
+      scale: 0.01
+      rule: 1
+      registers: [0x000D]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Grid frequency"
+      class: "frequency"
+      state_class: "measurement"      
+      uom: "Hz"
+      scale: 0.01
+      rule: 1
+      registers: [0x000E]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "L1 Voltage"
+      class: "voltage"
+      state_class: "measurement"      
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x000F]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "L1 Current"
+      class: "current"
+      state_class: "measurement"      
+      uom: "A"
+      scale: 0.01
+      rule: 1
+      registers: [0x0010]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "L2 Voltage"
+      class: "voltage"
+      state_class: "measurement"      
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0011]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "L2 Current"
+      class: "current"
+      state_class: "measurement"      
+      uom: "A"
+      scale: 0.01
+      rule: 1
+      registers: [0x0012]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "L3 Voltage"
+      class: "voltage"
+      state_class: "measurement"      
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0013]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "L3 Current"
+      class: "current"
+      state_class: "measurement"      
+      uom: "A"
+      scale: 0.01
+      rule: 1
+      registers: [0x0014]
+      icon: 'mdi:home-lightning-bolt'
+
+ - group: Inverter
+   items: 
+    - name: "Inverter status"
+      class: ""
+      state_class: "measurement"      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0000]
+      lookup: 
+      -  key: 0
+         value: "Stand-by"
+      -  key: 1
+         value: "Self-checking"
+      -  key: 2
+         value: "Normal"
+      -  key: 3
+         value: "FAULT"
+      -  key: 4
+         value: "Permanent"
+      icon: 'mdi:wrench'         
+
+    - name: "Inverter module temperature"
+      class: "temperature"
+      uom: "°C"
+      scale: 1
+      rule: 1
+      registers: [0x001B]
+      icon: 'mdi:thermometer'
+
+    - name: "Inverter inner temperature"
+      class: "temperature"
+      state_class: "measurement"      
+      uom: "°C"
+      scale: 1
+      rule: 1
+      registers: [0x001C]
+      icon: 'mdi:thermometer'      
+
+    - name: "Inverter bus voltage"
+      class: "voltage"
+      state_class: "measurement"      
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x001D]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "PV1 voltage sample by slave CPU"
+      class: "voltage"
+      state_class: "measurement"      
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x001E]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "PV1 current sample by slave CPU"
+      class: "current"
+      state_class: "measurement"      
+      uom: "A"
+      scale: 0.1
+      rule: 1
+      registers: [0x001F]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Countdown time"
+      class: ""
+      state_class: "measurement"      
+      uom: "s"
+      scale: 1
+      rule: 1
+      registers: [0x0020]
+      icon: ''
+
+    - name: "Inverter alert message"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0021]
+      icon: ''
+
+    - name: "Input mode"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0022]
+      icon: ''
+
+    - name: "Communication Board inner message"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0023]
+      icon: ''
+
+    - name: "Insulation of PV1+ to ground"
+      class: ""
+      state_class: "measurement"      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0024]
+      icon: ''
+
+    - name: "Insulation of PV2+ to ground"
+      class: ""
+      state_class: "measurement"      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0025]
+      icon: ''
+
+    - name: "Insulation of PV- to ground"
+      class: ""
+      state_class: "measurement"      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0026]
+      icon: ''
+
+    - name: "Country"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0027]
+      lookup: 
+      -  key: 0
+         value: "Germany"
+      -  key: 1
+         value: "CEI0-21 Internal"
+      -  key: 2
+         value: "Australia"
+      -  key: 3
+         value: "Spain RD1699"
+      -  key: 4
+         value: "Turkey"
+      -  key: 5
+         value: "Denmark"
+      -  key: 6
+         value: "Greece"
+      -  key: 7
+         value: "Netherland"
+      -  key: 8
+         value: "Belgium"
+      -  key: 9
+         value: "UK-G59"
+      -  key: 10
+         value: "China"
+      -  key: 11
+         value: "France"
+      -  key: 12
+         value: "Poland"
+      -  key: 13
+         value: "Germany BDEW"
+      -  key: 14
+         value: "Germany VDE0126"
+      -  key: 15
+         value: "Italy CEI0-16"
+      -  key: 16
+         value: "UK-G83"
+      -  key: 17
+         value: "Greece Islands"
+      -  key: 18
+         value: "EU EN50438"
+      -  key: 19
+         value: "EU EN61727"
+      -  key: 20
+         value: "Korea"
+      -  key: 21
+         value: "Sweden"
+      -  key: 22
+         value: "Europe General"
+      -  key: 23
+         value: "CEI0-21 External"
+      -  key: 24
+         value: "Cyprus"
+      -  key: 25
+         value: "India"
+      -  key: 26
+         value: "Philippines"
+      -  key: 27
+         value: "New Zeland"
+      -  key: 28
+         value: "Reserve"
+      -  key: 29
+         value: "Reserve"
+      icon: ''
+
+ - group: Alert
+   items: 
+    - name: "Fault 1"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0001]
+      lookup: 
+      -  key: 0
+         value: "No error"
+      -  key: 1
+         value: "ID01 Grid Over Voltage Protection"
+      -  key: 2
+         value: "ID02 Grid Under Voltage Protection"
+      -  key: 4
+         value: "ID03 Grid Over Frequency Protection"
+      -  key: 8
+         value: "ID04 Grid Under Frequency Protection"
+      -  key: 16
+         value: "ID05 PV Under Voltage Protection"
+      -  key: 32
+         value: "ID06 Grid Low Voltage Ride through"
+      -  key: 64
+         value: "ID07"
+      -  key: 128
+         value: "ID08"
+      -  key: 256
+         value: "ID09 PV Over Voltage Protection"
+      -  key: 512
+         value: "ID10 PV Input Current Unbalanced"
+      -  key: 1024
+         value: "ID11 PV Input Mode wrong configuration"
+      -  key: 2048
+         value: "ID12 Ground-Fault circuit interrupters fault"
+      -  key: 4096
+         value: "ID13 Phase sequence fault"
+      -  key: 8192
+         value: "ID14 Hardware boost over current protection"
+      -  key: 16384
+         value: "ID15 Hardware AC over current protection"
+      -  key: 32768
+         value: "ID16 Grid current is too high"
+      icon: 'mdi:wrench'  
+
+    - name: "Fault 2"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0002]
+      lookup: 
+      -  key: 0
+         value: "No error"
+      -  key: 1
+         value: "ID17 Grid current sampling error"
+      -  key: 2
+         value: "ID18 DCI sampling error"
+      -  key: 4
+         value: "ID19 Grid voltage sampling error"
+      -  key: 8
+         value: "ID20 GFCI device sampling error"
+      -  key: 16
+         value: "ID21 Main chip fault"
+      -  key: 32
+         value: "ID22 Hardware auxiliary power fault"
+      -  key: 64
+         value: "ID23 Bus voltage zero fault"
+      -  key: 128
+         value: "ID24 Output current not balanced"
+      -  key: 256
+         value: "ID25 Bus under voltage protection"
+      -  key: 512
+         value: "ID26 Bus over voltage protection"
+      -  key: 1024
+         value: "ID27 Bus voltage unbalanced"
+      -  key: 2048
+         value: "ID28 DCI is too high"
+      -  key: 4096
+         value: "ID29 Grid current is too high"
+      -  key: 8192
+         value: "ID30 Input current is too high"
+      -  key: 16384
+         value: "ID31"
+      -  key: 32768
+         value: "ID32"
+      icon: 'mdi:wrench'  
+
+    - name: "Fault 3"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0003]
+      lookup: 
+      -  key: 0
+         value: "No error"
+      -  key: 1
+         value: "ID33 Reserved"
+      -  key: 2
+         value: "ID34 Reserved"
+      -  key: 4
+         value: "ID35 Reserved"
+      -  key: 8
+         value: "ID36 Reserved"
+      -  key: 16
+         value: "ID37 Reserved"
+      -  key: 32
+         value: "ID38 Reserved"
+      -  key: 64
+         value: "ID39 Reserved"
+      -  key: 128
+         value: "ID40 Reserved"
+      -  key: 256
+         value: "ID41 Reserved"
+      -  key: 512
+         value: "ID42 Reserved"
+      -  key: 1024
+         value: "ID43 Reserved"
+      -  key: 2048
+         value: "ID44 Reserved"
+      -  key: 4096
+         value: "ID45 Reserved"
+      -  key: 8192
+         value: "ID46 Reserved"
+      -  key: 16384
+         value: "ID47 Reserved"
+      -  key: 32768
+         value: "ID48 Reserved"
+      icon: 'mdi:wrench'  
+
+    - name: "Fault 4"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0004]
+      lookup: 
+      -  key: 0
+         value: "No error"
+      -  key: 1
+         value: "ID49 Grid voltage sampling value between master and slave DSP vary widely"
+      -  key: 2
+         value: "ID50 Grid frequency sampling value between master and slave DSP vary widely"
+      -  key: 4
+         value: "ID51 DCI sampling value between master and slave DSP vary widely"
+      -  key: 8
+         value: "ID52 GFCI sampling value between master and slave DSP vary widely"
+      -  key: 16
+         value: "ID53 Communication failure between master and slave DSP failure"
+      -  key: 32
+         value: "ID53 Communication failure between slave and communication board"
+      -  key: 64
+         value: "ID55 Relay fault"
+      -  key: 128
+         value: "ID56 Insulation resistance between PV array and the earth is too low"
+      -  key: 256
+         value: "ID57 Inverter temp is too high"
+      -  key: 512
+         value: "ID58 Boost temp is too high"
+      -  key: 1024
+         value: "ID59 Environment temp is too high"
+      -  key: 2048
+         value: "ID60 Brak podłączenie falownika do kabla PE"
+      -  key: 4096
+         value: "ID61 Reserved"
+      -  key: 8192
+         value: "ID62 Reserved"
+      -  key: 16384
+         value: "ID63 Reserved"
+      -  key: 32768
+         value: "ID64 Reserved"
+      icon: 'mdi:wrench'  
+
+    - name: "Fault 5"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0005]
+      lookup: 
+      -  key: 0
+         value: "No error"
+      -  key: 1
+         value: "ID65 Grid current is too high and causes unrecoverable fault"
+      -  key: 2
+         value: "ID66 Bus voltage is too high and causes unrecoverable fault"
+      -  key: 4
+         value: "ID67 Grid current is unbalanced and causes unrecoverable fault"
+      -  key: 8
+         value: "ID68 Input current is unbalanced and causes unrecoverable fault"
+      -  key: 16
+         value: "ID69 Bus voltage is unbalanced and causes unrecoverable fault"
+      -  key: 32
+         value: "ID70 Grid current is too high and causes unrecoverable fault"
+      -  key: 64
+         value: "ID65 PV Input Mode Configuration is wrong and causes unrecoverable fault"
+      -  key: 128
+         value: "ID72 Reserved"
+      -  key: 256
+         value: "ID73 Reserved"
+      -  key: 512
+         value: "ID74 Input current is too high and causes unrecoverable fault"
+      -  key: 1024
+         value: "ID75 Error reading from EEPROM"
+      -  key: 2048
+         value: "ID76 Error writing to EEPROM"
+      -  key: 4096
+         value: "ID77 Relay fauilure causes unrecoverable fault"
+      -  key: 8192
+         value: "ID78 Reserved"
+      -  key: 16384
+         value: "ID79 Reserved"
+      -  key: 32768
+         value: "ID80 Reserved"
+      icon: 'mdi:wrench'  
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_wifikit.yaml b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_wifikit.yaml
new file mode 100644 (file)
index 0000000..050233f
--- /dev/null
@@ -0,0 +1,769 @@
+requests:
+  # Inverter State
+  - start: 0x0200
+    end:  0x0245
+    mb_functioncode: 0x03
+  # Inverter Settings
+  - start: 0x10B0
+    end: 0x10BC
+    mb_functioncode: 0x04
+  # Inverter Information
+  - start: 0x2000
+    end: 0x200B
+    mb_functioncode: 0x04
+
+# Tested with Solarman ME3000-SP with an embedded
+# "wifikit" logger. Might work for other devices
+# too.
+
+# The ME3000-SP is basically a glorified battery
+# charger - it is not directly connected to any
+# generation infrastructure, but can calculate
+# generated energy based on any CT clamps it is
+# connected to. For most people this entity will
+# be a generally accurate representation of their
+# PV panel output, but since this tracks generation
+# from _any_ source, it is named generically.
+
+parameters:
+ - group: Generation
+   items:
+    - name: "Generation Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "kW"
+      scale: 0.01
+      rule: 1
+      registers: [0x0215]
+      icon: 'mdi:solar-power'
+
+    - name: "Daily Generation"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.01
+      rule: 1
+      registers: [0x0218]
+      icon: 'mdi:solar-power'
+
+    - name: "Total Generation"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 1
+      rule: 3
+      registers: [0x021D,0x021C]
+      icon: 'mdi:solar-power'
+
+    - name: "Daily Generation Time"
+      class: "duration"
+      state_class: "total_increasing"
+      uom: "min"
+      scale: 1
+      rule: 1
+      registers: [0x0243]
+      icon: 'mdi:sun-clock-outline'
+
+    - name: "Total Generation Time"
+      class: "duration"
+      state_class: "total_increasing"
+      uom: "h"
+      scale: 1
+      rule: 3
+      registers: [0x0245,0x244]
+      icon: 'mdi:sun-clock-outline'
+
+ - group: Load
+   items:
+    - name: "Consumption Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "kW"
+      scale: 0.01
+      rule: 1
+      registers: [0x0213]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Daily Consumption"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.01
+      rule: 1
+      registers: [0x021B]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Total Consumption"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 1
+      rule: 3
+      registers: [0x0223,0x0222]
+      icon: 'mdi:home-lightning-bolt-outline'
+
+ - group: Grid
+   items:
+    - name: "Grid A Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0206]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Grid A Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.01
+      rule: 2
+      registers: [0x0207]
+      icon: 'mdi:current-ac'
+
+    - name: "Grid B Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x0208]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Grid B Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.01
+      rule: 2
+      registers: [0x0209]
+      icon: 'mdi:current-ac'
+
+    - name: "Grid C Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x020A]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Grid C Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.01
+      rule: 2
+      registers: [0x020B]
+      icon: 'mdi:current-ac'
+
+    - name: "Grid Frequency"
+      class: "frequency"
+      state_class: "measurement"
+      uom: "Hz"
+      scale: 0.01
+      rule: 1
+      registers: [0x20C]
+      icon: 'mdi:sine-wave'
+
+    - name: "Daily Power Sold"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.01
+      rule: 1
+      registers: [0x0219]
+      icon: 'mdi:transmission-tower-export'
+
+    - name: "Daily Power Bought"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.01
+      rule: 1
+      registers: [0x021A]
+      icon: 'mdi:transmission-tower-import'
+
+ - group: Battery
+   items:
+    - name: "Battery Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.01
+      rule: 1
+      registers: [0x020E]
+      icon: 'mdi:home-battery'
+
+    - name: "Battery Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.01
+      rule: 2
+      registers: [0x020F]
+      icon: 'mdi:current-dc'
+
+    - name: "Battery Charge / Discharge Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "kW"
+      scale: 0.01
+      rule: 2
+      registers: [0x020D]
+      icon: 'mdi:home-battery'
+
+    - name: "Battery SOC"
+      class: "battery"
+      state_class: "measurement"
+      uom: "%"
+      scale: 1
+      rule: 1
+      registers: [0x210]
+      icon: 'mdi:battery'
+
+    - name: "Battery Temperature"
+      class: "temperature"
+      state_class: "measurement"      
+      uom: "°C"
+      scale: 1
+      rule: 1
+      registers: [0x0211]
+      icon: 'mdi:thermometer'      
+
+    - name: "Battery Capacity"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x10B1]
+      icon: 'mdi:battery-high'      
+
+    - name: "Battery Max Charge Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x10B3]
+      icon: 'mdi:battery'
+
+    - name: "Battery Max Charge Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.01
+      rule: 2
+      registers: [0x10B4]
+      icon: 'mdi:current-dc'
+
+    - name: "Battery Min Discharge Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x10B6]
+      icon: 'mdi:battery'
+
+    - name: "Battery Max Discharge Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.01
+      rule: 2
+      registers: [0x10B7]
+      icon: 'mdi:current-dc'
+      
+    - name: "Battery Discharge Depth"
+      class: "battery"
+      state_class: "measurement"
+      uom: "%"
+      scale: 1
+      rule: 1
+      registers: [0x10B9]
+      icon: 'mdi:battery-high'     
+
+    - name: "Battery Empty Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.01
+      rule: 1
+      registers: [0x10BB]
+      icon: 'mdi:battery'
+
+    - name: "Battery Full Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.01
+      rule: 1
+      registers: [0x10BC]
+      icon: 'mdi:battery'
+
+    - name: "Battery Type"
+      class: ""
+      state_class: "measurement"      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x10B0]
+      # This field has different definitions
+      # depending on a "Version". There is no
+      # indication what version this is,
+      # whether it is hardware or software so
+      # this is the V1.00 list. If your battery
+      # type is detected incorrectly, you
+      # probably have V1.20 (whatever that is)
+      # and need to use a custom inverter
+      # definition with the commented lookups
+      # below.
+      lookup: 
+      -  key: 0x0000
+         value: "DARFON"
+      -  key: 0x0001
+         value: "PYLON"
+      -  key: 0x0003
+         value: "SOLTARO"
+      -  key: 0x0080
+         value: "TELE"
+      -  key: 0x0100
+         value: "DEFAULT"
+
+      # V1.20 Lookups
+      # lookup:
+      # -  key: 0x0000
+      #    value: "DARFON"
+      # -  key: 0x0001
+      #    value: "PYLON"
+      # -  key: 0x0002
+      #    value: "SOLTARO"
+      # -  key: 0x0003
+      #    value: "ALPHA.ESS"
+      # -  key: 0x0004
+      #    value: "GENERAL"
+      # -  key: 0x0100
+      #    value: "DEFAULT"
+
+ - group: InverterStatus
+   items: 
+    - name: "Inverter Status"
+      class: ""
+      state_class: "measurement"      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x200]
+      lookup: 
+      -  key: 0
+         value: "Wait"
+      -  key: 1
+         value: "Self Check"
+      -  key: 2
+         value: "Charging"
+      -  key: 3
+         value: "Check Discharge"
+      -  key: 4
+         value: "Discharging"
+      -  key: 5
+         value: "EPS"
+      -  key: 6
+         value: "Fault"
+      -  key: 7
+         value: "Permanent Fault"
+      icon: 'mdi:wrench'         
+
+    - name: "Inverter Temperature"
+      class: "temperature"
+      state_class: "measurement"      
+      uom: "°C"
+      scale: 1
+      rule: 1
+      registers: [0x0238]
+      icon: 'mdi:thermometer'      
+
+    - name: "Inverter Heat-Sink Temperature"
+      class: "temperature"
+      state_class: "measurement"      
+      uom: "°C"
+      scale: 1
+      rule: 1
+      registers: [0x0239]
+      icon: 'mdi:thermometer'      
+
+    - name: "Inverter Bus Voltage"
+      class: "voltage"
+      state_class: "measurement"      
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x022D]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "LLC Bus Voltage"
+      class: "voltage"
+      state_class: "measurement"      
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [0x022E]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Countdown Time"
+      class: ""
+      state_class: "measurement"      
+      uom: "s"
+      scale: 1
+      rule: 1
+      registers: [0x022A]
+      icon: ''
+
+    - name: "Inverter Alert Message"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x022B]
+      icon: ''
+
+    - name: "Communication Board Inner Message"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0242]
+      icon: ''
+
+    - name: "Country"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x023A]
+      lookup: 
+      -  key: 0
+         value: "Germany"
+      -  key: 1
+         value: "CEI0-21 Internal"
+      -  key: 2
+         value: "Australia"
+      -  key: 3
+         value: "Spain RD1699"
+      -  key: 4
+         value: "Turkey"
+      -  key: 5
+         value: "Denmark"
+      -  key: 6
+         value: "Greece"
+      -  key: 7
+         value: "Netherland"
+      -  key: 8
+         value: "Belgium"
+      -  key: 9
+         value: "UK-G59"
+      -  key: 10
+         value: "China"
+      -  key: 11
+         value: "France"
+      -  key: 12
+         value: "Poland"
+      -  key: 13
+         value: "Germany BDEW"
+      -  key: 14
+         value: "Germany VDE0126"
+      -  key: 15
+         value: "Italy CEI0-16"
+      -  key: 16
+         value: "UK-G83"
+      -  key: 17
+         value: "Greece Islands"
+      -  key: 18
+         value: "EU EN50438"
+      -  key: 19
+         value: "EU EN61727"
+      -  key: 20
+         value: "Korea"
+      -  key: 21
+         value: "Sweden"
+      -  key: 22
+         value: "Europe General"
+      -  key: 23
+         value: "CEI0-21 External"
+      -  key: 24
+         value: "Cyprus"
+      -  key: 25
+         value: "India"
+      -  key: 26
+         value: "Philippines"
+      -  key: 27
+         value: "New Zeland"
+      -  key: 28
+         value: "Reserve"
+      -  key: 29
+         value: "Reserve"
+      icon: ''
+
+ - group: Alert
+   items: 
+    - name: "Fault 1"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0201]
+      lookup: 
+      -  key: 0
+         value: "No error"
+      -  key: 1
+         value: "ID01 Grid Over Voltage Protection"
+      -  key: 2
+         value: "ID02 Grid Under Voltage Protection"
+      -  key: 4
+         value: "ID03 Grid Over Frequency Protection"
+      -  key: 8
+         value: "ID04 Grid Under Frequency Protection"
+      -  key: 16
+         value: "ID05 PV Under Voltage Protection"
+      -  key: 32
+         value: "ID06 Grid Low Voltage Ride through"
+      -  key: 64
+         value: "ID07"
+      -  key: 128
+         value: "ID08"
+      -  key: 256
+         value: "ID09 PV Over Voltage Protection"
+      -  key: 512
+         value: "ID10 PV Input Current Unbalanced"
+      -  key: 1024
+         value: "ID11 PV Input Mode wrong configuration"
+      -  key: 2048
+         value: "ID12 Ground-Fault circuit interrupters fault"
+      -  key: 4096
+         value: "ID13 Phase sequence fault"
+      -  key: 8192
+         value: "ID14 Hardware boost over current protection"
+      -  key: 16384
+         value: "ID15 Hardware AC over current protection"
+      -  key: 32768
+         value: "ID16 Grid current is too high"
+      icon: 'mdi:wrench'  
+
+    - name: "Fault 2"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0202]
+      lookup: 
+      -  key: 0
+         value: "No error"
+      -  key: 1
+         value: "ID17 Grid current sampling error"
+      -  key: 2
+         value: "ID18 DCI sampling error"
+      -  key: 4
+         value: "ID19 Grid voltage sampling error"
+      -  key: 8
+         value: "ID20 GFCI device sampling error"
+      -  key: 16
+         value: "ID21 Main chip fault"
+      -  key: 32
+         value: "ID22 Hardware auxiliary power fault"
+      -  key: 64
+         value: "ID23 Bus voltage zero fault"
+      -  key: 128
+         value: "ID24 Output current not balanced"
+      -  key: 256
+         value: "ID25 Bus under voltage protection"
+      -  key: 512
+         value: "ID26 Bus over voltage protection"
+      -  key: 1024
+         value: "ID27 Bus voltage unbalanced"
+      -  key: 2048
+         value: "ID28 DCI is too high"
+      -  key: 4096
+         value: "ID29 Grid current is too high"
+      -  key: 8192
+         value: "ID30 Input current is too high"
+      -  key: 16384
+         value: "ID31"
+      -  key: 32768
+         value: "ID32"
+      icon: 'mdi:wrench'  
+
+    - name: "Fault 3"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0203]
+      lookup: 
+      -  key: 0
+         value: "No error"
+      -  key: 1
+         value: "ID33 Reserved"
+      -  key: 2
+         value: "ID34 Reserved"
+      -  key: 4
+         value: "ID35 Reserved"
+      -  key: 8
+         value: "ID36 Reserved"
+      -  key: 16
+         value: "ID37 Reserved"
+      -  key: 32
+         value: "ID38 Reserved"
+      -  key: 64
+         value: "ID39 Reserved"
+      -  key: 128
+         value: "ID40 Reserved"
+      -  key: 256
+         value: "ID41 Reserved"
+      -  key: 512
+         value: "ID42 Reserved"
+      -  key: 1024
+         value: "ID43 Reserved"
+      -  key: 2048
+         value: "ID44 Reserved"
+      -  key: 4096
+         value: "ID45 Reserved"
+      -  key: 8192
+         value: "ID46 Reserved"
+      -  key: 16384
+         value: "ID47 Reserved"
+      -  key: 32768
+         value: "ID48 Reserved"
+      icon: 'mdi:wrench'  
+
+    - name: "Fault 4"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0204]
+      lookup: 
+      -  key: 0
+         value: "No error"
+      -  key: 1
+         value: "ID49 Grid voltage sampling value between master and slave DSP vary widely"
+      -  key: 2
+         value: "ID50 Grid frequency sampling value between master and slave DSP vary widely"
+      -  key: 4
+         value: "ID51 DCI sampling value between master and slave DSP vary widely"
+      -  key: 8
+         value: "ID52 GFCI sampling value between master and slave DSP vary widely"
+      -  key: 16
+         value: "ID53 Communication failure between master and slave DSP failure"
+      -  key: 32
+         value: "ID53 Communication failure between slave and communication board"
+      -  key: 64
+         value: "ID55 Relay fault"
+      -  key: 128
+         value: "ID56 Insulation resistance between PV array and the earth is too low"
+      -  key: 256
+         value: "ID57 Inverter temp is too high"
+      -  key: 512
+         value: "ID58 Boost temp is too high"
+      -  key: 1024
+         value: "ID59 Environment temp is too high"
+      -  key: 2048
+         value: "ID60 Brak podłączenie falownika do kabla PE"
+      -  key: 4096
+         value: "ID61 Reserved"
+      -  key: 8192
+         value: "ID62 Reserved"
+      -  key: 16384
+         value: "ID63 Reserved"
+      -  key: 32768
+         value: "ID64 Reserved"
+      icon: 'mdi:wrench'  
+
+    - name: "Fault 5"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x0205]
+      lookup: 
+      -  key: 0
+         value: "No error"
+      -  key: 1
+         value: "ID65 Grid current is too high and causes unrecoverable fault"
+      -  key: 2
+         value: "ID66 Bus voltage is too high and causes unrecoverable fault"
+      -  key: 4
+         value: "ID67 Grid current is unbalanced and causes unrecoverable fault"
+      -  key: 8
+         value: "ID68 Input current is unbalanced and causes unrecoverable fault"
+      -  key: 16
+         value: "ID69 Bus voltage is unbalanced and causes unrecoverable fault"
+      -  key: 32
+         value: "ID70 Grid current is too high and causes unrecoverable fault"
+      -  key: 64
+         value: "ID65 PV Input Mode Configuration is wrong and causes unrecoverable fault"
+      -  key: 128
+         value: "ID72 Reserved"
+      -  key: 256
+         value: "ID73 Reserved"
+      -  key: 512
+         value: "ID74 Input current is too high and causes unrecoverable fault"
+      -  key: 1024
+         value: "ID75 Error reading from EEPROM"
+      -  key: 2048
+         value: "ID76 Error writing to EEPROM"
+      -  key: 4096
+         value: "ID77 Relay fauilure causes unrecoverable fault"
+      -  key: 8192
+         value: "ID78 Reserved"
+      -  key: 16384
+         value: "ID79 Reserved"
+      -  key: 32768
+         value: "ID80 Reserved"
+      icon: 'mdi:wrench'  
+
+ - group: InverterInformation
+   items: 
+    - name: "Production Code"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [0x2000]
+
+    - name: "Serial Number"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 5
+      registers: [0x2001,0x2002,0x2003,0x2004,0x2005,0x2006,0x2007]
+      isstr: true
+
+    - name: "Software Version"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 5
+      registers: [0x2008,0x2009]
+      isstr: true
+
+    - name: "Hardware Version"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 5
+      registers: [0x200A,0x200B]
+      isstr: true
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/definitions/solis_1p8k-5g.yaml b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/solis_1p8k-5g.yaml
new file mode 100644 (file)
index 0000000..22ca58d
--- /dev/null
@@ -0,0 +1,204 @@
+# Solis Single Phase Inverter
+# 1P8K-5G
+# Modbus information derived by test and comparing to Solis Cloud
+# Gedger V.0.1 May 2022
+#
+requests:
+  - start: 2999
+    end:  3024
+    mb_functioncode: 0x04
+  - start: 3035
+    end:  3043
+    mb_functioncode: 0x04
+  - start: 3071
+    end:  3071
+    mb_functioncode: 0x04
+
+parameters:
+ - group: InverterStatus
+   items:
+    - name: "Inverter Status"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 6
+      registers: [3043]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Operating Status"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 6
+      registers: [3071]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Inverter Temperature"
+      class: "temperature"
+      state_class: "measurement"
+      uom: "°C"
+      scale: 0.1
+      rule: 2
+      registers: [3041]
+      icon: 'mdi:thermometer'
+
+#    - name: "Inverter ID"
+#      class: ""
+#      state_class: ""
+#      uom: ""
+#      scale: 1
+#      rule: 5
+#      registers: [33004,33005,33006,33007,33008,33009,33010,33011,33012,33013,33014,33015,33016,33017,33018,33019]
+#      isstr: true
+
+    - name: "Product Model"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 6
+      registers: [2999]
+      isstr: true
+
+    - name: "DSP Software Version"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 6
+      registers: [3000]
+      isstr: true
+
+    - name: "LCD Software Version"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 6
+      registers: [3001]
+      isstr: true
+
+ - group: InverterDC
+   items:
+    - name: "PV1 Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [3021]
+      icon: 'mdi:solar-power'
+
+    - name: "PV2 Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [3023]
+      icon: 'mdi:solar-power'
+
+    - name: "PV1 Current"
+      class: "current"
+      uom: "A"
+      scale: 0.1
+      rule: 1
+      registers: [3022]
+      icon: 'mdi:current-dc'
+
+    - name: "PV2 Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.1
+      rule: 1
+      registers: [3024]
+      icon: 'mdi:current-dc'
+
+    - name: "Total DC Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "kW"
+      scale: 0.001
+      rule: 3
+      registers: [3007, 3006]
+      icon: 'mdi:solar-power'
+
+ - group: InverterAC
+   items:
+    - name: "Inverter AC Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "kW"
+      scale: 0.001
+      rule: 3
+      registers: [3005, 3004]
+      icon: 'mdi:solar-power'
+
+    - name: "Inverter Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [3035]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Inverter Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.1
+      rule: 1
+      registers: [3038]
+      icon: 'mdi:current-ac'
+
+    - name: "Inverter Frequency"
+      class: "frequency"
+      state_class: "measurement"
+      uom: "Hz"
+      scale: 0.01
+      rule: 1
+      registers: [3042]
+      icon: 'mdi:sine-wave'
+
+ - group: Generation
+   items:
+    - name: "Daily Generation"
+      class: "energy"
+      state_class: "measurement"
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [3014]
+      icon: 'mdi:solar-power'
+
+    - name: "Monthly Generation"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 1
+      rule: 3
+      registers: [3011, 3010]
+      icon: 'mdi:solar-power'
+
+    - name: "Yearly Generation"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 1
+      rule: 3
+      registers: [3017, 3016]
+      icon: 'mdi:solar-power'
+
+    - name: "Total Generation"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 1
+      rule: 3
+      registers: [3009, 3008]
+      icon: 'mdi:solar-power'
+
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/definitions/solis_hybrid.yaml b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/solis_hybrid.yaml
new file mode 100644 (file)
index 0000000..78b24ee
--- /dev/null
@@ -0,0 +1,783 @@
+# Solis Single Phase Hybrid
+# RHI-(3-6)K-48ES-5G
+# Modbus information retrieved from:
+# https://www.scss.tcd.ie/coghlan/Elios4you/RS485_MODBUS-Hybrid-BACoghlan-201811228-1854.pdf
+
+requests:
+  - start: 33029
+    end:  33095
+    mb_functioncode: 0x04
+  - start: 33116
+    end:  33179
+    mb_functioncode: 0x04
+  - start: 33206
+    end:  33282
+    mb_functioncode: 0x04
+
+parameters:
+ - group: InverterStatus
+   items: 
+    - name: "Inverter Status"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [33095]
+      icon: 'mdi:home-lightning-bolt'
+      lookup: 
+      -  key: 0x0
+         value: "Waiting State"
+      -  key:  0x1
+         value: "Open Loop Operation"
+      -  key: 0x2
+         value: "Soft Start"
+      -  key: 0x3
+         value: "On Grid/Generating"
+      -  key: 0x1004
+         value: "Grid OverVoltage"         
+      -  key: 0x1010
+         value: "Grid UnderVoltage"        
+      -  key: 0x1012
+         value: "Grid OverFrequency"         
+      -  key: 0x1013
+         value: "Grid UnderFrequency"          
+      -  key: 0x1014
+         value: "Grid Imp too large"
+      -  key: 0x1015
+         value: "No Grid"         
+      -  key: 0x1016
+         value: "Grid Imbalance"         
+      -  key: 0x1017
+         value: "Grid Freq Jitter"         
+      -  key: 0x1018
+         value: "Grid Overcurrent"         
+      -  key: 0x1019
+         value: "Grid Tracking Fault"         
+      -  key: 0x1020
+         value: "DC OverVoltage"         
+      -  key: 0x1021
+         value: "DC Bus Overvoltage"
+      -  key: 0x1022
+         value: "DC Bus Uneven Voltage"         
+      -  key: 0x1024
+         value: "DC Bus Uneven Voltage2"
+      -  key: 0x1025
+         value: "DC A path OverCurrent"
+      -  key: 0x1026
+         value: "DC B path OverCurrent"         
+      -  key: 0x1027
+         value: "DC Input Disturbance"         
+      -  key: 0x1030
+         value: "Grid Disturbance"         
+      -  key: 0x1031
+         value: "DSP Initialization Protection "         
+      -  key: 0x1032
+         value: "Over Temp Protection"         
+      -  key: 0x1033
+         value: "PV Insulation Fault"         
+      -  key: 0x1034
+         value: "Leakage Current Protection"         
+      -  key: 0x1035
+         value: "Relay Detection Protection"         
+      -  key: 0x1036
+         value: "DSP_B Protection"         
+      -  key: 0x1037
+         value: "DC Component too Large"         
+      -  key: 0x1038
+         value: "12v UnderVoltage Protection"         
+      -  key: 0x1039
+         value: "Under Temperature Protection"         
+      -  key: 0x1040
+         value: "Arc Self-Test Protection"         
+      -  key: 0x1041
+         value: "Arc Protection"         
+      -  key: 0x1042
+         value: "DSP on-chip SRAM exception"         
+      -  key: 0x1043
+         value: "DSP on-chip FLASH exception"
+      -  key: 0x1044
+         value: "DSP on-chip PC pointer is abnormal"        
+      -  key: 0x1045
+         value: "DSP key register exception"
+      -  key: 0x1046
+         value: "Grid disturbance 02"         
+      -  key: 0x1047
+         value: "Grid current sampling abnormality"         
+      -  key: 0x1048
+         value: "IGBT overcurrent"
+      -  key: 0x1050
+         value: "Network current transient overcurrent"         
+      -  key: 0x1051
+         value: "Battery overvoltage hardware failure"         
+      -  key: 0x1052
+         value: "LLC hardware overcurrent"         
+      -  key: 0x1053
+         value: "Battery overvoltage detection"         
+      -  key: 0x1054
+         value: "Battery undervoltage detection"         
+      -  key: 0x1055
+         value: "Battery no connected"         
+      -  key: 0x1056
+         value: "Bypass overvoltage fault"         
+      -  key: 0x1057
+         value: "Bypass overload fault"      
+
+    - name: "Operating Status"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [33121]
+      icon: 'mdi:home-lightning-bolt'
+      lookup: 
+      -  key: 0x701
+         value: "Normal Operation"
+      -  key: 0x702
+         value: "Initial Standby"
+      -  key: 0x704
+         value: "Control Shutdown"
+      -  key: 0x708
+         value: "Downtime"
+      -  key: 0x710
+         value: "Standby"         
+      -  key: 0x720
+         value: "Derating Operation"        
+      -  key: 0x740
+         value: "Limit Operation"      
+      -  key: 0x780
+         value: "Bypass Overload"              
+                     
+    - name: "Grid Fault Status"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [33116]
+      icon: 'mdi:alert'
+      lookup: 
+      -  key: 0x0000
+         value: "No Fault"
+      -  key: 0x1
+         value: "No Grid"
+      -  key: 0x2
+         value: "Grid OverVoltage"
+      -  key: 0x4
+         value: "Grid UnderVoltage"
+      -  key: 0x8
+         value: "Grid OverFrequency"         
+      -  key: 0x10
+         value: "Grid UnderFrequency"
+      -  key: 0x20
+         value: "Grid Imbalance"
+      -  key: 0x40
+         value: "Grid Frequncy Jitter"
+      -  key: 0x80
+         value: "Grid Impedence too Large"
+      -  key: 0x100
+         value: "Grid Tracking Fault"        
+      -  key: 0x200
+         value: "Meter Comm Failure"        
+      -  key: 0x400
+         value: "Failsafe"              
+                                                                                                     
+    - name: "Backup Load Fault Status"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [33117]
+      icon: 'mdi:alert'
+      lookup: 
+      -  key: 0x0
+         value: "No Fault"
+      -  key: 0x1
+         value: "Bypass OverVoltage Fault"
+      -  key: 0x2
+         value: "Bypass Overload Fault"             
+    - name: "Battery Fault Status"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [33118]
+      icon: 'mdi:alert'
+      lookup: 
+      -  key: 0x0
+         value: "No Fault"
+      -  key: 0x1
+         value: "Battery Not Connected"
+      -  key: 0x2
+         value: "Battery OverVoltage Detection"
+      -  key: 0x4
+         value: "Battery UnderVoltage Detection"              
+                                               
+    - name: "Fault Status 04 (Device)"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [33119]
+      icon: 'mdi:alert'
+      lookup: 
+      -  key: 0x0000
+         value: "No Fault"
+      -  key: 0x1
+         value: "DC OverVoltage"
+      -  key: 0x2
+         value: "DC Bus OverVoltage"
+      -  key: 0x4
+         value: "DC Bus Uneven Voltage"
+      -  key: 0x8
+         value: "DC Bus UnderVoltage"         
+      -  key: 0x10
+         value: "DC Bus2 Uneven Voltage"
+      -  key: 0x20
+         value: "DC A path OverCurrent"
+      -  key: 0x40
+         value: "DC B path OverCurrent"
+      -  key: 0x80
+         value: "DC Input Disturbance"
+      -  key: 0x100
+         value: "Grid OverCurrent"        
+      -  key: 0x200
+         value: "IGBT OverCurrent"        
+      -  key: 0x400
+         value: "Grid Disturbance 2"      
+      -  key: 0x800
+         value: "Arc Self-Test Protection"
+      -  key: 0x1000
+         value: "Arc Fault Reservation"
+      -  key: 0x2000
+         value: "Grid Current Sample Abnormality"  
+    - name: "Fault Status 05 (Device)"
+      class: ""
+      state_class: ""
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [33120]
+      icon: 'mdi:alert'
+      lookup: 
+      -  key: 0x0000
+         value: "No Fault"
+      -  key: 0x1
+         value: "Grid Disturbance"
+      -  key: 0x2
+         value: "DC Component Too Large"
+      -  key: 0x4
+         value: "Over Temp Protection"
+      -  key: 0x8
+         value: "Relay Detection Protection"         
+      -  key: 0x10
+         value: "Under Temp Protection"
+      -  key: 0x20
+         value: "PV Insulation Fault"
+      -  key: 0x40
+         value: "12V UnderVoltage Protection"
+      -  key: 0x80
+         value: "Leakage Current Protection"
+      -  key: 0x100
+         value: "Leakage Current Self-Test"        
+      -  key: 0x200
+         value: "DSP Initialization Protect"        
+      -  key: 0x400
+         value: "DSP B Protection"      
+      -  key: 0x800
+         value: "Battery Overvoltage H/W Failure"
+      -  key: 0x1000
+         value: "LLC Hardware OverCurrent"      
+      -  key: 0x2000
+         value: "Network Side Transient OverCurrent"
+      -  key: 0x4000
+         value: "CAN Communication Failed"         
+      -  key: 0x8000
+         value: "DSP Communication Failed"         
+    - name: "Inverter Temperature"
+      class: "temperature"
+      state_class: "measurement"      
+      uom: "°C"
+      scale: 0.1
+      rule: 2
+      registers: [33093]
+      icon: 'mdi:thermometer'
+
+# Sensors below are outside of modbus request ranges.
+# If enabling, ensure to amend the request start register.
+# 
+#    - name: "Inverter ID"
+#      class: ""
+#      state_class: ""      
+#      uom: ""
+#      scale: 1
+#      rule: 5
+#      registers: [33004,33005,33006,33007,33008,33009,33010,33011,33012,33013,33014,33015,33016,33017,33018,33019]
+#      isstr: true
+      
+#    - name: "Product Model"
+#      class: ""
+#      state_class: ""      
+#      uom: ""
+#      scale: 1
+#      rule: 6
+#      registers: [33000]
+#      isstr: true
+
+#    - name: "DSP Software Version"
+#      class: ""
+#      state_class: ""      
+#      uom: ""
+#      scale: 1
+#      rule: 6
+#      registers: [33001]
+#      isstr: true
+    
+#    - name: "LCD Software Version"
+#      class: ""
+#      state_class: ""      
+#      uom: ""
+#      scale: 1
+#      rule: 6
+#      registers: [33002]
+#      isstr: true
+    
+#    - name: "Protocol Software Version"
+#      class: ""
+#      state_class: ""      
+#      uom: ""
+#      scale: 1
+#      rule: 6
+#      registers: [33003]
+#      isstr: true
+
+    - name: "Storage Control Mode"
+      class: ""
+      state_class: ""      
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [33132]
+      icon: 'mdi:battery-clock'
+      lookup: 
+      -  key: 0x21
+         value: "Spontaneous Mode"
+      -  key: 0x22
+         value: "Optimized Revenue Mode"
+      -  key: 0x23
+         value: "Charging from Grid"        
+      -  key: 0x24
+         value: "Off-Grid Storage Mode"
+      -  key: 0x28      
+         value: "Battery Wake-Up"                                
+ - group: InverterDC
+   items: 
+    - name: "PV1 Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [33049]
+      icon: 'mdi:solar-power'
+
+    - name: "PV2 Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [33051]
+      icon: 'mdi:solar-power'
+
+    - name: "PV1 Current"
+      class: "current"
+      uom: "A"
+      scale: 0.1
+      rule: 1
+      registers: [33050]
+      icon: 'mdi:current-dc'
+
+    - name: "PV2 Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.1
+      rule: 1
+      registers: [33052]
+      icon: 'mdi:current-dc'
+    
+    - name: "Inverter DC Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 3
+      registers: [33058,33057]
+      icon: 'mdi:solar-power'
+
+ - group: InverterAC
+   items: 
+    - name: "Inverter AC Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 4
+      registers: [33152,33151]
+      icon: 'mdi:solar-power'
+
+    - name: "Inverter Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [33073]
+      icon: 'mdi:transmission-tower'
+    
+    - name: "Inverter Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.1
+      rule: 1
+      registers: [33076]
+      icon: 'mdi:current-ac'
+
+    - name: "Inverter Active Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 4
+      registers: [33080,33079]
+      icon: 'mdi:transmission-tower'
+
+      # Inverter Reactive Power is defined as a signed 32 bit integer
+      # across 33082 and 33081, however the field appears to be only
+      # 20 bits wide i.e. the upper 12 bits are always zero.
+      # Define only the lower signed 16 bits for moment
+    - name: "Inverter Reactive Power"
+      class: "reactive_power"
+      state_class: "measurement"
+      uom: "var"
+      scale: 1
+      rule: 4
+      registers: [33082]
+      icon: 'mdi:transmission-tower'
+    
+    - name: "Inverter Apparent Power"
+      class: "apparent_power"
+      state_class: "measurement"
+      uom: "VA"
+      scale: 1
+      rule: 4
+      registers: [33084,33083]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Inverter Frequency"
+      class: "frequency"
+      state_class: "measurement"
+      uom: "Hz"
+      scale: 0.01
+      rule: 1
+      registers: [33094]
+      icon: 'mdi:sine-wave'
+
+ - group: Generation
+   items: 
+    - name: "Daily Generation"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [33035]
+      icon: 'mdi:solar-power'
+
+    - name: "Monthly Generation"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 1
+      rule: 3
+      registers: [33032,33031]
+      icon: 'mdi:solar-power'
+
+    - name: "Yearly Generation"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 1
+      rule: 3
+      registers: [33038,33037]
+      icon: 'mdi:solar-power'
+
+    - name: "Total Generation"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 1
+      rule: 3
+      registers: [33030,33029]
+      icon: 'mdi:solar-power'
+
+ - group: Grid
+   items: 
+    - name: "Meter Frequency"
+      class: "frequency"
+      state_class: "measurement"
+      uom: "Hz"
+      scale: 0.01
+      rule: 1
+      registers: [33282]
+      icon: 'mdi:sine-wave'
+
+    - name: "Meter Power Factor"
+      class: "power_factor"
+      state_class: "measurement"
+      uom: "%"
+      scale: 0.01
+      rule: 2
+      registers: [33281]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Meter Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [33251]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Meter Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.01
+      rule: 1
+      registers: [33252]
+      icon: 'mdi:current-ac'
+
+    - name: "Meter Active Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 4
+      registers: [33258,33257]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Meter Reactive Power"
+      class: "reactive_power"
+      state_class: "measurement"
+      uom: "var"
+      scale: 1
+      rule: 4
+      registers: [33266,33265]
+      icon: 'mdi:transmission-tower'
+    
+    - name: "Meter Apparent Power"
+      class: "apparent_power"
+      state_class: "measurement"
+      uom: "VA"
+      scale: 1
+      rule: 4
+      registers: [33274,33273]
+      icon: 'mdi:transmission-tower'
+
+    - name: "Daily Energy Imported"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [33171]
+      icon: 'mdi:home-import-outline'
+
+    - name: "Total Energy Imported"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 1
+      rule: 3
+      registers: [33170,33169]
+      icon: 'mdi:home-import-outline'
+      
+    - name: "Daily Energy Exported"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [33175]
+      icon: 'mdi:home-export-outline'
+
+    - name: "Total Energy Exported"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 1
+      rule: 3
+      registers: [33174,33173]
+      icon: 'mdi:home-export-outline'
+
+ - group: Load
+   items: 
+    - name: "House Load Power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 1
+      rule: 1
+      registers: [33147]
+      icon: 'mdi:home-lightning-bolt'
+
+    - name: "Backup Load Power"
+      class: "power"
+      state_class: "measurement"      
+      uom: "W"
+      scale: 1
+      rule: 1
+      registers: [33148]
+      icon: 'mdi:home-battery'
+
+    - name: "Daily House+Backup Load Consumption"
+      class: "energy"
+      state_class: "total_increasing"      
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [33179]
+      icon: 'mdi:lightning-bolt-outline'
+
+    - name: "Total House+Backup Load Consumption"
+      class: "energy"
+      state_class: "total_increasing"      
+      uom: "kWh"
+      scale: 1
+      rule: 3
+      registers: [33178,33177]
+      icon: 'mdi:lightning-bolt-outline'
+
+ - group: Battery
+   items: 
+    - name: "Battery Status"
+      class: ""
+      state_class: "measurement"
+      uom: ""
+      scale: 1
+      rule: 1
+      registers: [33135]
+      isstr: true
+      lookup: 
+      -  key: 0
+         value: "Charge"
+      -  key: 1
+         value: "Discharge"
+      icon: 'mdi:battery'
+
+    - name: "Battery Power"
+      class: "power"
+      state_class: "measurement"
+      uom: "W"
+      scale: 1
+      rule: 4
+      registers: [33150,33149]
+      icon: 'mdi:battery-charging'
+
+    - name: "Battery SOC"
+      class: "battery"
+      state_class: "measurement"
+      uom: "%"
+      scale: 1
+      rule: 1
+      registers: [33139]
+      icon: 'mdi:battery'
+    
+    - name: "Battery SOH"
+      class: "battery"
+      state_class: "measurement"
+      uom: "%"
+      scale: 1
+      rule: 1
+      registers: [33140]
+      icon: 'mdi:battery'
+
+    - name: "Battery Current"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.1
+      rule: 2
+      registers: [33134]
+      icon: 'mdi:current-dc'
+    - name: "Battery Voltage"
+      class: "voltage"
+      state_class: "measurement"
+      uom: "V"
+      scale: 0.1
+      rule: 1
+      registers: [33133]
+      icon: 'mdi:battery'
+
+    - name: "Today Battery Charge"
+      class: "energy"
+      state_class: "total_increasing"      
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [33163]
+      icon: 'mdi:battery-plus'
+
+    - name: "Today Battery Discharge"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 0.1
+      rule: 1
+      registers: [33167]
+      icon: 'mdi:battery-minus'
+
+    - name: "Total Battery Charge"
+      class: "energy"
+      state_class: "total_increasing"      
+      uom: "kWh"
+      scale: 1
+      rule: 3
+      registers: [33162,33161]
+      icon: 'mdi:battery-plus'
+
+    - name: "Total Battery Discharge"
+      class: "energy"
+      state_class: "total_increasing"
+      uom: "kWh"
+      scale: 1
+      rule: 3
+      registers: [33166,33165]
+      icon: 'mdi:battery-minus'
+    
+    - name: "Battery Charge Current Limit"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.1
+      rule: 1
+      registers: [33206]
+      icon: 'mdi:battery-arrow-up'
+
+    - name: "Battery Discharge Current Limit"
+      class: "current"
+      state_class: "measurement"
+      uom: "A"
+      scale: 0.1
+      rule: 1
+      registers: [33207]
+      icon: 'mdi:battery-arrow-down'
diff --git a/bundles/org.openhab.binding.solarman/src/main/resources/definitions/zcs_azzurro-ktl-v3.yaml b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/zcs_azzurro-ktl-v3.yaml
new file mode 100644 (file)
index 0000000..3ce74e0
--- /dev/null
@@ -0,0 +1,745 @@
+# ZCS Azzurro 3-phase non-hybrid inverters
+# with LSW-3 WiFi logger with SN 23xxxxxxxx and FW LSW3_15_270A_1.53:
+# 3PH 3.3KTL-V3
+# 3PH 4.4KTL-V3
+# 3PH 5.5KTL-V3
+# 3PH 6.6KTL-V3
+
+# Not tested, but could probably work:
+# ZCS Azzurro 3PH 8.8KTL-V3
+# ZCS Azzurro 3PH 11KTL-V3
+# ZCS Azzurro 3PH 12KTL-V3
+# SOFAR Solar 4.4KTLX-G3
+# SOFAR Solar 5.5KTLX-G3
+# SOFAR Solar 6.6KTLX-G3
+# SOFAR Solar 8.8KTLX-G3
+# SOFAR Solar 11KTLX-G3
+# SOFAR Solar 12KTLX-G3
+
+requests:
+  - start: 0x0400
+    end: 0x042B
+    mb_functioncode: 0x03
+  - start: 0x0482
+    end: 0x04A4
+    mb_functioncode: 0x03
+  - start: 0x0582
+    end: 0x0589
+    mb_functioncode: 0x03
+  - start: 0x0682
+    end: 0x068B
+    mb_functioncode: 0x03
+
+parameters:
+  - group: Solar
+    items:
+      - name: 'PV Generation today'
+        class: 'energy'
+        state_class: 'total'
+        uom: 'kWh'
+        scale: 0.01
+        rule: 1
+        registers: [0x0685, 0x0684]
+        icon: 'mdi:solar-power'
+
+      - name: 'PV Generation total'
+        class: 'energy'
+        state_class: 'total_increasing'
+        uom: 'kWh'
+        scale: 0.1
+        rule: 3
+        registers: [0x0687, 0x0686]
+        icon: 'mdi:solar-power'
+
+      - name: 'PV1 Power'
+        class: 'power'
+        state_class: 'measurement'
+        uom: 'W'
+        scale: 10
+        rule: 1
+        registers: [0x0586]
+        icon: 'mdi:solar-power'
+
+      - name: 'PV2 Power'
+        class: 'power'
+        state_class: 'measurement'
+        uom: 'W'
+        scale: 10
+        rule: 1
+        registers: [0x0589]
+        icon: 'mdi:solar-power'
+
+      - name: 'PV1 Voltage'
+        class: 'voltage'
+        state_class: 'measurement'
+        uom: 'V'
+        scale: 0.1
+        rule: 1
+        registers: [0x0584]
+        icon: 'mdi:solar-power'
+
+      - name: 'PV2 Voltage'
+        class: 'voltage'
+        state_class: 'measurement'
+        uom: 'V'
+        scale: 0.1
+        rule: 1
+        registers: [0x0587]
+        icon: 'mdi:solar-power'
+
+      - name: 'PV1 Current'
+        class: 'current'
+        state_class: 'measurement'
+        uom: 'A'
+        scale: 0.01
+        rule: 1
+        registers: [0x0585]
+        icon: 'mdi:solar-power'
+
+      - name: 'PV2 Current'
+        class: 'current'
+        state_class: 'measurement'
+        uom: 'A'
+        scale: 0.01
+        rule: 1
+        registers: [0x0588]
+        icon: 'mdi:solar-power'
+
+  - group: Grid
+    items:
+      - name: 'Grid Frequency'
+        class: 'frequency'
+        state_class: 'measurement'
+        uom: 'Hz'
+        scale: 0.01
+        rule: 1
+        registers: [0x0484]
+        icon: 'mdi:home-lightning-bolt'
+
+      - name: 'Active Power Output Total'
+        class: 'power'
+        state_class: 'measurement'
+        uom: 'W'
+        scale: 10
+        rule: 2
+        registers: [0x0485]
+        icon: 'mdi:home-lightning-bolt'
+
+  - group: Inverter
+    items:
+      - name: 'Inverter status'
+        class: ''
+        state_class: 'measurement'
+        uom: ''
+        scale: 1
+        rule: 1
+        registers: [0x0404]
+        lookup:
+          - key: 0
+            value: 'Stand-by'
+          - key: 1
+            value: 'Self-checking'
+          - key: 2
+            value: 'Normal'
+          - key: 3
+            value: 'FAULT'
+          - key: 4
+            value: 'Permanent'
+        icon: 'mdi:wrench'
+
+      - name: 'Module temperature'
+        class: 'temperature'
+        uom: '°C'
+        scale: 0.1
+        rule: 2
+        registers: [0x0683]
+        icon: 'mdi:thermometer'
+
+      - name: 'Ambient temperature'
+        class: 'temperature'
+        uom: '°C'
+        scale: 1
+        rule: 2
+        registers: [0x0418]
+        icon: 'mdi:thermometer'
+
+      - name: 'Radiator temperature'
+        class: 'temperature'
+        uom: '°C'
+        scale: 1
+        rule: 2
+        registers: [0x041A]
+        icon: 'mdi:thermometer'
+
+      - name: 'Insulation Resistance'
+        class: ''
+        state_class: 'measurement'
+        uom: 'Ω'
+        scale: 1
+        rule: 1
+        registers: [0x042B]
+        icon: 'mdi:omega'
+
+  - group: Alert
+    items:
+      - name: 'Alert'
+        class: ''
+        state_class: ''
+        uom: ''
+        scale: 1
+        rule: 6
+        registers:
+          [
+            0x0405,
+            0x0406,
+            0x0407,
+            0x0408,
+            0x0409,
+            0x040A,
+            0x040B,
+            0x040C,
+            0x040D,
+            0x040E,
+            0x040F,
+            0x0410,
+          ]
+
+      - name: 'Fault 1'
+        class: ''
+        state_class: ''
+        uom: ''
+        scale: 1
+        rule: 1
+        registers: [0x0405]
+        isstr: true
+        icon: 'mdi:wrench'
+        lookup:
+          - key: 0
+            value: 'No error'
+          - key: 1
+            value: 'ID01 Grid Over Voltage Protection'
+          - key: 2
+            value: 'ID02 Grid Under Voltage Protection'
+          - key: 4
+            value: 'ID03 Grid Over Frequency Protection'
+          - key: 8
+            value: 'ID04 Grid Under Frequency Protection'
+          - key: 16
+            value: 'ID05 Leakage current fault'
+          - key: 32
+            value: 'ID06 High penetration error'
+          - key: 64
+            value: 'ID07 Low penetration error'
+          - key: 128
+            value: 'ID08 Islanding error'
+          - key: 256
+            value: 'ID09 Grid voltage transient value overvoltage 1'
+          - key: 512
+            value: 'ID10 Grid voltage transient value overvoltage 2'
+          - key: 1024
+            value: 'ID11 Grid line voltage error'
+          - key: 2048
+            value: 'ID12 Inverter voltage error'
+          - key: 4096
+            value: 'ID13 Anti-backflow overload'
+          - key: 8192
+            value: 'ID14'
+          - key: 16384
+            value: 'ID15'
+          - key: 32768
+            value: 'ID16'
+
+      - name: 'Fault 2'
+        class: ''
+        state_class: ''
+        uom: ''
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        isstr: true
+        registers: [0x0406]
+        lookup:
+          - key: 0
+            value: 'No error'
+          - key: 1
+            value: 'ID17 Grid current sampling error'
+          - key: 2
+            value: 'ID18 Grid current DC component sampling error (AC side)'
+          - key: 4
+            value: 'ID19 Grid voltage sampling error (DC side)'
+          - key: 8
+            value: 'ID20 Grid voltage sampling error (AC side)'
+          - key: 16
+            value: 'ID21 Leakage current sampling error (DC side)'
+          - key: 32
+            value: 'ID22 Leakage current sampling error (AC side)'
+          - key: 64
+            value: 'ID23 Load voltage DC component sampling error'
+          - key: 128
+            value: 'ID24 DC input current sampling error'
+          - key: 256
+            value: 'ID25 DC component sampling error of grid current (DC side)'
+          - key: 512
+            value: 'ID26 DC input branch current sampling error'
+          - key: 1024
+            value: 'ID27'
+          - key: 2048
+            value: 'ID28'
+          - key: 4096
+            value: 'ID29 Leakage current consistency error'
+          - key: 8192
+            value: 'ID30 Grid voltage consistency error'
+          - key: 16384
+            value: 'ID31 DCI consistency error'
+          - key: 32768
+            value: 'ID32'
+
+      - name: 'Fault 3'
+        class: ''
+        state_class: ''
+        uom: ''
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        isstr: true
+        registers: [0x0407]
+        lookup:
+          - key: 0
+            value: 'No error'
+          - key: 1
+            value: 'ID033 SPI communication error (DC side)'
+          - key: 2
+            value: 'ID034 SPI communication error (AC side)'
+          - key: 4
+            value: 'ID035 Chip error (DC side)'
+          - key: 8
+            value: 'ID036 Chip error (AC side)'
+          - key: 16
+            value: 'ID037 Auxiliary power error'
+          - key: 32
+            value: 'ID038 Inverter soft start failure'
+          - key: 64
+            value: 'ID039 '
+          - key: 128
+            value: 'ID040 '
+          - key: 256
+            value: 'ID041 Relay detection failure'
+          - key: 512
+            value: 'ID042 Low insulation impedance'
+          - key: 1024
+            value: 'ID043 Grounding error'
+          - key: 2048
+            value: 'ID044 Input mode setting error'
+          - key: 4096
+            value: 'ID045 CT error'
+          - key: 8192
+            value: 'ID046 Input reversal error'
+          - key: 16384
+            value: 'ID047 Parallel error'
+          - key: 32768
+            value: 'ID048 Serial number error'
+
+      - name: 'Fault 4'
+        class: ''
+        state_class: ''
+        uom: ''
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        registers: [0x0408]
+        isstr: true
+        lookup:
+          - key: 0
+            value: 'No error'
+          - key: 1
+            value: 'ID049 Battery temperature protection'
+          - key: 2
+            value: 'ID050 Heat sink 1 temperature protection'
+          - key: 4
+            value: 'ID051 Heater 2 temperature protection'
+          - key: 8
+            value: 'ID052 Heater 3 temperature protection'
+          - key: 16
+            value: 'ID053 Heatsink 4 temperature protection'
+          - key: 32
+            value: 'ID054 Heatsink 5 temperature protection'
+          - key: 64
+            value: 'ID055 Radiator 6 temperature protection'
+          - key: 128
+            value: 'ID056 '
+          - key: 256
+            value: 'ID057 Ambient temperature 1 protection'
+          - key: 512
+            value: 'ID058 Ambient temperature 2 protection'
+          - key: 1024
+            value: 'ID059 Module 1 temperature protection'
+          - key: 2048
+            value: 'ID060 Module 2 temperature protection'
+          - key: 4096
+            value: 'ID061 Module 3 temperature protection'
+          - key: 8192
+            value: 'ID062 Module temperature difference is too large'
+          - key: 16384
+            value: 'ID063 '
+          - key: 32768
+            value: 'ID064 '
+
+      - name: 'Fault 5'
+        class: ''
+        state_class: ''
+        uom: ''
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        registers: [0x0409]
+        isstr: true
+        lookup:
+          - key: 0
+            value: 'No error'
+          - key: 1
+            value: 'ID065 Bus voltage RMS unbalance'
+          - key: 2
+            value: 'ID066 Bus voltage transient   value unbalance'
+          - key: 4
+            value: 'ID067 Undervoltage of busbar during grid connection'
+          - key: 8
+            value: 'ID068 Bus bar low voltage'
+          - key: 16
+            value: 'ID069 PV overvoltage'
+          - key: 32
+            value: 'ID070 Battery over-voltage'
+          - key: 64
+            value: 'ID071 LLCBus overvoltage protection'
+          - key: 128
+            value: 'ID072 Inverter bus voltage RMS software overvoltage'
+          - key: 256
+            value: 'ID073 Inverter bus voltage transient value software overvoltage'
+          - key: 512
+            value: 'ID074 Flying Cross Capacitor Overvoltage Protection'
+          - key: 1024
+            value: 'ID075 Flying Cross capacitor undervoltage protection'
+          - key: 2048
+            value: 'ID076 '
+          - key: 4096
+            value: 'ID077 '
+          - key: 8192
+            value: 'ID078 '
+          - key: 16384
+            value: 'ID079 '
+          - key: 32768
+            value: 'ID080 '
+
+      - name: 'Fault 6'
+        class: ''
+        state_class: ''
+        uom: ''
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        isstr: true
+        registers: [0x040A]
+        lookup:
+          - key: 0
+            value: 'No error'
+          - key: 1
+            value: 'ID081 Battery overcurrent software protection'
+          - key: 2
+            value: 'ID082 Dci overcurrent protection'
+          - key: 4
+            value: 'ID083 Output transient current protection'
+          - key: 8
+            value: 'ID084 BuckBoost software overcurrent'
+          - key: 16
+            value: 'ID085 Output RMS current protection'
+          - key: 32
+            value: 'ID086 PV instantaneous current overcurrent software protection'
+          - key: 64
+            value: 'ID087 PV parallel uneven current'
+          - key: 128
+            value: 'ID088 Output current unbalance'
+          - key: 256
+            value: 'ID089 PV software overcurrent protection'
+          - key: 512
+            value: 'ID090 Balanced circuit overcurrent protection'
+          - key: 1024
+            value: 'ID091 Resonance protection'
+          - key: 2048
+            value: 'ID092 '
+          - key: 4096
+            value: 'ID093 '
+          - key: 8192
+            value: 'ID094 '
+          - key: 16384
+            value: 'ID095 '
+          - key: 32768
+            value: 'ID096 '
+
+      - name: 'Fault 7'
+        class: ''
+        state_class: ''
+        uom: ''
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        isstr: true
+        registers: [0x040B]
+        lookup:
+          - key: 0
+            value: 'No error'
+          - key: 1
+            value: 'ID097 LLC bus hardware overvoltage'
+          - key: 2
+            value: 'ID098 Inverter bus hardware overvoltage'
+          - key: 4
+            value: 'ID099 BuckBoost hardware overcurrent'
+          - key: 8
+            value: 'ID100 Battery hardware overcurrent'
+          - key: 16
+            value: 'ID101 '
+          - key: 32
+            value: 'ID102 PV hardware overcurrent'
+          - key: 64
+            value: 'ID103 AC output hardware overcurrent'
+          - key: 128
+            value: 'ID104 '
+          - key: 256
+            value: 'ID105 Power meter error'
+          - key: 512
+            value: 'ID106 Serial number model error'
+          - key: 1024
+            value: 'ID107 '
+          - key: 2048
+            value: 'ID108 '
+          - key: 4096
+            value: 'ID109 '
+          - key: 8192
+            value: 'ID110 Overload protection 1'
+          - key: 16384
+            value: 'ID111 Overload protection 2'
+          - key: 32768
+            value: 'ID112 Overload protection 3'
+
+      - name: 'Fault 8'
+        class: ''
+        state_class: ''
+        uom: ''
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        registers: [0x040C]
+        isstr: true
+        lookup:
+          - key: 0
+            value: 'No error'
+          - key: 1
+            value: 'ID113 Overtemperature derating'
+          - key: 2
+            value: 'ID114 Frequency down load'
+          - key: 4
+            value: 'ID115 Frequency loading'
+          - key: 8
+            value: 'ID116 Voltage down load'
+          - key: 16
+            value: 'ID117 Voltage loading'
+          - key: 32
+            value: 'ID118 '
+          - key: 64
+            value: 'ID119 '
+          - key: 128
+            value: 'ID120 '
+          - key: 256
+            value: 'ID121 Lightning protection failure (DC)'
+          - key: 512
+            value: 'ID122 Lightning protection failure (AC)'
+          - key: 1024
+            value: 'ID123 '
+          - key: 2048
+            value: 'ID124 Battery low voltage protection'
+          - key: 4096
+            value: 'ID125 Battery low voltage shutdown'
+          - key: 8192
+            value: 'ID126 Battery low voltage pre-alarm'
+          - key: 16384
+            value: 'ID127 '
+          - key: 32768
+            value: 'ID128 '
+
+      - name: 'Fault 9'
+        class: ''
+        state_class: ''
+        uom: ''
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        isstr: true
+        registers: [0x040D]
+        lookup:
+          - key: 0
+            value: 'No error'
+          - key: 1
+            value: 'ID129 Output hardware overcurrent permanent fault'
+          - key: 2
+            value: 'ID130 Bus overvoltage permanent fault'
+          - key: 4
+            value: 'ID131 Bus hardware over-voltage permanent fault'
+          - key: 8
+            value: 'ID132 PV uneven flow permanent fault'
+          - key: 16
+            value: 'ID133 Battery overcurrent permanent fault in EPS mode'
+          - key: 32
+            value: 'ID134 Output transient overcurrent permanent fault'
+          - key: 64
+            value: 'ID135 Output current unbalance permanent fault'
+          - key: 128
+            value: 'ID136 '
+          - key: 256
+            value: 'ID137 Input mode setting error permanent fault'
+          - key: 512
+            value: 'ID138 Input overcurrent permanent fault'
+          - key: 1024
+            value: 'ID139 Input hardware overcurrent permanent fault'
+          - key: 2048
+            value: 'ID140 Relay permanent fault'
+          - key: 4096
+            value: 'ID141 Bus unbalance permanent fault'
+          - key: 8192
+            value: 'ID142 Lightning protection permanent fault - DC side'
+          - key: 16384
+            value: 'ID143 Lightning protection permanent fault - AC side'
+          - key: 32768
+            value: 'ID144 '
+
+      - name: 'Fault 10'
+        class: ''
+        state_class: ''
+        uom: ''
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        isstr: true
+        registers: [0x040E]
+        lookup:
+          - key: 0
+            value: 'No error'
+          - key: 1
+            value: 'ID145 USB fault'
+          - key: 2
+            value: 'ID146 WIFI fault'
+          - key: 4
+            value: 'ID147 Bluetooth fault'
+          - key: 8
+            value: 'ID148 RTC clock fault'
+          - key: 16
+            value: 'ID149 Communication board EEPROM error'
+          - key: 32
+            value: 'ID150 Communication board FLASH error'
+          - key: 64
+            value: 'ID151 '
+          - key: 128
+            value: 'ID152 Safety regulation version error'
+          - key: 256
+            value: 'ID153 SCI communication error (DC side)'
+          - key: 512
+            value: 'ID154 SCI communication error (AC side)'
+          - key: 1024
+            value: 'ID155 SCI communication error (convergence board side)'
+          - key: 2048
+            value: 'ID156 Software version inconsistency'
+          - key: 4096
+            value: 'ID157 Lithium battery 1 communication error'
+          - key: 8192
+            value: 'ID158 Li-ion battery 2 communication error'
+          - key: 16384
+            value: 'ID159 Lithium battery 3 communication error'
+          - key: 32768
+            value: 'ID160 Lithium battery 4 communication failure'
+
+      - name: 'Fault 11'
+        class: ''
+        state_class: ''
+        uom: ''
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        registers: [0x040F]
+        isstr: true
+        lookup:
+          - key: 0
+            value: 'No error'
+          - key: 1
+            value: 'ID161 Forced shutdown'
+          - key: 2
+            value: 'ID162 Remote shutdown'
+          - key: 4
+            value: 'ID163 Drms0 shutdown'
+          - key: 8
+            value: 'ID164 '
+          - key: 16
+            value: 'ID165 Remote down load'
+          - key: 32
+            value: 'ID166 Logic interface down load'
+          - key: 64
+            value: 'ID167 Anti-Reverse Flow Downgrade'
+          - key: 128
+            value: 'ID168 '
+          - key: 256
+            value: 'ID169 Fan 1 failure'
+          - key: 512
+            value: 'ID170 Fan 2 failure'
+          - key: 1024
+            value: 'ID171 Fan 3 failure'
+          - key: 2048
+            value: 'ID172 Fan 4 failure'
+          - key: 4096
+            value: 'ID173 Fan 5 failure'
+          - key: 8192
+            value: 'ID174 Fan 6 failure'
+          - key: 16384
+            value: 'ID175 Fan 7 fault'
+          - key: 32768
+            value: 'ID176 Meter communication failure'
+
+      - name: 'Fault 12'
+        class: ''
+        state_class: ''
+        uom: ''
+        scale: 1
+        rule: 1
+        icon: 'mdi:wrench'
+        registers: [0x0410]
+        isstr: true
+        lookup:
+          - key: 0
+            value: 'No error'
+          - key: 1
+            value: 'ID177 BMS over-voltage alarm'
+          - key: 2
+            value: 'ID178 BMS undervoltage alarm'
+          - key: 4
+            value: 'ID179 BMS high temperature alarm'
+          - key: 8
+            value: 'ID180 BMS low temperature alarm'
+          - key: 16
+            value: 'ID181 BMS charge/discharge overload alarm'
+          - key: 32
+            value: 'ID182 BMS short circuit alarm'
+          - key: 64
+            value: 'ID183 BMS version inconsistency'
+          - key: 128
+            value: 'ID184 BMS CAN version inconsistency'
+          - key: 256
+            value: 'ID185 BMS CAN version is too low'
+          - key: 512
+            value: 'ID186 '
+          - key: 1024
+            value: 'ID187 '
+          - key: 2048
+            value: 'ID188 '
+          - key: 4096
+            value: 'ID189 Arc device communication failure'
+          - key: 8192
+            value: 'ID190 DC arc alarm fault'
+          - key: 16384
+            value: 'ID191 PID repair failed'
+          - key: 32768
+            value: 'ID192 PLC module heartbeat loss'
diff --git a/bundles/org.openhab.binding.solarman/src/test/java/org/openhab/binding/solarman/internal/DefinitionParserTest.java b/bundles/org.openhab.binding.solarman/src/test/java/org/openhab/binding/solarman/internal/DefinitionParserTest.java
new file mode 100644 (file)
index 0000000..4d09d2d
--- /dev/null
@@ -0,0 +1,109 @@
+/**
+ * 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.solarman.internal;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Objects;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.solarman.internal.defmodel.InverterDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@NonNullByDefault
+public class DefinitionParserTest {
+    private final Logger logger = LoggerFactory.getLogger(DefinitionParserTest.class);
+    private final DefinitionParser definitionParser = new DefinitionParser();
+
+    @Test
+    void testInverterDefinitionsCanBeLoaded() throws IOException {
+        List<String> yamlFiles = scanForYamlFiles("definitions");
+        List<String> definitionIds = extractDefinitionIdFromYamlFiles(yamlFiles);
+
+        assertFalse(definitionIds.isEmpty());
+
+        definitionIds.forEach(definitionId -> {
+            @Nullable
+            InverterDefinition inverterDefinition = definitionParser.parseDefinition(definitionId);
+            assertNotNull(inverterDefinition);
+        });
+    }
+
+    public static List<String> extractDefinitionIdFromYamlFiles(List<String> yamlFiles) {
+        Pattern pattern = Pattern.compile("definitions/(.*)\\.yaml");
+
+        return yamlFiles.stream().map(file -> {
+            Matcher matcher = pattern.matcher(file);
+            return matcher.matches() ? matcher.group(1) : file;
+        }).collect(Collectors.toList());
+    }
+
+    public List<String> scanForYamlFiles(String directoryPath) throws IOException {
+        List<String> yamlFiles = new ArrayList<>();
+        ClassLoader classLoader = Objects.requireNonNull(DefinitionParserTest.class.getClassLoader());
+        Enumeration<URL> resources = classLoader.getResources(directoryPath);
+
+        Collections.list(resources).stream().flatMap(resource -> {
+            try {
+                if (resource.getProtocol().equals("jar")) {
+                    String path = resource.getPath();
+                    String jarPath = path.substring(5, path.indexOf("!"));
+                    try (JarFile jarFile = new JarFile(jarPath)) {
+                        return jarFile.stream()
+                                .filter(e -> e.getName().startsWith(directoryPath) && e.getName().endsWith(".yaml"))
+                                .map(JarEntry::getName);
+                    }
+                } else if (resource.getProtocol().equals("file")) {
+                    return scanDirectory(directoryPath).stream();
+                }
+            } catch (IOException e) {
+                logger.error(e.getMessage(), e);
+            }
+            return Stream.empty();
+        }).forEach(yamlFiles::add);
+
+        return yamlFiles;
+    }
+
+    private static List<String> scanDirectory(String directoryPath) throws IOException {
+        URL url = Objects.requireNonNull(DefinitionParserTest.class.getClassLoader()).getResource(directoryPath);
+        if (url == null) {
+            throw new IllegalArgumentException("Invalid directory path: " + directoryPath);
+        }
+        String[] files = new java.io.File(url.getPath()).list((dir, name) -> name.endsWith(".yaml"));
+        if (files != null) {
+            return Arrays.stream(files).map(file -> directoryPath + "/" + file).toList();
+        }
+        return Collections.emptyList();
+    }
+}
diff --git a/bundles/org.openhab.binding.solarman/src/test/java/org/openhab/binding/solarman/internal/modbus/SolarmanV5ProtocolTest.java b/bundles/org.openhab.binding.solarman/src/test/java/org/openhab/binding/solarman/internal/modbus/SolarmanV5ProtocolTest.java
new file mode 100644 (file)
index 0000000..4577f75
--- /dev/null
@@ -0,0 +1,148 @@
+/**
+ * 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.solarman.internal.modbus;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+
+import javax.validation.constraints.NotNull;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.openhab.binding.solarman.internal.SolarmanLoggerConfiguration;
+import org.openhab.binding.solarman.internal.modbus.exception.SolarmanException;
+
+/**
+ * @author Catalin Sanda - Initial contribution
+ */
+@ExtendWith(MockitoExtension.class)
+@NonNullByDefault
+class SolarmanV5ProtocolTest {
+    SolarmanLoggerConnection solarmanLoggerConnection = (@NotNull SolarmanLoggerConnection) mock(
+            SolarmanLoggerConnection.class);
+
+    private SolarmanLoggerConfiguration loggerConfiguration = new SolarmanLoggerConfiguration("192.168.1.1", 8899,
+            "1234567890", "sg04lp3", 60, null);
+
+    private SolarmanV5Protocol solarmanV5Protocol = new SolarmanV5Protocol(loggerConfiguration);
+
+    @Test
+    void testbuildSolarmanV5Frame() {
+        byte[] requestFrame = solarmanV5Protocol.buildSolarmanV5Frame((byte) 0x03, 0x0000, 0x0020);
+
+        byte[] expectedFrame = { (byte) 0xA5, (byte) 0x17, (byte) 0x00, (byte) 0x10, (byte) 0x45, (byte) 0x00,
+                (byte) 0x00, (byte) 0xD2, (byte) 0x02, (byte) 0x96, (byte) 0x49, (byte) 0x02, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x21, (byte) 0x85, (byte) 0xD2, (byte) 0x9D, (byte) 0x15 };
+
+        assertArrayEquals(requestFrame, expectedFrame);
+    }
+
+    @Test
+    void testReadRegister0x01() throws SolarmanException {
+        // given
+        when(solarmanLoggerConnection.sendRequest(any())).thenReturn(
+                hexStringToByteArray("a5000000000000000000000000000000000000000000000000010301000ac84300000015"));
+
+        // when
+        Map<Integer, byte[]> regValues = solarmanV5Protocol.readRegisters(solarmanLoggerConnection, (byte) 0x03, 1, 1);
+
+        // then
+        assertEquals(1, regValues.size());
+        assertTrue(regValues.containsKey(1));
+        assertEquals("000A", bytesToHex(regValues.get(1)));
+    }
+
+    @Test
+    void testReadRegisters0x02to0x03() throws SolarmanException {
+        // given
+        when(solarmanLoggerConnection.sendRequest(any())).thenReturn(
+                hexStringToByteArray("a5000000000000000000000000000000000000000000000000010302000a000b13f600000015"));
+
+        // when
+        Map<Integer, byte[]> regValues = solarmanV5Protocol.readRegisters(solarmanLoggerConnection, (byte) 0x03, 2, 3);
+
+        // then
+        assertEquals(2, regValues.size());
+        assertTrue(regValues.containsKey(2));
+        assertTrue(regValues.containsKey(3));
+        assertEquals("000A", bytesToHex(regValues.get(2)));
+        assertEquals("000B", bytesToHex(regValues.get(3)));
+    }
+
+    @Test
+    void testReadRegisterSUN10KSG04LP3EUPart1() throws SolarmanException {
+        // given
+        when(solarmanLoggerConnection.sendRequest(any())).thenReturn(hexStringToByteArray(
+                "a53b0010150007482ee38d020121d0060091010000403e486301032800ffffff160a12162420ffffffffffffffffffffffffffffffffffff0001ffff0001ffff000003e81fa45115"));
+
+        // when
+        Map<Integer, byte[]> regValues = solarmanV5Protocol.readRegisters(solarmanLoggerConnection, (byte) 0x03, 0x3c,
+                0x4f);
+
+        // then
+        assertEquals(20, regValues.size());
+        assertTrue(regValues.containsKey(0x3c));
+        assertTrue(regValues.containsKey(0x4f));
+        assertEquals("00FF", bytesToHex(regValues.get(0x3c)));
+        assertEquals("03E8", bytesToHex(regValues.get(0x4f)));
+    }
+
+    @Test
+    void testReadRegisterSUN10KSG04LP3EUPart2() throws SolarmanException {
+        // given
+        when(solarmanLoggerConnection.sendRequest(any())).thenReturn(hexStringToByteArray(
+                "a5330010150008482ee38d020122d0060091010000403e486301032000010000ffffffffffff0001ffffffffffffffffffff0000ffff0011ffffffff3a005715"));
+
+        // when
+        Map<Integer, byte[]> regValues = solarmanV5Protocol.readRegisters(solarmanLoggerConnection, (byte) 0x03, 0x50,
+                0x5f);
+
+        // then
+        assertEquals(16, regValues.size());
+        assertTrue(regValues.containsKey(0x50));
+        assertTrue(regValues.containsKey(0x5f));
+        assertEquals("0001", bytesToHex(regValues.get(0x50)));
+        assertEquals("FFFF", bytesToHex(regValues.get(0x5f)));
+    }
+
+    private static byte[] hexStringToByteArray(String s) {
+        int len = s.length();
+        byte[] data = new byte[len / 2];
+        for (int i = 0; i < len; i += 2) {
+            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
+        }
+        return data;
+    }
+
+    @Nullable
+    private static String bytesToHex(byte @Nullable [] bytes) {
+        if (bytes == null) {
+            return null;
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (byte b : bytes) {
+            sb.append(String.format("%02X", b));
+        }
+        return sb.toString();
+    }
+}
index 1f26e09f2c2d3d438b36b7edbc3a00897f45d599..2e9c12d782a3477fb07aff4d1dbb31b259ed56cd 100644 (file)
     <module>org.openhab.binding.solaredge</module>
     <module>org.openhab.binding.solarforecast</module>
     <module>org.openhab.binding.solarlog</module>
+    <module>org.openhab.binding.solarman</module>
     <module>org.openhab.binding.solarmax</module>
     <module>org.openhab.binding.solarwatt</module>
     <module>org.openhab.binding.solax</module>