From 6ec86dd665fdb1c218cd4446969b7af49f598d32 Mon Sep 17 00:00:00 2001 From: Catalin Sanda Date: Wed, 31 Jul 2024 00:46:28 +0300 Subject: [PATCH] [solarman] Initial contribution (#16835) * Initial commit for the Solarman Binding. Signed-off-by: Catalin Sanda Signed-off-by: Ciprian Pascu --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + bundles/org.openhab.binding.solarman/NOTICE | 13 + .../org.openhab.binding.solarman/README.md | 290 ++++ bundles/org.openhab.binding.solarman/pom.xml | 17 + .../src/main/feature/feature.xml | 9 + .../solarman/internal/DefinitionParser.java | 62 + .../internal/SolarmanBindingConstants.java | 30 + .../internal/SolarmanHandlerFactory.java | 53 + .../internal/SolarmanLoggerConfiguration.java | 74 + .../internal/SolarmanLoggerHandler.java | 225 +++ .../internal/channel/BaseChannelConfig.java | 30 + .../channel/SolarmanChannelManager.java | 100 ++ .../internal/defmodel/InverterDefinition.java | 55 + .../solarman/internal/defmodel/Parameter.java | 46 + .../internal/defmodel/ParameterItem.java | 156 ++ .../solarman/internal/defmodel/Request.java | 74 + .../internal/defmodel/Validation.java | 54 + .../solarman/internal/modbus/CRC16Modbus.java | 45 + .../modbus/SolarmanLoggerConnection.java | 131 ++ .../modbus/SolarmanLoggerConnector.java | 33 + .../internal/modbus/SolarmanV5Protocol.java | 252 +++ .../SolarmanAuthenticationException.java | 43 + .../SolarmanConnectionException.java | 34 + .../modbus/exception/SolarmanException.java | 34 + .../exception/SolarmanProtocolException.java | 30 + .../internal/typeprovider/ChannelUtils.java | 142 ++ .../SolarmanChannelTypeProvider.java | 131 ++ .../updater/SolarmanChannelUpdater.java | 238 +++ .../updater/SolarmanProcessResult.java | 88 ++ .../solarman/internal/util/StreamUtils.java | 85 + .../src/main/resources/OH-INF/addon/addon.xml | 10 + .../OH-INF/config/datetime-channel-config.xml | 46 + .../OH-INF/config/dynamic-channel-config.xml | 46 + .../OH-INF/config/number-channel-config.xml | 46 + .../OH-INF/config/string-channel-config.xml | 46 + .../resources/OH-INF/i18n/solarman.properties | 123 ++ .../main/resources/OH-INF/thing/channels.xml | 31 + .../resources/OH-INF/thing/thing-types.xml | 69 + .../resources/definitions/deye_2mppt.yaml | 137 ++ .../resources/definitions/deye_4mppt.yaml | 470 ++++++ .../resources/definitions/deye_hybrid.yaml | 548 +++++++ .../resources/definitions/deye_sg04lp3.yaml | 523 ++++++ .../resources/definitions/deye_string.yaml | 206 +++ .../resources/definitions/kstar_hybrid.yaml | 793 ++++++++++ .../resources/definitions/sofar_g3hyd.yaml | 1407 +++++++++++++++++ .../definitions/sofar_hyd3k-6k-es.yaml | 1147 ++++++++++++++ .../resources/definitions/sofar_lsw3.yaml | 602 +++++++ .../resources/definitions/sofar_wifikit.yaml | 769 +++++++++ .../resources/definitions/solis_1p8k-5g.yaml | 204 +++ .../resources/definitions/solis_hybrid.yaml | 783 +++++++++ .../definitions/zcs_azzurro-ktl-v3.yaml | 745 +++++++++ .../internal/DefinitionParserTest.java | 109 ++ .../modbus/SolarmanV5ProtocolTest.java | 148 ++ bundles/pom.xml | 1 + 55 files changed, 11589 insertions(+) create mode 100644 bundles/org.openhab.binding.solarman/NOTICE create mode 100644 bundles/org.openhab.binding.solarman/README.md create mode 100644 bundles/org.openhab.binding.solarman/pom.xml create mode 100644 bundles/org.openhab.binding.solarman/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/DefinitionParser.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanBindingConstants.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanHandlerFactory.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanLoggerConfiguration.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanLoggerHandler.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/channel/BaseChannelConfig.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/channel/SolarmanChannelManager.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/InverterDefinition.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/Parameter.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/ParameterItem.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/Request.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/Validation.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/CRC16Modbus.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/SolarmanLoggerConnection.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/SolarmanLoggerConnector.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/SolarmanV5Protocol.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanAuthenticationException.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanConnectionException.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanException.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanProtocolException.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/typeprovider/ChannelUtils.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/typeprovider/SolarmanChannelTypeProvider.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/updater/SolarmanChannelUpdater.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/updater/SolarmanProcessResult.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/util/StreamUtils.java create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/addon/addon.xml create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/datetime-channel-config.xml create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/dynamic-channel-config.xml create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/number-channel-config.xml create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/string-channel-config.xml create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/i18n/solarman.properties create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/thing/channels.xml create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/thing/thing-types.xml create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_2mppt.yaml create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_4mppt.yaml create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_hybrid.yaml create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_sg04lp3.yaml create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_string.yaml create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/definitions/kstar_hybrid.yaml create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_g3hyd.yaml create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_hyd3k-6k-es.yaml create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_lsw3.yaml create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_wifikit.yaml create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/definitions/solis_1p8k-5g.yaml create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/definitions/solis_hybrid.yaml create mode 100644 bundles/org.openhab.binding.solarman/src/main/resources/definitions/zcs_azzurro-ktl-v3.yaml create mode 100644 bundles/org.openhab.binding.solarman/src/test/java/org/openhab/binding/solarman/internal/DefinitionParserTest.java create mode 100644 bundles/org.openhab.binding.solarman/src/test/java/org/openhab/binding/solarman/internal/modbus/SolarmanV5ProtocolTest.java diff --git a/CODEOWNERS b/CODEOWNERS index 0a4f724322f..085876b5408 100755 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -344,6 +344,7 @@ /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 diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index e95b1542ac8..e4acce9bd8d 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1716,6 +1716,11 @@ org.openhab.binding.solarlog ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.solarman + ${project.version} + org.openhab.addons.bundles org.openhab.binding.solarmax diff --git a/bundles/org.openhab.binding.solarman/NOTICE b/bundles/org.openhab.binding.solarman/NOTICE new file mode 100644 index 00000000000..38d625e3492 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/NOTICE @@ -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 index 00000000000..7676fd28a9e --- /dev/null +++ b/bundles/org.openhab.binding.solarman/README.md @@ -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 index 00000000000..a3446256ee2 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 4.3.0-SNAPSHOT + + + org.openhab.binding.solarman + + openHAB Add-ons :: Bundles :: Solarman Binding + + 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 index 00000000000..de6deb616f9 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.solarman/${project.version} + + 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 index 00000000000..93c1fcbb52e --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/DefinitionParser.java @@ -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 index 00000000000..1152bdec7fa --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanBindingConstants.java @@ -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 index 00000000000..543df075bc0 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanHandlerFactory.java @@ -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 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 index 00000000000..4e8b082a14d --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanLoggerConfiguration.java @@ -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 index 00000000000..a5dfb68c682 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/SolarmanLoggerHandler.java @@ -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 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 mergedRequests = !additionalRequests.isBlank() + ? mergeRequests(inverterDefinition.getRequests(), extractAdditionalRequests(additionalRequests)) + : inverterDefinition.getRequests(); + + Map 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 mergedRequests, + Map 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 Map mergeMaps(Map map1, Map map2) { + return Stream.concat(map1.entrySet().stream(), map2.entrySet().stream()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1)); + } + + private Map extractChannelMappingFromChannels(List 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 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 mergeRequests(List requestList1, List requestList2) { + return Stream.concat(requestList1.stream(), requestList2.stream()).collect(Collectors.toList()); + } + + private List 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 setupChannelsForInverterDefinition(InverterDefinition inverterDefinition) { + ThingBuilder thingBuilder = editThing(); + + List oldDynamicChannels = thing.getChannels().stream() + .filter(channel -> channel.getProperties().containsKey(DYNAMIC_CHANNEL)).toList(); + + Map 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 index 00000000000..1eb99cfac9e --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/channel/BaseChannelConfig.java @@ -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 index 00000000000..5fad5438aa8 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/channel/SolarmanChannelManager.java @@ -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 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 configurationMap = objectMapper.convertValue(baseChannelConfig, new TypeReference<>() { + }); + + configurationMap.forEach(configuration::put); + + return configuration; + } + + private String convertRegisters(List 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 index 00000000000..b4c4f958107 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/InverterDefinition.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.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 requests = new ArrayList<>(); + private List parameters = new ArrayList<>(); + + public String getInverterDefinitionId() { + return inverterDefinitionId; + } + + public void setInverterDefinitionId(String inverterDefinitionId) { + this.inverterDefinitionId = inverterDefinitionId; + } + + public List getRequests() { + return requests; + } + + public void setRequests(List requests) { + this.requests = requests; + } + + public List getParameters() { + return parameters; + } + + public void setParameters(List 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 index 00000000000..0e43f9eed4e --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/Parameter.java @@ -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 items = new ArrayList(); + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public List getItems() { + return items; + } + + public void setItems(List 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 index 00000000000..bc5a9cbf7cd --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/ParameterItem.java @@ -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 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 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 getRegisters() { + return registers; + } + + public void setRegisters(List 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 index 00000000000..b6659cd8cf6 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/Request.java @@ -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 index 00000000000..3ca171e78ae --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/defmodel/Validation.java @@ -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 index 00000000000..7bb9658e089 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/CRC16Modbus.java @@ -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 index 00000000000..cf316734929 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/SolarmanLoggerConnection.java @@ -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 index 00000000000..b3ef66bdd8d --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/SolarmanLoggerConnector.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.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 index 00000000000..16f484b6d78 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/SolarmanV5Protocol.java @@ -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 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 + * Solarman V5 Protocol + * + * @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 Function 03 (03hex) Read Holding + * Registers + * + * @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 parseModbusReadHoldingRegistersResponse(byte @Nullable [] frame, int firstReg, + int lastReg) throws SolarmanProtocolException { + int regCount = lastReg - firstReg + 1; + Map 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 index 00000000000..10442c9cb1e --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanAuthenticationException.java @@ -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 index 00000000000..e88d3e0e130 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanConnectionException.java @@ -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 index 00000000000..06d530991c8 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanException.java @@ -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 index 00000000000..4a57fb52c9a --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/modbus/exception/SolarmanProtocolException.java @@ -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 index 00000000000..6a49b99adad --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/typeprovider/ChannelUtils.java @@ -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 index 00000000000..fb27dd593b6 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/typeprovider/SolarmanChannelTypeProvider.java @@ -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 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 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 extractInverterDefinitionId(String file) { + return Stream.of(file).map(INVERTER_DEFINITION_PATTERN::matcher).filter(Matcher::matches) + .map(matcher -> matcher.group(1)).findFirst(); + } + + public Collection 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 index 00000000000..a9aa8ccd8d2 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/updater/SolarmanChannelUpdater.java @@ -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 requests, + SolarmanLoggerConnector solarmanLoggerConnector, SolarmanV5Protocol solarmanV5Protocol, + Map 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 paramToChannelMapping, + Map readRegistersMap) { + paramToChannelMapping.forEach((parameterItem, channelUID) -> { + List 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 registers, + Map 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 registers, + Map 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 registers, + Map 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 registers, + Map 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 registers, Map 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 registers, + Map 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 registers, Map 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 List reversed(List initialList) { + List 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 index 00000000000..5bc66d9154b --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/updater/SolarmanProcessResult.java @@ -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> successfulRequestMap; + private final Map exceptionRequestMap; + + public SolarmanProcessResult() { + this(Collections.emptyMap(), Collections.emptyMap()); + } + + private SolarmanProcessResult(Map> successfulRequestMap, + Map 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 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 getReadRegistersMap() { + return successfulRequestMap.values().stream().reduce(new HashMap<>(), SolarmanProcessResult::mergeMaps); + } + + private static Map mergeMaps(Map map1, Map 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 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 index 00000000000..91bae6c7e52 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/java/org/openhab/binding/solarman/internal/util/StreamUtils.java @@ -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 The type of the first stream elements + * @param The type of the second stream elements + * @param 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 Stream zip(Stream a, Stream b, + BiFunction zipper) { + Objects.requireNonNull(zipper); + Spliterator aSpliterator = Objects.requireNonNull(a).spliterator(); + Spliterator 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 aIterator = Spliterators.iterator(aSpliterator); + Iterator bIterator = Spliterators.iterator(bSpliterator); + Iterator cIterator = new Iterator() { + @Override + public boolean hasNext() { + return aIterator.hasNext() && bIterator.hasNext(); + } + + @Override + public C next() { + return zipper.apply(aIterator.next(), bIterator.next()); + } + }; + + Spliterator 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 The type of the first object + * @param The type of the second object + */ + public record Tuple (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 index 00000000000..44bc7a08c15 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/addon/addon.xml @@ -0,0 +1,10 @@ + + + + binding + Solarman Logger Binding + This is the binding for Solarman Logger + local + 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 index 00000000000..435e457bf8e --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/datetime-channel-config.xml @@ -0,0 +1,46 @@ + + + + + + + The unit of measurement used for this channel + true + + + + The scaling factor, the final value will be scaled by this + true + + + + The type of measurement. See explanation for possible values + true + + + + + + + + + + + + + + + The offset subtracted from the measurement + true + + + + Comma separated list of registers to read for the measurement + true + + + + 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 index 00000000000..326437b10c8 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/dynamic-channel-config.xml @@ -0,0 +1,46 @@ + + + + + + + The unit of measurement used for this channel + true + + + + The scaling factor, the final value will be scaled by this + true + + + + The type of measurement. See explanation for possible values + true + + + + + + + + + + + + + + + The offset subtracted from the measurement + true + + + + Comma separated list of registers to read for the measurement + true + + + + 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 index 00000000000..593853d90a5 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/number-channel-config.xml @@ -0,0 +1,46 @@ + + + + + + + The unit of measurement used for this channel + true + + + + The scaling factor, the final value will be scaled by this + true + + + + The type of measurement. See explanation for possible values + true + + + + + + + + + + + + + + + The offset subtracted from the measurement + true + + + + Comma separated list of registers to read for the measurement + true + + + + 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 index 00000000000..92ba342efe1 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/config/string-channel-config.xml @@ -0,0 +1,46 @@ + + + + + + + The unit of measurement used for this channel + true + + + + The scaling factor, the final value will be scaled by this + true + + + + The type of measurement. See explanation for possible values + true + + + + + + + + + + + + + + + The offset subtracted from the measurement + true + + + + Comma separated list of registers to read for the measurement + true + + + + 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 index 00000000000..58f7dfd6e41 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/i18n/solarman.properties @@ -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 index 00000000000..b8e1d752fe7 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/thing/channels.xml @@ -0,0 +1,31 @@ + + + + + String + + + + + + Number + + + + + + DateTime + + + + + + String + + + + + 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 index 00000000000..4db7f770a2a --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,69 @@ + + + + + + + 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. + + + + network-address + + Hostname or IP address of the Solarman logger. + false + + + + Port of the Solarman logger (default 8899). + 8899 + true + + + + The type of inverter connected to the logger (default deye_sg04lp3). + false + + + + + + + + + + + + + + + + + + + Serial number of the Solarman logger. + false + + + + Interval to query the logger (default 60). + 60 + true + + + + 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 + + true + + + + + 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 index 00000000000..5ce0bd68604 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_2mppt.yaml @@ -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 index 00000000000..2b84b178230 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_4mppt.yaml @@ -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 index 00000000000..b1e25e5fb85 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_hybrid.yaml @@ -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 index 00000000000..7d80fe40264 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_sg04lp3.yaml @@ -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 index 00000000000..566d94f1171 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/deye_string.yaml @@ -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 index 00000000000..49ec6222955 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/kstar_hybrid.yaml @@ -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 index 00000000000..ba30139fef2 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_g3hyd.yaml @@ -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 index 00000000000..1b379dfcaac --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_hyd3k-6k-es.yaml @@ -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 index 00000000000..7edc3eb168a --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_lsw3.yaml @@ -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 index 00000000000..050233f07cd --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/sofar_wifikit.yaml @@ -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 index 00000000000..22ca58d3652 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/solis_1p8k-5g.yaml @@ -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 index 00000000000..78b24eea7ab --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/solis_hybrid.yaml @@ -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 index 00000000000..3ce74e0b668 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/main/resources/definitions/zcs_azzurro-ktl-v3.yaml @@ -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 index 00000000000..4d09d2d246d --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/test/java/org/openhab/binding/solarman/internal/DefinitionParserTest.java @@ -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 yamlFiles = scanForYamlFiles("definitions"); + List definitionIds = extractDefinitionIdFromYamlFiles(yamlFiles); + + assertFalse(definitionIds.isEmpty()); + + definitionIds.forEach(definitionId -> { + @Nullable + InverterDefinition inverterDefinition = definitionParser.parseDefinition(definitionId); + assertNotNull(inverterDefinition); + }); + } + + public static List extractDefinitionIdFromYamlFiles(List 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 scanForYamlFiles(String directoryPath) throws IOException { + List yamlFiles = new ArrayList<>(); + ClassLoader classLoader = Objects.requireNonNull(DefinitionParserTest.class.getClassLoader()); + Enumeration 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 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 index 00000000000..4577f750533 --- /dev/null +++ b/bundles/org.openhab.binding.solarman/src/test/java/org/openhab/binding/solarman/internal/modbus/SolarmanV5ProtocolTest.java @@ -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 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 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 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 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(); + } +} diff --git a/bundles/pom.xml b/bundles/pom.xml index 2e61870247b..7da801270ad 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -380,6 +380,7 @@ org.openhab.binding.solaredge org.openhab.binding.solarforecast org.openhab.binding.solarlog + org.openhab.binding.solarman org.openhab.binding.solarmax org.openhab.binding.solarwatt org.openhab.binding.solax