mirror of
https://github.com/danieldemus/openhab-addons
synced 2026-07-29 12:34:21 +02:00
[modbus.foxinverter] Initial contribution (#19637)
* Initial Contribution: modbus.foxinverter Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
This commit is contained in:
@@ -243,6 +243,7 @@
|
||||
/bundles/org.openhab.binding.minecraft/ @ibaton
|
||||
/bundles/org.openhab.binding.modbus/ @ssalonen
|
||||
/bundles/org.openhab.binding.modbus.e3dc/ @weymann
|
||||
/bundles/org.openhab.binding.modbus.foxinverter/ @holgerfriedrich
|
||||
/bundles/org.openhab.binding.modbus.helioseasycontrols/ @bern77
|
||||
/bundles/org.openhab.binding.modbus.kermi/ @KaaNee
|
||||
/bundles/org.openhab.binding.modbus.sbc/ @fwolter
|
||||
|
||||
@@ -1201,6 +1201,11 @@
|
||||
<artifactId>org.openhab.binding.modbus.e3dc</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.modbus.foxinverter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.modbus.helioseasycontrols</artifactId>
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,184 @@
|
||||
# Modbus FoxInverter Binding
|
||||
|
||||
This binding integrates the FoxESS solar inverters into openHAB.
|
||||
|
||||
## Supported Inverters
|
||||
|
||||
As FoxESS inverters are sold in slightly different configurations, inverters of several brands might be supported.
|
||||
|
||||
This one is tested:
|
||||
|
||||
- Solakon ONE
|
||||
|
||||
Others are untested, but might work:
|
||||
|
||||
- FoxESS Avocado 22 Pro
|
||||
|
||||
## Supported Things
|
||||
|
||||
The binding supports only one thing:
|
||||
|
||||
- `mq2200-inverter`: The FoxESS MQ-2200 inverter (also sold with the product name Solakon ONE or Avocado 22 Pro), connected via Modbus TCP.
|
||||
|
||||
## Preparation
|
||||
|
||||
The data from the inverter is read via Modbus. So you need to configure a Modbus TCP Slave `tcp` as bridge first.
|
||||
The inverter can be connected via LAN or WLAN.
|
||||
As of now, you need to get the IP address of the inverter from your Router.
|
||||
IP is obtained via DHCP.
|
||||
I have not found a way to set a static IP on the Solakon ONE.
|
||||
|
||||
To troubleshoot, it is recommended to open a telnet connection to port 502. If it connects, you have likely found your device with an active Modbus server.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
Once you've configured the Modbus TCP Slave as Bridge, you can configure the mq2200-inverter thing.
|
||||
You just have to select the configured bridge and optionally configure the polling interval.
|
||||
|
||||
The foxinverter binding supports autodiscovery, so if you enable discovery in the bridge options, you can just press the `scan` button to add your inverter to the inbox.
|
||||
|
||||
### FoxESS MQ-2200 Inverter (`mq2200-inverter`)
|
||||
|
||||
| Name | Type | Description | Default | Required | Advanced |
|
||||
|---------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------|---------|----------|----------|
|
||||
| pollInterval | integer | Interval the device is polled in ms. | 5000 | yes | no |
|
||||
| maxTries | integer | Specifies how many times the binding should retry reading data if a read attempt fails. <br/>Set to `1` to disable retries and use a single attempt. | 3 | yes | no |
|
||||
|
||||
## Channels
|
||||
|
||||
> NOTE: All channels are currently read-only. Writing is not yet implemented.
|
||||
|
||||
The `mq2200-inverter` thing has channels that serve the current state of the FoxESS inverter, as you are used to from the vendor app.
|
||||
|
||||
| Channel Type ID | Item Type | Description | Advanced | Channel Group |
|
||||
|------------------------------------|--------------------------|---------------------------------------|-----------|---------------------|
|
||||
| fi-inverter-power | Number:Power | Inverter Active Power | no | Overview |
|
||||
| fi-home-import-power | Number:Power | Power flow with home network | no | Overview |
|
||||
| fi-daily-pv-generation | Number:Energy | Daily PV Generation | no | Overview |
|
||||
| fi-total-pv-generation | Number:Energy | Total PV Generation | no | Overview |
|
||||
| fi-internal-temperature | Number:Temperature | Internal Temperature | yes | Overview |
|
||||
| fi-meter-connected | Contact | Smart Meter is Connected | yes | Overview |
|
||||
| fi-status-alarm | Contact | System Warning | no | Overview |
|
||||
| fi-status-operation | Contact | System in Operational Mode | no | Overview |
|
||||
| fi-status-standby | Contact | System in Standby Mode (not yet off) | yes | Overview |
|
||||
| fi-pv-power | Number:Power | PV Power | no | MPPT Information |
|
||||
| fi-mppt1-voltage | Number:ElectricPotential | MPPT1 Voltage | yes | MPPT Information |
|
||||
| fi-mppt1-current | Number:ElectricCurrent | MPPT1 Current | yes | MPPT Information |
|
||||
| fi-mppt1-power | Number:Power | MPPT1 Power | no | MPPT Information |
|
||||
| fi-mppt2-voltage | Number:ElectricPotential | MPPT2 Voltage | yes | MPPT Information |
|
||||
| fi-mppt2-current | Number:ElectricCurrent | MPPT2 Current | yes | MPPT Information |
|
||||
| fi-mppt2-power | Number:Power | MPPT2 Power | no | MPPT Information |
|
||||
| fi-mppt3-voltage | Number:ElectricPotential | MPPT3 Voltage | yes | MPPT Information |
|
||||
| fi-mppt3-current | Number:ElectricCurrent | MPPT3 Current | yes | MPPT Information |
|
||||
| fi-mppt3-power | Number:Power | MPPT3 Power | no | MPPT Information |
|
||||
| fi-mppt4-voltage | Number:ElectricPotential | MPPT4 Voltage | yes | MPPT Information |
|
||||
| fi-mppt4-current | Number:ElectricCurrent | MPPT4 Current | yes | MPPT Information |
|
||||
| fi-mppt4-power | Number:Power | MPPT4 Power | no | MPPT Information |
|
||||
| fi-battery-voltage | Number:ElectricPotential | Battery Voltage | yes | Battery Information |
|
||||
| fi-battery-current | Number:ElectricCurrent | Battery Current | yes | Battery Information |
|
||||
| fi-battery-level | Number:Dimensionless | Battery Level | no | Battery Information |
|
||||
| fi-battery-charging-power | Number:Power | Battery Charging Power | no | Battery Information |
|
||||
| fi-battery-temperature | Number:Temperature | Internal Temperature | no | Battery Information |
|
||||
| fi-battery-minimum-soc | Number:Dimensionless | Battery Min Charging Level | yes | Battery Information |
|
||||
| fi-battery-maximum-soc | Number:Dimensionless | Battery Max Charging Level | yes | Battery Information |
|
||||
| fi-battery-minimum-soc-on-grid | Number:Dimensionless | Battery Min Charging Level on Grid | yes | Battery Information |
|
||||
| fi-phase-a-voltage | Number:ElectricPotential | Phase A Voltage | yes | Grid Information |
|
||||
| fi-phase-b-voltage | Number:ElectricPotential | Phase B Voltage | yes | Grid Information |
|
||||
| fi-phase-c-voltage | Number:ElectricPotential | Phase C Voltage | yes | Grid Information |
|
||||
| fi-grid-frequency | Number:Frequency | Grid Frequency | yes | Grid Information |
|
||||
| fi-grid-export-power | Number:Power | Overall Power Export to Grid | no | Grid Information |
|
||||
| fi-status-on-grid | Contact | Power Grid Available | yes | Grid Information |
|
||||
| fi-eps-output | Switch | Enable EPS Output | no | EPS |
|
||||
| fi-eps-export-power | Number:Power | EPS Output Power | no | EPS |
|
||||
|
||||
## Full Example
|
||||
|
||||
This example shows how to configure a FoxESS inverter connected via Modbus and uses the most common channels.
|
||||
|
||||
### mq2200.things
|
||||
|
||||
```java
|
||||
Bridge modbus:tcp:powerplant [ host="10.0.0.2", port=502, id=1, enableDiscovery=false ] {
|
||||
Thing mq2200-inverter inverter "FoxESS Inverter" [ pollInterval=5000 ]
|
||||
}
|
||||
```
|
||||
|
||||
### mq2200.items
|
||||
|
||||
```java
|
||||
// Groups
|
||||
Group inverter "FoxESS Inverter" <solarplant> ["Inverter"]
|
||||
Group overview "Overview" <line> (inverter)
|
||||
Group mppt "MPPT Information" <solarplant> (inverter)
|
||||
Group batteryInformation "Battery information" <battery> (inverter)
|
||||
Group gridInformation "Grid information" <energy> (inverter)
|
||||
Group eps "Emergency Power Supply" <energy> (inverter)
|
||||
|
||||
// Overview
|
||||
Number:Power active_power "Inverter Power" <line> (overview) ["Measurement", "Power"] {channel="modbus:mq2200-inverter:powerplant:inverter:fi-overview#fi-inverter-power"}
|
||||
Number:Power battery_power "Home Import Power" <line> (batteryInformation,overview) ["Measurement", "Power"] {channel="modbus:mq2200-inverter:powerplant:inverter:fi-overview#fi-home-import-power"}
|
||||
Number:Energy daily_pv_generation "Daily PV Generation" <line> (overview) ["Measurement", "Energy"] {channel="modbus:mq2200-inverter:powerplant:inverter:fi-overview#fi-daily-pv-generation"}
|
||||
Number:Energy total_pv_generation "Total PV Generation" <line> (overview) ["Measurement", "Energy"] {channel="modbus:mq2200-inverter:powerplant:inverter:fi-overview#fi-total-pv-generation"}
|
||||
Number:Temperature internal_temperature "Internal Temperature" <temperature> (overview) ["Measurement", "Temperature"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-overview#fi-internal-temperature"}
|
||||
Contact meter_connected "Meter is Connected" (overview) ["Measurement"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-overview#fi-meter-connected"}
|
||||
Contact status_alarm "Status Alarm" (overview) ["Measurement"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-overview#fi-status-alarm"}
|
||||
Contact status_operation "Status Operation" (overview) ["Measurement"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-overview#fi-status-operation"}
|
||||
Contact status_standby "Status Standby" (overview) ["Measurement"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-overview#fi-status-standby"}
|
||||
|
||||
// MPPT information
|
||||
Number:Power pv_power "PV Power" <line> (mppt,overview) ["Measurement", "Power"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-mppt-information#fi-pv-power"}
|
||||
Number:ElectricPotential mppt1_voltage "MPPT1 Voltage" <energy> (mppt) ["Measurement", "Voltage"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-mppt-information#fi-mppt1-voltage"}
|
||||
Number:ElectricCurrent mppt1_current "MPPT1 Current" <energy> (mppt) ["Measurement", "Current"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-mppt-information#fi-mppt1-current"}
|
||||
Number:Power mppt1_power "MPPT1 Power" <line> (mppt) ["Measurement", "Power"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-mppt-information#fi-mppt1-power"}
|
||||
Number:ElectricPotential mppt2_voltage "MPPT2 Voltage" <energy> (mppt) ["Measurement", "Voltage"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-mppt-information#fi-mppt2-voltage"}
|
||||
Number:ElectricCurrent mppt2_current "MPPT2 Current" <energy> (mppt) ["Measurement", "Current"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-mppt-information#fi-mppt2-current"}
|
||||
Number:Power mppt2_power "MPPT2 Power" <line> (mppt) ["Measurement", "Power"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-mppt-information#fi-mppt2-power"}
|
||||
Number:ElectricPotential mppt3_voltage "MPPT3 Voltage" <energy> (mppt) ["Measurement", "Voltage"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-mppt-information#fi-mppt3-voltage"}
|
||||
Number:ElectricCurrent mppt3_current "MPPT3 Current" <energy> (mppt) ["Measurement", "Current"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-mppt-information#fi-mppt3-current"}
|
||||
Number:Power mppt3_power "MPPT3 Power" <line> (mppt) ["Measurement", "Power"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-mppt-information#fi-mppt3-power"}
|
||||
Number:ElectricPotential mppt4_voltage "MPPT4 Voltage" <energy> (mppt) ["Measurement", "Voltage"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-mppt-information#fi-mppt4-voltage"}
|
||||
Number:ElectricCurrent mppt4_current "MPPT4 Current" <energy> (mppt) ["Measurement", "Current"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-mppt-information#fi-mppt4-current"}
|
||||
Number:Power mppt4_power "MPPT4 Power" <line> (mppt) ["Measurement", "Power"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-mppt-information#fi-mppt4-power"}
|
||||
|
||||
// Battery information
|
||||
Number:Dimensionless battery_level "Battery Level [%.0f %%]" <battery> (batteryInformation,overview) ["Measurement", "Energy"] {channel="modbus:mq2200-inverter:powerplant:inverter:fi-battery-information#fi-battery-level", unit="%"}
|
||||
Number:Power battery_charging_power "Battery Charging Power" <line> (batteryInformation,overview) ["Measurement", "Power"] {channel="modbus:mq2200-inverter:powerplant:inverter:fi-battery-information#fi-battery-charging-power"}
|
||||
Number:ElectricPotential battery_voltage "Battery Voltage" <energy> (batteryInformation) ["Measurement", "Voltage"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-battery-information#fi-battery-voltage"}
|
||||
Number:ElectricCurrent battery_current "Battery Current" <energy> (batteryInformation) ["Measurement", "Current"]
|
||||
{channel="modbus:mq2200-inverter:powerplant:inverter:fi-battery-information#fi-battery-current"}
|
||||
Number:Temperature battery_temperature "Battery Temperature" <temperature> (overview) ["Measurement", "Temperature"]{channel="modbus:mq2200-inverter:powerplant:inverter:fi-battery-information#fi-battery-temperature"}
|
||||
Number:Dimensionless battery_minimum_soc "Battery Min Charging Level [%.0f %%]" <battery> (batteryInformation) ["Control"] {channel="modbus:mq2200-inverter:powerplant:inverter:fi-battery-information#fi-battery-minimum-soc", unit="%"}
|
||||
Number:Dimensionless battery_maximum_soc "Battery Max Charging Level [%.0f %%]" <battery> (batteryInformation) ["Control"] {channel="modbus:mq2200-inverter:powerplant:inverter:fi-battery-information#fi-battery-maximum-soc", unit="%"}
|
||||
Number:Dimensionless battery_minimum_soc_on_grid "Battery Min Charging Level on Grid" <battery> (batteryInformation) ["Control"] {channel="modbus:mq2200-inverter:powerplant:inverter:fi-battery-information#fi-battery-minimum-soc-on-grid", unit="%"}
|
||||
|
||||
// Grid information
|
||||
Number:ElectricPotential phase_a_voltage "Phase A Voltage" <energy> (gridInformation) ["Measurement"] {channel="modbus:mq2200-inverter:powerplant:inverter:fi-grid-information#fi-phase-a-voltage"}
|
||||
Number:ElectricPotential phase_b_voltage "Phase B Voltage" <energy> (gridInformation) ["Measurement"] {channel="modbus:mq2200-inverter:powerplant:inverter:fi-grid-information#fi-phase-b-voltage"}
|
||||
Number:ElectricPotential phase_c_voltage "Phase C Voltage" <energy> (gridInformation) ["Measurement"] {channel="modbus:mq2200-inverter:powerplant:inverter:fi-grid-information#fi-phase-c-voltage"}
|
||||
Number:Frequency grid_frequency "Grid Frequency" <pump> (gridInformation) ["Measurement"] {channel="modbus:mq2200-inverter:powerplant:inverter:fi-grid-information#fi-grid-frequency"}
|
||||
Number:Power grid_export_power "Export Power" <line> (gridInformation) ["Measurement", "Power"] {channel="modbus:mq2200-inverter:powerplant:inverter:fi-grid-information#fi-grid-export-power"}
|
||||
Contact status_on_grid "Status Power Grid Available" (gridInformation) ["Measurement"] {channel="modbus:mq2200-inverter:powerplant:inverter:fi-grid-information#fi-status-on-grid"}
|
||||
|
||||
// EPS
|
||||
Switch eps_output "EPS Output Enabled" (eps) ["Control"] {channel="modbus:mq2200-inverter:powerplant:inverter:fi-emergency-power-supply#fi-eps-output"}
|
||||
Number:Power eps_export_power "EPS Export Power" <energy> (eps) ["Measurement", "Power"] {channel="modbus:mq2200-inverter:powerplant:inverter:fi-emergency-power-supply#fi-eps-export-power"}
|
||||
```
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.modbus.foxinverter</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Modbus FoxInverter Binding</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.modbus</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.modbus.foxinverter.internal;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Constants for converting values.
|
||||
*
|
||||
* @author Sönke Küper - Initial contribution
|
||||
* @author Holger Friedrich - Carry over from SunGrow binding, additions
|
||||
*/
|
||||
@NonNullByDefault
|
||||
final class ConversionConstants {
|
||||
|
||||
private ConversionConstants() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplicand for 0.1.
|
||||
*/
|
||||
static final BigDecimal DIV_BY_TEN = new BigDecimal(BigInteger.ONE, 1);
|
||||
static final BigDecimal DIV_BY_HUNDRED = new BigDecimal(BigInteger.ONE, 2);
|
||||
static final BigDecimal DIV_BY_THOUSAND = new BigDecimal(BigInteger.ONE, 3);
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.modbus.foxinverter.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link MQ2200InverterConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Holger Friedrich - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MQ2200InverterConfiguration {
|
||||
|
||||
public int pollInterval;
|
||||
public int maxTries;
|
||||
}
|
||||
+450
@@ -0,0 +1,450 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.modbus.foxinverter.internal;
|
||||
|
||||
import static org.openhab.binding.modbus.foxinverter.internal.ModbusFoxInverterBindingConstants.*;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.modbus.handler.BaseModbusThingHandler;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.openhab.core.io.transport.modbus.AsyncModbusFailure;
|
||||
import org.openhab.core.io.transport.modbus.AsyncModbusReadResult;
|
||||
import org.openhab.core.io.transport.modbus.ModbusBitUtilities;
|
||||
import org.openhab.core.io.transport.modbus.ModbusConstants;
|
||||
import org.openhab.core.io.transport.modbus.ModbusReadFunctionCode;
|
||||
import org.openhab.core.io.transport.modbus.ModbusReadRequestBlueprint;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
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.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.osgi.framework.Bundle;
|
||||
import org.osgi.framework.FrameworkUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MQ2200InverterHandler} is responsible for reading the Modbus values of the
|
||||
* FoxESS MQ-2200 inverter.
|
||||
*
|
||||
* @author Holger Friedrich - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MQ2200InverterHandler extends BaseModbusThingHandler {
|
||||
// used by Register definition to mark a new request must be created,
|
||||
// required if you are not allowed to read certain registers ranges
|
||||
public static final int ENFORCE_NEW_REQUEST = -1;
|
||||
|
||||
private static final int INVALID_ACTIVE_POWER_VALUE = -30000;
|
||||
private static final int STATUS_BIT_STANDBY = 0x01;
|
||||
private static final int STATUS_BIT_OPERATION = 0x04;
|
||||
private static final int STATUS_BIT_FAULT = 0x40;
|
||||
private static final int EPS_OUTPUT_ON_VALUE = 2;
|
||||
private static final String HIDDEN_CHANNEL_PREFIX = "hidden-";
|
||||
private static final String CHANNEL_ALARM1 = "alarm1";
|
||||
private static final String CHANNEL_ALARM2 = "alarm2";
|
||||
private static final String CHANNEL_ALARM3 = "alarm3";
|
||||
private static final String CHANNEL_EPS_OUTPUT = "eps-output";
|
||||
private static final String CHANNEL_GRID_FREQUENCY = "grid-frequency";
|
||||
private static final String CHANNEL_INVERTER_POWER = "inverter-power";
|
||||
private static final String CHANNEL_STATUS1 = "status1";
|
||||
private static final String CHANNEL_STATUS_ALARM = "status-alarm";
|
||||
private static final String CHANNEL_STATUS_ON_GRID = "status-on-grid";
|
||||
private static final String CHANNEL_STATUS_OPERATION = "status-operation";
|
||||
private static final String CHANNEL_STATUS_STANDBY = "status-standby";
|
||||
|
||||
private static final class ModbusRequest {
|
||||
|
||||
private final Deque<MQ2200InverterRegisters> registers;
|
||||
private final ModbusReadRequestBlueprint blueprint;
|
||||
|
||||
public ModbusRequest(Deque<MQ2200InverterRegisters> registers, int slaveId, int tries) {
|
||||
this.registers = registers;
|
||||
this.blueprint = initReadRequest(registers, slaveId, tries);
|
||||
}
|
||||
|
||||
private ModbusReadRequestBlueprint initReadRequest(Deque<MQ2200InverterRegisters> registers, int slaveId,
|
||||
int tries) {
|
||||
int firstRegister = Objects.requireNonNull(registers.getFirst()).getRegisterNumber();
|
||||
int lastRegister = Objects.requireNonNull(registers.getLast()).getRegisterNumber();
|
||||
int length = lastRegister - firstRegister + Objects.requireNonNull(registers.getLast()).getRegisterCount();
|
||||
assert length <= ModbusConstants.MAX_REGISTERS_READ_COUNT;
|
||||
|
||||
return new ModbusReadRequestBlueprint(slaveId, ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS,
|
||||
firstRegister, length, tries);
|
||||
}
|
||||
}
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MQ2200InverterHandler.class);
|
||||
private LocaleProvider localeProvider;
|
||||
private TranslationProvider translationProvider;
|
||||
private final Bundle bundle;
|
||||
|
||||
private List<ModbusRequest> modbusRequests = new ArrayList<>();
|
||||
|
||||
private volatile int[] alarm = new int[3]; // cache status of the 3 alarm registers
|
||||
private volatile boolean alarmState = false; // previous alarm state
|
||||
private volatile boolean statusFault = false; // cache status of fault bit
|
||||
|
||||
public MQ2200InverterHandler(Thing thing, final TranslationProvider translationProvider,
|
||||
final LocaleProvider localeProvider) {
|
||||
super(thing);
|
||||
this.localeProvider = localeProvider;
|
||||
this.translationProvider = translationProvider;
|
||||
bundle = FrameworkUtil.getBundle(this.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the inverter Registers into multiple ModbusRequest, to ensure the max request size.
|
||||
*/
|
||||
private List<ModbusRequest> buildRequests(int tries) {
|
||||
final List<ModbusRequest> requests = new ArrayList<>();
|
||||
Deque<MQ2200InverterRegisters> currentRequest = new ArrayDeque<>();
|
||||
int currentRequestFirstRegister = 0;
|
||||
|
||||
for (MQ2200InverterRegisters channel : MQ2200InverterRegisters.values()) {
|
||||
logger.debug("Evaluating register {}", channel.name());
|
||||
|
||||
if (currentRequest.isEmpty()) {
|
||||
currentRequest.add(channel);
|
||||
currentRequestFirstRegister = channel.getRegisterNumber();
|
||||
} else if (ENFORCE_NEW_REQUEST == channel.getRegisterNumber()) {
|
||||
// marker, start new request
|
||||
if (!currentRequest.isEmpty()) {
|
||||
requests.add(new ModbusRequest(currentRequest, getSlaveId(), tries));
|
||||
}
|
||||
currentRequest = new ArrayDeque<>();
|
||||
} else {
|
||||
int sizeWithRegisterAdded = channel.getRegisterNumber() - currentRequestFirstRegister
|
||||
+ channel.getRegisterCount();
|
||||
if (sizeWithRegisterAdded > ModbusConstants.MAX_REGISTERS_READ_COUNT) {
|
||||
requests.add(new ModbusRequest(currentRequest, getSlaveId(), tries));
|
||||
currentRequest = new ArrayDeque<>();
|
||||
|
||||
currentRequest.add(channel);
|
||||
currentRequestFirstRegister = channel.getRegisterNumber();
|
||||
|
||||
logger.debug("Starting new Modbus request template due to size limit, first register {} ({})",
|
||||
channel.name(), currentRequestFirstRegister);
|
||||
} else {
|
||||
currentRequest.add(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentRequest.isEmpty()) {
|
||||
requests.add(new ModbusRequest(currentRequest, getSlaveId(), tries));
|
||||
}
|
||||
logger.debug("Created {} Modbus request templates.", requests.size());
|
||||
return requests;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("Inverter {}, channel {} received command {}", getThing().getUID(), channelUID, command);
|
||||
|
||||
if (command instanceof RefreshType && !this.modbusRequests.isEmpty()) {
|
||||
logger.info("REFRESH command received, submitting one-time polls for all registers.");
|
||||
|
||||
readStaticData();
|
||||
|
||||
for (ModbusRequest request : this.modbusRequests) {
|
||||
submitOneTimePoll(request.blueprint,
|
||||
(AsyncModbusReadResult result) -> this.readSuccessful(request, result), this::readError);
|
||||
}
|
||||
} else {
|
||||
// TODO implement sending commands to device, maybe safeguarded by checking the model info first
|
||||
logger.warn("Command {} on channel {} is not supported yet.", command, channelUID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modbusInitialize() {
|
||||
final MQ2200InverterConfiguration config = getConfigAs(MQ2200InverterConfiguration.class);
|
||||
|
||||
if (config.pollInterval <= 0) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
getTranslation("offline.config.poll_interval", config.pollInterval));
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.maxTries <= 0) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
getTranslation("offline.config.max_tries", config.maxTries));
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateStatus(ThingStatus.UNKNOWN);
|
||||
// read static data, store into properties
|
||||
getThing().setProperties(readStaticData());
|
||||
|
||||
// setup regular polling
|
||||
this.modbusRequests = this.buildRequests(config.maxTries);
|
||||
|
||||
for (ModbusRequest request : modbusRequests) {
|
||||
registerRegularPoll(request.blueprint, config.pollInterval, 0,
|
||||
(AsyncModbusReadResult result) -> this.readSuccessful(request, result), this::readError);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> readStaticData() {
|
||||
Map<String, String> properties = new java.util.concurrent.ConcurrentHashMap<>();
|
||||
try {
|
||||
// Initial poll of static info
|
||||
// a) manufacturer, model, serial number
|
||||
submitOneTimePoll(new ModbusReadRequestBlueprint(getSlaveId(),
|
||||
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, 30000, 16 * 3, 3),
|
||||
(AsyncModbusReadResult result) -> {
|
||||
logger.trace("Initial poll successful {}", result);
|
||||
byte[] res = result.getRegisters().get().getBytes();
|
||||
String modelInfo = new String(res, 0, 16).trim();
|
||||
String serialNo = new String(res, 16, 16).trim();
|
||||
String manufacturerId = new String(res, 32, 16).trim();
|
||||
|
||||
logger.debug("Inverter Model: {}, S/N: {}, Manufacturer ID: {}", modelInfo, serialNo,
|
||||
manufacturerId);
|
||||
if (!modelInfo.isEmpty()) {
|
||||
properties.put(PROPERTY_MODEL_NAME, modelInfo);
|
||||
}
|
||||
if (!serialNo.isEmpty()) {
|
||||
properties.put(PROPERTY_SERIAL_NO, serialNo);
|
||||
}
|
||||
if (!manufacturerId.isEmpty()) {
|
||||
properties.put(PROPERTY_MANUFACTURER_ID, manufacturerId);
|
||||
}
|
||||
}, (AsyncModbusFailure<ModbusReadRequestBlueprint> error) -> {
|
||||
logger.warn("Initial read of model information failed", error.getCause());
|
||||
});
|
||||
Thread.sleep(100);
|
||||
// b) firmware versions
|
||||
submitOneTimePoll(new ModbusReadRequestBlueprint(getSlaveId(),
|
||||
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, 36001, 3, 3), (AsyncModbusReadResult result) -> {
|
||||
byte[] res = result.getRegisters().get().getBytes();
|
||||
properties.put(PROPERTY_FIRMWARE_WR, String.format("%d.%03d", res[0], res[1]));
|
||||
// TODO The mapping for PROPERTY_FIRMWARE_PV is not confirmed, PV FW could also be stored in
|
||||
// res[4,5]
|
||||
properties.put(PROPERTY_FIRMWARE_PV, String.format("%d.%03d", res[2], res[3]));
|
||||
}, (AsyncModbusFailure<ModbusReadRequestBlueprint> error) -> {
|
||||
// reading properties is not critical, just log at debug level
|
||||
logger.debug("Reading firmware WR/PV failed", error.getCause());
|
||||
});
|
||||
Thread.sleep(100);
|
||||
// c) firmware versions
|
||||
submitOneTimePoll(new ModbusReadRequestBlueprint(getSlaveId(),
|
||||
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, 37003, 1, 3), (AsyncModbusReadResult result) -> {
|
||||
byte[] res = result.getRegisters().get().getBytes();
|
||||
properties.put(PROPERTY_FIRMWARE_BMS, String.format("%d.%03d", res[0], res[1]));
|
||||
}, (AsyncModbusFailure<ModbusReadRequestBlueprint> error) -> {
|
||||
// reading properties is not critical, just log at debug level
|
||||
logger.debug("Reading firmware BMS failed", error.getCause());
|
||||
});
|
||||
Thread.sleep(100);
|
||||
// d) rated power, max active power
|
||||
submitOneTimePoll(new ModbusReadRequestBlueprint(getSlaveId(),
|
||||
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, 39053, 2 * 2, 3),
|
||||
(AsyncModbusReadResult result) -> {
|
||||
ModbusBitUtilities.extractStateFromRegisters(result.getRegisters().get(), 0,
|
||||
ModbusConstants.ValueType.UINT32).ifPresent(v -> {
|
||||
properties.put(PROPERTY_RATED_POWER, Objects.toString(v.toBigDecimal()) + " W");
|
||||
});
|
||||
ModbusBitUtilities.extractStateFromRegisters(result.getRegisters().get(), 2,
|
||||
ModbusConstants.ValueType.UINT32).ifPresent(v -> {
|
||||
properties.put(PROPERTY_MAX_ACTIVE_POWER,
|
||||
Objects.toString(v.toBigDecimal()) + " W");
|
||||
});
|
||||
}, (AsyncModbusFailure<ModbusReadRequestBlueprint> error) -> {
|
||||
// reading properties is not critical, just log at debug level
|
||||
logger.debug("Reading rated/max active power failed", error.getCause());
|
||||
});
|
||||
// wait for async polls to complete; waiting is not ideal but not worth implementing a complex logic here
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("Interrupted while reading device properties");
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
private void processAlarmState() {
|
||||
boolean currentAlarmState = (alarm[0] != 0) || (alarm[1] != 0) || (alarm[2] != 0) || statusFault;
|
||||
if (currentAlarmState != alarmState) {
|
||||
alarmState = currentAlarmState;
|
||||
if (alarmState) {
|
||||
logger.warn("Inverter {} is in Alarm State: Alarm1=0x{}, Alarm2=0x{}, Alarm3=0x{}, StatusFault={}",
|
||||
getThing().getUID(), Integer.toHexString(alarm[0]), Integer.toHexString(alarm[1]),
|
||||
Integer.toHexString(alarm[2]), statusFault);
|
||||
// indicate severe error also by setting the ThingStatus
|
||||
// (UNKNOWN seems more appropriate than OFFLINE, as the device still gets commands)
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
} else {
|
||||
logger.info("Inverter {}, alarm resolved", getThing().getUID());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
OpenClosedType state = alarmState ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
|
||||
logger.debug("{} {} -> {}", CHANNEL_STATUS_ALARM.toUpperCase(), alarmState, state);
|
||||
updateState(new ChannelUID(thing.getUID(), "fi-overview", "fi-" + CHANNEL_STATUS_ALARM), state);
|
||||
}
|
||||
|
||||
private void processHiddenChannel(MQ2200InverterRegisters channel, org.openhab.core.types.State v) {
|
||||
// this block deals with channels which are not directly exposed, but
|
||||
// used to update other channels
|
||||
logger.trace("Update on internal channel {} to {}", channel.getChannelName(), v);
|
||||
DecimalType d = v.as(DecimalType.class);
|
||||
if (d == null) {
|
||||
logger.warn("Internal channel {} is not DecimalType, cannot process", channel.getChannelName());
|
||||
} else {
|
||||
int i = d.intValue();
|
||||
switch (channel.getChannelName().substring(HIDDEN_CHANNEL_PREFIX.length())) {
|
||||
// active power reports -30000 W during startup, to be suppressed
|
||||
case CHANNEL_INVERTER_POWER:
|
||||
if (i != INVALID_ACTIVE_POWER_VALUE) {
|
||||
logger.debug("{} {}", CHANNEL_INVERTER_POWER.toUpperCase(), v);
|
||||
updateState(new ChannelUID(thing.getUID(), "fi-" + channel.getChannelGroup(),
|
||||
"fi-" + CHANNEL_INVERTER_POWER), v);
|
||||
}
|
||||
break;
|
||||
// grid frequency is used to create STATUS_ON_GRID
|
||||
case CHANNEL_GRID_FREQUENCY:
|
||||
logger.debug("{} {}", CHANNEL_GRID_FREQUENCY.toUpperCase(), v);
|
||||
updateState(new ChannelUID(thing.getUID(), "fi-" + channel.getChannelGroup(),
|
||||
"fi-" + CHANNEL_GRID_FREQUENCY), v);
|
||||
OpenClosedType state = (i >= 1) ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
|
||||
logger.debug("{} {} -> {}", CHANNEL_STATUS_ON_GRID.toUpperCase(), i >= 1, state);
|
||||
updateState(new ChannelUID(thing.getUID(), "fi-" + channel.getChannelGroup(),
|
||||
"fi-" + CHANNEL_STATUS_ON_GRID), state);
|
||||
break;
|
||||
// status seems to use only 3 bits, export a separate channel for each
|
||||
case CHANNEL_STATUS1:
|
||||
boolean statusStandby = (i & STATUS_BIT_STANDBY) != 0;
|
||||
boolean statusOperation = (i & STATUS_BIT_OPERATION) != 0;
|
||||
OpenClosedType stateUpdate = statusStandby ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
|
||||
logger.debug("{} {} -> {}", CHANNEL_STATUS_STANDBY.toUpperCase(), statusStandby, stateUpdate);
|
||||
updateState(new ChannelUID(thing.getUID(), "fi-" + channel.getChannelGroup(),
|
||||
"fi-" + CHANNEL_STATUS_STANDBY), stateUpdate);
|
||||
stateUpdate = statusOperation ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
|
||||
logger.debug("{} {} -> {}", CHANNEL_STATUS_OPERATION.toUpperCase(), statusOperation, stateUpdate);
|
||||
updateState(new ChannelUID(thing.getUID(), "fi-" + channel.getChannelGroup(),
|
||||
"fi-" + CHANNEL_STATUS_OPERATION), stateUpdate);
|
||||
// this is a global variable, as the fault state is stored and evaluated for alarm output
|
||||
statusFault = (i & STATUS_BIT_FAULT) != 0;
|
||||
processAlarmState();
|
||||
break;
|
||||
// alarm states are currently
|
||||
case CHANNEL_ALARM1:
|
||||
alarm[0] = i;
|
||||
processAlarmState();
|
||||
break;
|
||||
case CHANNEL_ALARM2:
|
||||
alarm[1] = i;
|
||||
processAlarmState();
|
||||
break;
|
||||
case CHANNEL_ALARM3:
|
||||
alarm[2] = i;
|
||||
processAlarmState();
|
||||
break;
|
||||
// EPS output state is set to 0 or 2 by the app
|
||||
case CHANNEL_EPS_OUTPUT:
|
||||
OnOffType epsState = OnOffType.OFF;
|
||||
if (i == EPS_OUTPUT_ON_VALUE) {
|
||||
epsState = OnOffType.ON;
|
||||
}
|
||||
logger.debug("{} {}", CHANNEL_EPS_OUTPUT.toUpperCase(), epsState);
|
||||
updateState(new ChannelUID(thing.getUID(), "fi-" + channel.getChannelGroup(),
|
||||
"fi-" + CHANNEL_EPS_OUTPUT), epsState);
|
||||
break;
|
||||
default:
|
||||
logger.warn("Unhandled internal channel {}", channel.getChannelName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readSuccessful(ModbusRequest request, AsyncModbusReadResult result) {
|
||||
logger.trace("readSuccessful {}: {}", request, result);
|
||||
result.getRegisters().ifPresent(registers -> {
|
||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
// if alarm is set, do not set ONLINE as is would override the alarm indication
|
||||
if (!alarmState) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
|
||||
int firstRegister = Objects.requireNonNull(request.registers.getFirst()).getRegisterNumber();
|
||||
|
||||
for (MQ2200InverterRegisters channel : request.registers) {
|
||||
int index = channel.getRegisterNumber() - firstRegister;
|
||||
logger.debug("{} {}", channel.toString(), ModbusBitUtilities
|
||||
.extractStateFromRegisters(registers, index, channel.getType()).map(channel::createState));
|
||||
ModbusBitUtilities.extractStateFromRegisters(registers, index, channel.getType())
|
||||
.map(channel::createState).ifPresentOrElse(v -> {
|
||||
if (!channel.getChannelName().startsWith(HIDDEN_CHANNEL_PREFIX)) {
|
||||
updateState(createChannelUid(channel), v);
|
||||
} else {
|
||||
processHiddenChannel(channel, v);
|
||||
}
|
||||
}, () -> {
|
||||
logger.warn("Could not extract state for channel {}", channel.getChannelName());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void readError(AsyncModbusFailure<ModbusReadRequestBlueprint> error) {
|
||||
// TODO improve error handling, stop regular polling on repeated errors, sporadic polling only
|
||||
this.logger.debug("Failed to get Modbus data", error.getCause());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
getTranslation("offline.communication_error", error.getCause().getMessage()));
|
||||
}
|
||||
|
||||
private ChannelUID createChannelUid(MQ2200InverterRegisters register) {
|
||||
return new ChannelUID(thing.getUID(), "fi-" + register.getChannelGroup(), "fi-" + register.getChannelName());
|
||||
}
|
||||
|
||||
/**
|
||||
* get translated text
|
||||
*
|
||||
* @param text text to be translated, may contain placeholders {n} for the n-th optional argument of this function
|
||||
* @param arguments any optional arguments, will be inserted
|
||||
* @return translated text with substitutions if translationProvider is set and provides a translation, otherwise
|
||||
* returns original text with substitutions
|
||||
*/
|
||||
public String getTranslation(final String text, @Nullable Object @Nullable... arguments) {
|
||||
// locale cannot be cached, as getLocale() will return different result once locale is changed by user
|
||||
final Locale locale = localeProvider.getLocale();
|
||||
final String res = translationProvider.getText(bundle, text, text, locale, arguments);
|
||||
if (res != null) {
|
||||
return res;
|
||||
}
|
||||
// translating not possible, we still have the original text without any substitutions
|
||||
if (arguments == null || arguments.length == 0) {
|
||||
return text;
|
||||
}
|
||||
// else execute pattern substitution in untranslated text
|
||||
return MessageFormat.format(text, arguments);
|
||||
}
|
||||
}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.modbus.foxinverter.internal;
|
||||
|
||||
import static org.openhab.core.io.transport.modbus.ModbusConstants.ValueType.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.measure.Unit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.io.transport.modbus.ModbusConstants.ValueType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link MQ2200InverterRegisters} is responsible for defining Modbus registers and their units.
|
||||
*
|
||||
* @author Holger Friedrich - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum MQ2200InverterRegisters {
|
||||
// this temperature is shown in the app
|
||||
BATTERY_TEMPERATURE(37617, INT16, ConversionConstants.DIV_BY_TEN, quantityFactory(SIUnits.CELSIUS),
|
||||
"battery-information"),
|
||||
|
||||
METER_CONNECTED(38801, UINT16, BigDecimal.ONE, contactFactory(), "overview"),
|
||||
|
||||
// seems not useful yet, always 0
|
||||
// Thing data protocolVersion "Protocol version" [ readStart="39000", readValueType="uint32" ] // always 0
|
||||
|
||||
// status is 1 during start and shutdown, 4 during normal operation, 0x40 on fault
|
||||
// (poweroff cannot be read, as it will shut down Modbus communication)
|
||||
HIDDEN_STATUS1(39063, UINT16, BigDecimal.ONE, DecimalType::new, "overview"),
|
||||
// STATUS3 seems to use only bit0, but it does not indicate the grid status properly
|
||||
// grid outage could be detected via GRID_FREQUENCY register
|
||||
// STATUS_ON_GRID(39065, UINT32, BigDecimal.ONE, contactFactory(), "status"),
|
||||
HIDDEN_ALARM1(39067, UINT16, BigDecimal.ONE, DecimalType::new, ""),
|
||||
HIDDEN_ALARM2(39068, UINT16, BigDecimal.ONE, DecimalType::new, ""),
|
||||
HIDDEN_ALARM3(39069, UINT16, BigDecimal.ONE, DecimalType::new, ""),
|
||||
|
||||
// NOTE: this list needs to be sorted by register number!
|
||||
MPPT1_VOLTAGE(39070, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.VOLT), "mppt-information"),
|
||||
MPPT1_CURRENT(39071, UINT16, ConversionConstants.DIV_BY_HUNDRED, quantityFactory(Units.AMPERE), "mppt-information"),
|
||||
MPPT2_VOLTAGE(39072, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.VOLT), "mppt-information"),
|
||||
MPPT2_CURRENT(39073, UINT16, ConversionConstants.DIV_BY_HUNDRED, quantityFactory(Units.AMPERE), "mppt-information"),
|
||||
MPPT3_VOLTAGE(39074, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.VOLT), "mppt-information"),
|
||||
MPPT3_CURRENT(39075, UINT16, ConversionConstants.DIV_BY_HUNDRED, quantityFactory(Units.AMPERE), "mppt-information"),
|
||||
MPPT4_VOLTAGE(39076, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.VOLT), "mppt-information"),
|
||||
MPPT4_CURRENT(39077, UINT16, ConversionConstants.DIV_BY_HUNDRED, quantityFactory(Units.AMPERE), "mppt-information"),
|
||||
|
||||
PV_POWER(39118, INT32, BigDecimal.ONE, quantityFactory(Units.WATT), "mppt-information"),
|
||||
|
||||
HIDDEN_GRID_FREQUENCY(39139, UINT16, new BigDecimal(BigInteger.ONE, 2), quantityFactory(Units.HERTZ),
|
||||
"grid-information"),
|
||||
|
||||
PHASE_A_VOLTAGE(39123, INT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.VOLT), "grid-information"),
|
||||
PHASE_B_VOLTAGE(39124, INT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.VOLT), "grid-information"),
|
||||
PHASE_C_VOLTAGE(39125, INT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.VOLT), "grid-information"),
|
||||
|
||||
// INVERTER_x_CURRENT 39126, 39128, 39130 seem not to work for MQ2200
|
||||
|
||||
HIDDEN_INVERTER_POWER(39134, INT32, BigDecimal.ONE, quantityFactory(Units.WATT), "overview"),
|
||||
|
||||
// seems not to work for MQ2200
|
||||
// REACTIVE_POWER(39136, INT32, BigDecimal.ONE, quantityFactory(Units.VAR), "overview"),
|
||||
// POWER_FACTOR(39138, INT16, ConversionConstants.DIV_BY_THOUSAND, DecimalType::new, "overview"),
|
||||
INTERNAL_TEMPERATURE(39141, INT16, ConversionConstants.DIV_BY_TEN, quantityFactory(SIUnits.CELSIUS), "overview"),
|
||||
|
||||
TOTAL_PV_GENERATION(39149, UINT32, ConversionConstants.DIV_BY_HUNDRED, quantityFactory(Units.KILOWATT_HOUR),
|
||||
"overview"),
|
||||
DAILY_PV_GENERATION(39151, UINT32, ConversionConstants.DIV_BY_HUNDRED, quantityFactory(Units.KILOWATT_HOUR),
|
||||
"overview"),
|
||||
|
||||
HOME_IMPORT_POWER(39162, INT32, BigDecimal.ONE, quantityFactory(Units.WATT), "overview"),
|
||||
GRID_EXPORT_POWER(39168, INT32, BigDecimal.ONE, quantityFactory(Units.WATT), "grid-information"),
|
||||
|
||||
EPS_EXPORT_POWER(39216, INT32, BigDecimal.ONE, quantityFactory(Units.WATT), "emergency-power-supply"),
|
||||
|
||||
BATTERY_VOLTAGE(39227, UINT16, ConversionConstants.DIV_BY_TEN, quantityFactory(Units.VOLT), "battery-information"),
|
||||
BATTERY_CURRENT(39228, INT32, ConversionConstants.DIV_BY_THOUSAND, quantityFactory(Units.AMPERE),
|
||||
"battery-information"),
|
||||
|
||||
// prefer combined power over bat1 power
|
||||
BATTERY_CHARGING_POWER(39237, INT32, BigDecimal.ONE, quantityFactory(Units.WATT), "battery-information"),
|
||||
|
||||
MPPT1_POWER(39279, INT32, BigDecimal.ONE, quantityFactory(Units.WATT), "mppt-information"),
|
||||
MPPT2_POWER(39281, INT32, BigDecimal.ONE, quantityFactory(Units.WATT), "mppt-information"),
|
||||
MPPT3_POWER(39283, INT32, BigDecimal.ONE, quantityFactory(Units.WATT), "mppt-information"),
|
||||
MPPT4_POWER(39285, INT32, BigDecimal.ONE, quantityFactory(Units.WATT), "mppt-information"),
|
||||
|
||||
BATTERY_LEVEL(39424, UINT16, BigDecimal.ONE, percentFactory(), "battery-information"),
|
||||
|
||||
// TODO implement remote control, registers 46001-46007
|
||||
|
||||
BATTERY_MINIMUM_SOC(46609, UINT16, BigDecimal.ONE, percentFactory(), "battery-information"),
|
||||
BATTERY_MAXIMUM_SOC(46610, UINT16, BigDecimal.ONE, percentFactory(), "battery-information"),
|
||||
BATTERY_MINIMUM_SOC_ON_GRID(46611, UINT16, BigDecimal.ONE, percentFactory(), "battery-information"),
|
||||
|
||||
// 0:off 2:on, special handling in MQ2200InverterHandler
|
||||
HIDDEN_EPS_OUTPUT(46613, UINT16, BigDecimal.ONE, DecimalType::new, "emergency-power-supply");
|
||||
|
||||
// does not work, always returns 7
|
||||
// WORK_MODE(49203, UINT16, BigDecimal.ONE, DecimalType::new, "overview"),
|
||||
|
||||
// some registers in between cannot be read and will make the Modbus request fail
|
||||
// BLOCKER(MQ2200InverterHandler.ENFORCE_NEW_REQUEST, UINT16, BigDecimal.ONE, DecimalType::new, ""),
|
||||
// does not change during manual shutdown using the button on the device
|
||||
// SYSTEM_POWER_STATE(49228, UINT16, BigDecimal.ONE, DecimalType::new, "overview"),
|
||||
// idle state seems to be 0
|
||||
// IDLE_STATE(49229, UINT16, BigDecimal.ONE, DecimalType::new, "overview"),
|
||||
// IDLE_LOAD_POWER_THRESHOLD(49230, UINT16, BigDecimal.ONE, DecimalType::new, "overview");
|
||||
|
||||
private final BigDecimal multiplier;
|
||||
private final int registerNumber;
|
||||
private final ValueType type;
|
||||
|
||||
private final Function<BigDecimal, BigDecimal> conversion;
|
||||
private final Function<BigDecimal, State> stateFactory;
|
||||
private final String channelGroup;
|
||||
|
||||
MQ2200InverterRegisters(int registerNumber, ValueType type, BigDecimal multiplier,
|
||||
Function<BigDecimal, State> stateFactory, Function<BigDecimal, BigDecimal> conversion,
|
||||
String channelGroup) {
|
||||
this.multiplier = multiplier;
|
||||
this.registerNumber = registerNumber;
|
||||
this.type = type;
|
||||
this.conversion = conversion;
|
||||
this.stateFactory = stateFactory;
|
||||
this.channelGroup = channelGroup;
|
||||
}
|
||||
|
||||
MQ2200InverterRegisters(int registerNumber, ValueType type, BigDecimal multiplier,
|
||||
Function<BigDecimal, State> stateFactory, String channelGroup) {
|
||||
this.multiplier = multiplier;
|
||||
this.registerNumber = registerNumber;
|
||||
this.type = type;
|
||||
this.conversion = Function.identity();
|
||||
this.stateFactory = stateFactory;
|
||||
this.channelGroup = channelGroup;
|
||||
}
|
||||
|
||||
private static Function<BigDecimal, State> contactFactory() {
|
||||
return (BigDecimal value) -> value.intValue() == 0 ? OpenClosedType.CLOSED : OpenClosedType.OPEN;
|
||||
}
|
||||
|
||||
private static Function<BigDecimal, State> percentFactory() {
|
||||
return (BigDecimal value) -> new QuantityType<>(value, Units.PERCENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Function that creates {@link QuantityType} states with the given {@link Unit}.
|
||||
*
|
||||
* @param unit {@link Unit} to be used for the value.
|
||||
* @return Function for value creation.
|
||||
*/
|
||||
private static Function<BigDecimal, State> quantityFactory(Unit<?> unit) {
|
||||
return (BigDecimal value) -> new QuantityType<>(value, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Modbus register number.
|
||||
*
|
||||
* @return Modbus register number.
|
||||
*/
|
||||
public int getRegisterNumber() {
|
||||
return registerNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ValueType} for the channel.
|
||||
*
|
||||
* @return {@link ValueType} for the channel.
|
||||
*/
|
||||
public ValueType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the count of registers read to return the value of this register.
|
||||
*
|
||||
* @return register count.
|
||||
*/
|
||||
public int getRegisterCount() {
|
||||
return this.type.getBits() / 16;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the channel group.
|
||||
*
|
||||
* @return channel group id.
|
||||
*/
|
||||
public String getChannelGroup() {
|
||||
return channelGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the channel name.
|
||||
*
|
||||
* @return the channel name.
|
||||
*/
|
||||
public String getChannelName() {
|
||||
return this.name().toLowerCase().replace('_', '-');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link State} for the given register value.
|
||||
*
|
||||
* @param registerValue the value for the channel.
|
||||
* @return {@link State} for the given value.
|
||||
*/
|
||||
public State createState(DecimalType registerValue) {
|
||||
final BigDecimal scaledValue = registerValue.toBigDecimal().multiply(this.multiplier);
|
||||
|
||||
final BigDecimal convertedValue = conversion.apply(scaledValue);
|
||||
return this.stateFactory.apply(convertedValue);
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.modbus.foxinverter.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.modbus.ModbusBindingConstants;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link ModbusFoxInverterBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Holger Friedrich - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModbusFoxInverterBindingConstants {
|
||||
|
||||
public static final String PROPERTY_MODEL_NAME = "modelName";
|
||||
public static final String PROPERTY_SERIAL_NO = "serialNumber";
|
||||
public static final String PROPERTY_MANUFACTURER_ID = "manufacturerId";
|
||||
public static final String PROPERTY_RATED_POWER = "ratedPower";
|
||||
public static final String PROPERTY_MAX_ACTIVE_POWER = "maxActivePower";
|
||||
public static final String PROPERTY_FIRMWARE_BMS = "firmwareBMS";
|
||||
public static final String PROPERTY_FIRMWARE_WR = "firmwareWR";
|
||||
public static final String PROPERTY_FIRMWARE_PV = "firmwarePV";
|
||||
|
||||
/**
|
||||
* ThingType-ID for Inverter.
|
||||
*/
|
||||
public static final ThingTypeUID THING_TYPE_MQ2200_INVERTER = new ThingTypeUID(ModbusBindingConstants.BINDING_ID,
|
||||
"mq2200-inverter");
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.modbus.foxinverter.internal;
|
||||
|
||||
import static org.openhab.binding.modbus.foxinverter.internal.ModbusFoxInverterBindingConstants.THING_TYPE_MQ2200_INVERTER;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.modbus.discovery.ModbusDiscoveryListener;
|
||||
import org.openhab.binding.modbus.discovery.ModbusDiscoveryParticipant;
|
||||
import org.openhab.binding.modbus.handler.EndpointNotInitializedException;
|
||||
import org.openhab.binding.modbus.handler.ModbusEndpointThingHandler;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.io.transport.modbus.AsyncModbusFailure;
|
||||
import org.openhab.core.io.transport.modbus.AsyncModbusReadResult;
|
||||
import org.openhab.core.io.transport.modbus.ModbusCommunicationInterface;
|
||||
import org.openhab.core.io.transport.modbus.ModbusReadFunctionCode;
|
||||
import org.openhab.core.io.transport.modbus.ModbusReadRequestBlueprint;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Discovery service for FoxInverter devices
|
||||
*
|
||||
* @author Holger Friedrich - initial contribution
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
@NonNullByDefault
|
||||
public class ModbusFoxInverterDiscoveryParticipant implements ModbusDiscoveryParticipant {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ModbusFoxInverterDiscoveryParticipant.class);
|
||||
|
||||
private class FoxInverterDiscoveryProcess {
|
||||
private static final String PROPERTY_UNIQUE_ADDRESS = "uniqueAddress";
|
||||
private static final String MQ2200_IDENTIFIER = "MQ2200";
|
||||
private static final int MQ2200_START_ADDRESS = 30000;
|
||||
private final ModbusEndpointThingHandler handler;
|
||||
private final ModbusDiscoveryListener listener;
|
||||
|
||||
public FoxInverterDiscoveryProcess(ModbusEndpointThingHandler handler, ModbusDiscoveryListener listener) {
|
||||
this.handler = handler;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void detectModel() throws EndpointNotInitializedException {
|
||||
logger.debug("Beginning scan for FoxESS MQ-2200 device at address {}", MQ2200_START_ADDRESS);
|
||||
ModbusCommunicationInterface comms = handler.getCommunicationInterface();
|
||||
if (comms == null) {
|
||||
throw new EndpointNotInitializedException();
|
||||
}
|
||||
// Initial poll of static info
|
||||
// manufacturer, model, serial number
|
||||
comms.submitOneTimePoll(new ModbusReadRequestBlueprint(handler.getSlaveId(),
|
||||
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, MQ2200_START_ADDRESS, 16 * 3, 3),
|
||||
(AsyncModbusReadResult result) -> {
|
||||
logger.trace("Initial poll successful {}", result);
|
||||
byte[] res = result.getRegisters().get().getBytes();
|
||||
String modelInfo = new String(res, 0, 16).trim();
|
||||
if (modelInfo.startsWith(MQ2200_IDENTIFIER)) {
|
||||
// Handle MQ2200 specific logic
|
||||
|
||||
String serialNo = new String(res, 16, 16).trim();
|
||||
String manufacturerId = new String(res, 32, 16).trim();
|
||||
|
||||
logger.debug("Detected Inverter Model: {}, S/N: {}, Manufacturer ID: {}", modelInfo,
|
||||
serialNo, manufacturerId);
|
||||
// construct an ID, only alphanumeric characters, lower case
|
||||
String deviceId = (serialNo + manufacturerId).replaceAll("[^a-zA-Z0-9]", "").toLowerCase();
|
||||
if (deviceId.isBlank()) {
|
||||
logger.debug("Device ID is blank, cannot create discovery result");
|
||||
} else {
|
||||
ThingTypeUID thingTypeUID = THING_TYPE_MQ2200_INVERTER;
|
||||
ThingUID thingUID = new ThingUID(thingTypeUID, handler.getUID(), deviceId);
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put(PROPERTY_UNIQUE_ADDRESS,
|
||||
handler.getUID().getAsString() + ":" + deviceId);
|
||||
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
|
||||
.withProperties(properties).withRepresentationProperty(PROPERTY_UNIQUE_ADDRESS)
|
||||
.withBridge(handler.getUID()).withLabel("FoxESS MQ-2200 Inverter").build();
|
||||
|
||||
listener.thingDiscovered(discoveryResult);
|
||||
}
|
||||
}
|
||||
listener.discoveryFinished();
|
||||
}, (AsyncModbusFailure<ModbusReadRequestBlueprint> error) -> {
|
||||
logger.debug("Reading device info from MQ2200 failed, skipping detection", error.getCause());
|
||||
listener.discoveryFinished();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return Set.of(THING_TYPE_MQ2200_INVERTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startDiscovery(ModbusEndpointThingHandler handler, ModbusDiscoveryListener listener) {
|
||||
logger.debug("Starting fox inverter discovery");
|
||||
try {
|
||||
new FoxInverterDiscoveryProcess(handler, listener).detectModel();
|
||||
} catch (EndpointNotInitializedException ex) {
|
||||
logger.debug("Could not start discovery process");
|
||||
listener.discoveryFinished();
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.modbus.foxinverter.internal;
|
||||
|
||||
import static org.openhab.binding.modbus.foxinverter.internal.ModbusFoxInverterBindingConstants.*;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link ModbusFoxInverterHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Holger Friedrich - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.foxinverter", service = ThingHandlerFactory.class)
|
||||
public class ModbusFoxInverterHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_MQ2200_INVERTER);
|
||||
private LocaleProvider localeProvider;
|
||||
private TranslationProvider translationProvider;
|
||||
|
||||
@Activate
|
||||
public ModbusFoxInverterHandlerFactory(final @Reference TranslationProvider translationProvider,
|
||||
final @Reference LocaleProvider localeProvider) {
|
||||
this.translationProvider = translationProvider;
|
||||
this.localeProvider = localeProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_MQ2200_INVERTER.equals(thingTypeUID)) {
|
||||
return new MQ2200InverterHandler(thing, translationProvider, localeProvider);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:foxinverter:mq2200-inverter">
|
||||
<parameter name="pollInterval" type="integer" required="true" min="100" unit="ms">
|
||||
<label>Poll Interval</label>
|
||||
<description>Time between polling the data in ms.</description>
|
||||
<default>5000</default>
|
||||
</parameter>
|
||||
<parameter name="maxTries" type="integer" min="1">
|
||||
<label>Maximum Tries When Reading</label>
|
||||
<default>3</default>
|
||||
<description>Specifies how many times the binding should retry reading data if a read attempt fails.
|
||||
Set to `1` to
|
||||
disable retries and use a single attempt.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</config-description:config-descriptions>
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
# thing types
|
||||
|
||||
thing-type.modbus.mq2200-inverter.label = FoxESS MQ-2200 Inverter
|
||||
thing-type.modbus.mq2200-inverter.description = FoxESS MQ-2200 / Solakon ONE, Avocado 22 Pro inverter connected via Modbus TCP.
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.foxinverter.mq2200-inverter.maxTries.label = Maximum Tries When Reading
|
||||
thing-type.config.foxinverter.mq2200-inverter.maxTries.description = Specifies how many times the binding should retry reading data if a read attempt fails. Set to `1` to disable retries and use a single attempt.
|
||||
thing-type.config.foxinverter.mq2200-inverter.pollInterval.label = Poll Interval
|
||||
thing-type.config.foxinverter.mq2200-inverter.pollInterval.description = Time between polling the data in ms.
|
||||
|
||||
# channel group types
|
||||
|
||||
channel-group-type.modbus.fi-battery-information.label = Battery Information
|
||||
channel-group-type.modbus.fi-emergency-power-supply.label = EPS Information
|
||||
channel-group-type.modbus.fi-grid-information.label = Grid Information
|
||||
channel-group-type.modbus.fi-mppt-information.label = MPPT Information
|
||||
channel-group-type.modbus.fi-overview.label = Overview
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.modbus.fi-battery-charging-power.label = Battery Charging Power
|
||||
channel-type.modbus.fi-battery-charging-power.description = Battery power flow. Positive values indicate charging, negative values indicate discharging.
|
||||
channel-type.modbus.fi-battery-current.label = Battery Current
|
||||
channel-type.modbus.fi-battery-level.label = Battery Level
|
||||
channel-type.modbus.fi-battery-maximum-soc.label = Battery maximum state of charge
|
||||
channel-type.modbus.fi-battery-minimum-soc-on-grid.label = Battery minimum state of charge (on grid)
|
||||
channel-type.modbus.fi-battery-minimum-soc.label = Battery minimum state of charge
|
||||
channel-type.modbus.fi-battery-voltage.label = Battery Voltage
|
||||
channel-type.modbus.fi-daily-pv-generation.label = Daily PV Generation
|
||||
channel-type.modbus.fi-eps-export-power.label = EPS Export Power
|
||||
channel-type.modbus.fi-eps-export-power.description = Emergency power supply socket power. Positive values indicate supplying connected devices, negative values indicate receiving power from external source.
|
||||
channel-type.modbus.fi-eps-output.label = EPS Enabled
|
||||
channel-type.modbus.fi-grid-export-power.label = Grid Export Power
|
||||
channel-type.modbus.fi-grid-export-power.description = Power flow with public grid. Positive values indicate export to grid, negative values indicate import from grid.
|
||||
channel-type.modbus.fi-grid-frequency.label = Grid Frequency
|
||||
channel-type.modbus.fi-home-import-power.label = Home Import Power
|
||||
channel-type.modbus.fi-home-import-power.description = Power flow with home network. Negative when feeding home, positive when drawing from grid.
|
||||
channel-type.modbus.fi-internal-temperature.label = Internal Temperature
|
||||
channel-type.modbus.fi-inverter-power.label = Inverter Power
|
||||
channel-type.modbus.fi-inverter-power.description = Inverter active power output. Always positive.
|
||||
channel-type.modbus.fi-meter-connected.label = Meter Connected
|
||||
channel-type.modbus.fi-mppt1-current.label = MPPT1 Current
|
||||
channel-type.modbus.fi-mppt1-power.label = MPPT1 Power
|
||||
channel-type.modbus.fi-mppt1-voltage.label = MPPT1 Voltage
|
||||
channel-type.modbus.fi-mppt2-current.label = MPPT2 Current
|
||||
channel-type.modbus.fi-mppt2-power.label = MPPT2 Power
|
||||
channel-type.modbus.fi-mppt2-voltage.label = MPPT2 Voltage
|
||||
channel-type.modbus.fi-mppt3-current.label = MPPT3 Current
|
||||
channel-type.modbus.fi-mppt3-power.label = MPPT3 Power
|
||||
channel-type.modbus.fi-mppt3-voltage.label = MPPT3 Voltage
|
||||
channel-type.modbus.fi-mppt4-current.label = MPPT4 Current
|
||||
channel-type.modbus.fi-mppt4-power.label = MPPT4 Power
|
||||
channel-type.modbus.fi-mppt4-voltage.label = MPPT4 Voltage
|
||||
channel-type.modbus.fi-phase-a-voltage.label = Phase A Voltage
|
||||
channel-type.modbus.fi-phase-b-voltage.label = Phase B Voltage
|
||||
channel-type.modbus.fi-phase-c-voltage.label = Phase C Voltage
|
||||
channel-type.modbus.fi-pv-power.label = PV Power
|
||||
channel-type.modbus.fi-pv-power.description = Current solar power generation from PV panels. Always positive.
|
||||
channel-type.modbus.fi-status-alarm.label = System Status: Alarm
|
||||
channel-type.modbus.fi-status-on-grid.label = On Grid
|
||||
channel-type.modbus.fi-status-operation.label = System Status: Operation
|
||||
channel-type.modbus.fi-status-standby.label = System Status: Standby
|
||||
channel-type.modbus.fi-total-pv-generation.label = Total PV Generation
|
||||
|
||||
# specific strings
|
||||
|
||||
offline.communication_error = Failed to retrieve data: {0}
|
||||
offline.config.poll_interval = Invalid poll interval: {0}
|
||||
offline.config.max_tries = Invalid Maximum Tries When Reading: {0}
|
||||
+476
@@ -0,0 +1,476 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="modbus"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="mq2200-inverter">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="tcp"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>FoxESS MQ-2200 Inverter</label>
|
||||
<description>FoxESS MQ-2200 / Solakon ONE, Avocado 22 Pro inverter connected via Modbus TCP.</description>
|
||||
<category>Inverter</category>
|
||||
<channel-groups>
|
||||
<channel-group id="fi-overview" typeId="fi-overview"/>
|
||||
<channel-group id="fi-mppt-information" typeId="fi-mppt-information"/>
|
||||
<channel-group id="fi-battery-information" typeId="fi-battery-information"/>
|
||||
<channel-group id="fi-grid-information" typeId="fi-grid-information"/>
|
||||
<channel-group id="fi-emergency-power-supply" typeId="fi-emergency-power-supply"/>
|
||||
</channel-groups>
|
||||
<config-description-ref uri="thing-type:foxinverter:mq2200-inverter"/>
|
||||
</thing-type>
|
||||
|
||||
<channel-group-type id="fi-overview">
|
||||
<label>Overview</label>
|
||||
<channels>
|
||||
<channel id="fi-inverter-power" typeId="fi-inverter-power"/>
|
||||
<channel id="fi-home-import-power" typeId="fi-home-import-power"/>
|
||||
<channel id="fi-daily-pv-generation" typeId="fi-daily-pv-generation"/>
|
||||
<channel id="fi-total-pv-generation" typeId="fi-total-pv-generation"/>
|
||||
<channel id="fi-internal-temperature" typeId="fi-internal-temperature"/>
|
||||
<channel id="fi-meter-connected" typeId="fi-meter-connected"/>
|
||||
<channel id="fi-status-alarm" typeId="fi-status-alarm"/>
|
||||
<channel id="fi-status-operation" typeId="fi-status-operation"/>
|
||||
<channel id="fi-status-standby" typeId="fi-status-standby"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="fi-mppt-information">
|
||||
<label>MPPT Information</label>
|
||||
<channels>
|
||||
<channel id="fi-pv-power" typeId="fi-pv-power"/>
|
||||
<channel id="fi-mppt1-voltage" typeId="fi-mppt1-voltage"/>
|
||||
<channel id="fi-mppt1-current" typeId="fi-mppt1-current"/>
|
||||
<channel id="fi-mppt1-power" typeId="fi-mppt1-power"/>
|
||||
<channel id="fi-mppt2-voltage" typeId="fi-mppt2-voltage"/>
|
||||
<channel id="fi-mppt2-current" typeId="fi-mppt2-current"/>
|
||||
<channel id="fi-mppt2-power" typeId="fi-mppt2-power"/>
|
||||
<channel id="fi-mppt3-voltage" typeId="fi-mppt3-voltage"/>
|
||||
<channel id="fi-mppt3-current" typeId="fi-mppt3-current"/>
|
||||
<channel id="fi-mppt3-power" typeId="fi-mppt3-power"/>
|
||||
<channel id="fi-mppt4-voltage" typeId="fi-mppt4-voltage"/>
|
||||
<channel id="fi-mppt4-current" typeId="fi-mppt4-current"/>
|
||||
<channel id="fi-mppt4-power" typeId="fi-mppt4-power"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="fi-battery-information">
|
||||
<label>Battery Information</label>
|
||||
<category>Battery</category>
|
||||
<channels>
|
||||
<channel id="fi-battery-voltage" typeId="fi-battery-voltage"/>
|
||||
<channel id="fi-battery-current" typeId="fi-battery-current"/>
|
||||
<channel id="fi-battery-level" typeId="fi-battery-level"/>
|
||||
<channel id="fi-battery-charging-power" typeId="fi-battery-charging-power"/>
|
||||
<channel id="fi-battery-temperature" typeId="fi-battery-temperature"/>
|
||||
<channel id="fi-battery-minimum-soc" typeId="fi-battery-minimum-soc"/>
|
||||
<channel id="fi-battery-maximum-soc" typeId="fi-battery-maximum-soc"/>
|
||||
<channel id="fi-battery-minimum-soc-on-grid" typeId="fi-battery-minimum-soc-on-grid"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="fi-grid-information">
|
||||
<label>Grid Information</label>
|
||||
<channels>
|
||||
<channel id="fi-phase-a-voltage" typeId="fi-phase-a-voltage"/>
|
||||
<channel id="fi-phase-b-voltage" typeId="fi-phase-b-voltage"/>
|
||||
<channel id="fi-phase-c-voltage" typeId="fi-phase-c-voltage"/>
|
||||
<channel id="fi-grid-frequency" typeId="fi-grid-frequency"/>
|
||||
<channel id="fi-grid-export-power" typeId="fi-grid-export-power"/>
|
||||
<channel id="fi-status-on-grid" typeId="fi-status-on-grid"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="fi-emergency-power-supply">
|
||||
<label>EPS Information</label>
|
||||
<channels>
|
||||
<channel id="fi-eps-output" typeId="fi-eps-output"/>
|
||||
<channel id="fi-eps-export-power" typeId="fi-eps-export-power"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-type id="fi-inverter-power">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Inverter Power</label>
|
||||
<description>Inverter active power output. Always positive.</description>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Power</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-home-import-power">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Home Import Power</label>
|
||||
<description>Power flow with home network. Negative when feeding home, positive when drawing from grid.</description>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Power</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-daily-pv-generation">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Daily PV Generation</label>
|
||||
<category>Line</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Energy</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-total-pv-generation">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Total PV Generation</label>
|
||||
<category>Line</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Energy</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-internal-temperature" advanced="true">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Internal Temperature</label>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Temperature</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-meter-connected" advanced="true">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Meter Connected</label>
|
||||
<tags>
|
||||
<tag>Status</tag>
|
||||
</tags>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-status-alarm" advanced="false">
|
||||
<item-type>Contact</item-type>
|
||||
<label>System Status: Alarm</label>
|
||||
<tags>
|
||||
<tag>Status</tag>
|
||||
</tags>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-status-operation" advanced="false">
|
||||
<item-type>Contact</item-type>
|
||||
<label>System Status: Operation</label>
|
||||
<tags>
|
||||
<tag>Status</tag>
|
||||
</tags>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-status-standby" advanced="true">
|
||||
<item-type>Contact</item-type>
|
||||
<label>System Status: Standby</label>
|
||||
<tags>
|
||||
<tag>Status</tag>
|
||||
</tags>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="fi-pv-power">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>PV Power</label>
|
||||
<description>Current solar power generation from PV panels. Always positive.</description>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Power</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-mppt1-voltage" advanced="true">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>MPPT1 Voltage</label>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Voltage</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-mppt1-current" advanced="true">
|
||||
<item-type>Number:ElectricCurrent</item-type>
|
||||
<label>MPPT1 Current</label>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Current</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-mppt1-power" advanced="false">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>MPPT1 Power</label>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Power</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-mppt2-voltage" advanced="true">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>MPPT2 Voltage</label>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Voltage</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-mppt2-power" advanced="false">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>MPPT2 Power</label>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Power</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-mppt2-current" advanced="true">
|
||||
<item-type>Number:ElectricCurrent</item-type>
|
||||
<label>MPPT2 Current</label>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Current</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-mppt3-voltage" advanced="true">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>MPPT3 Voltage</label>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Voltage</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-mppt3-current" advanced="true">
|
||||
<item-type>Number:ElectricCurrent</item-type>
|
||||
<label>MPPT3 Current</label>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Current</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-mppt3-power" advanced="false">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>MPPT3 Power</label>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Power</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-mppt4-voltage" advanced="true">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>MPPT4 Voltage</label>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Voltage</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-mppt4-power" advanced="false">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>MPPT4 Power</label>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Power</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-mppt4-current" advanced="true">
|
||||
<item-type>Number:ElectricCurrent</item-type>
|
||||
<label>MPPT4 Current</label>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Current</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="fi-phase-a-voltage" advanced="true">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>Phase A Voltage</label>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Voltage</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-phase-b-voltage" advanced="true">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>Phase B Voltage</label>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Voltage</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-phase-c-voltage" advanced="true">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>Phase C Voltage</label>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Voltage</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-grid-frequency" advanced="true">
|
||||
<item-type>Number:Frequency</item-type>
|
||||
<label>Grid Frequency</label>
|
||||
<category>Line</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Frequency</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-grid-export-power">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Grid Export Power</label>
|
||||
<description>Power flow with public grid. Positive values indicate export to grid, negative values indicate import
|
||||
from grid.</description>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Power</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-status-on-grid" advanced="true">
|
||||
<item-type>Contact</item-type>
|
||||
<label>On Grid</label>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Status</tag>
|
||||
</tags>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="fi-battery-voltage" advanced="true">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>Battery Voltage</label>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Voltage</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-battery-current" advanced="true">
|
||||
<item-type>Number:ElectricCurrent</item-type>
|
||||
<label>Battery Current</label>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Current</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-battery-charging-power">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Battery Charging Power</label>
|
||||
<description>Battery power flow. Positive values indicate charging, negative values indicate discharging.</description>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Power</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-battery-level">
|
||||
<item-type unitHint="%">Number:Dimensionless</item-type>
|
||||
<label>Battery Level</label>
|
||||
<category>Battery</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Energy</tag>
|
||||
</tags>
|
||||
<state pattern="%.0f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-battery-temperature" advanced="false">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Battery Temperature</label>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Temperature</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-battery-minimum-soc" advanced="true">
|
||||
<item-type unitHint="%">Number:Dimensionless</item-type>
|
||||
<label>Battery minimum state of charge</label>
|
||||
<category>Battery</category>
|
||||
<tags>
|
||||
<tag>Setpoint</tag>
|
||||
<tag>Energy</tag>
|
||||
</tags>
|
||||
<state pattern="%.0f %unit%" readOnly="false"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-battery-maximum-soc" advanced="true">
|
||||
<item-type unitHint="%">Number:Dimensionless</item-type>
|
||||
<label>Battery maximum state of charge</label>
|
||||
<category>Battery</category>
|
||||
<tags>
|
||||
<tag>Setpoint</tag>
|
||||
<tag>Energy</tag>
|
||||
</tags>
|
||||
<state pattern="%.0f %unit%" readOnly="false"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-battery-minimum-soc-on-grid" advanced="true">
|
||||
<item-type unitHint="%">Number:Dimensionless</item-type>
|
||||
<label>Battery minimum state of charge (on grid)</label>
|
||||
<category>Battery</category>
|
||||
<tags>
|
||||
<tag>Setpoint</tag>
|
||||
<tag>Energy</tag>
|
||||
</tags>
|
||||
<state pattern="%.0f %unit%" readOnly="false"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-eps-output">
|
||||
<item-type>Switch</item-type>
|
||||
<label>EPS Enabled</label>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Control</tag>
|
||||
<tag>Power</tag>
|
||||
</tags>
|
||||
<state readOnly="false"/>
|
||||
</channel-type>
|
||||
<channel-type id="fi-eps-export-power">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>EPS Export Power</label>
|
||||
<description>Emergency power supply socket power. Positive values indicate supplying connected devices, negative
|
||||
values indicate receiving power from external source.</description>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Power</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.modbus.foxinverter.internal;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.io.transport.modbus.ModbusBitUtilities;
|
||||
import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* @author Sönke Küper - Initial contribution
|
||||
* @author Holger Friedrich - Carry over from SunGrow, change BatteryLevel test to Number:Dimensionless
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class MQ2200InverterRegistersTest {
|
||||
@Test
|
||||
public void testCreatePercentTypeState() {
|
||||
MQ2200InverterRegisters batteryLevelRegister = MQ2200InverterRegisters.BATTERY_LEVEL;
|
||||
|
||||
ModbusRegisterArray registers = new ModbusRegisterArray(100);
|
||||
Optional<DecimalType> value = ModbusBitUtilities.extractStateFromRegisters( //
|
||||
registers, //
|
||||
0, //
|
||||
batteryLevelRegister.getType() //
|
||||
);
|
||||
assertTrue(value.isPresent());
|
||||
DecimalType decimalTypeValue = value.get();
|
||||
// Value is not scaled yet
|
||||
assertEquals(BigDecimal.valueOf(100), decimalTypeValue.toBigDecimal());
|
||||
|
||||
State state = batteryLevelRegister.createState(decimalTypeValue);
|
||||
assertInstanceOf(QuantityType.class, state);
|
||||
assertEquals("100 %", state.toFullString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateQuantityTypeState() {
|
||||
MQ2200InverterRegisters mpptVoltage = MQ2200InverterRegisters.MPPT1_VOLTAGE;
|
||||
|
||||
ModbusRegisterArray registers = new ModbusRegisterArray(1234);
|
||||
Optional<DecimalType> value = ModbusBitUtilities.extractStateFromRegisters( //
|
||||
registers, //
|
||||
0, //
|
||||
mpptVoltage.getType() //
|
||||
);
|
||||
assertTrue(value.isPresent());
|
||||
DecimalType decimalTypeValue = value.get();
|
||||
// Value is not scaled yet
|
||||
assertEquals(BigDecimal.valueOf(1234), decimalTypeValue.toBigDecimal());
|
||||
|
||||
State state = mpptVoltage.createState(decimalTypeValue);
|
||||
assertInstanceOf(QuantityType.class, state);
|
||||
assertEquals("123.4 V", state.toFullString());
|
||||
}
|
||||
}
|
||||
@@ -276,6 +276,7 @@
|
||||
<module>org.openhab.binding.minecraft</module>
|
||||
<module>org.openhab.binding.modbus</module>
|
||||
<module>org.openhab.binding.modbus.e3dc</module>
|
||||
<module>org.openhab.binding.modbus.foxinverter</module>
|
||||
<module>org.openhab.binding.modbus.kermi</module>
|
||||
<module>org.openhab.binding.modbus.sbc</module>
|
||||
<module>org.openhab.binding.modbus.studer</module>
|
||||
|
||||
Reference in New Issue
Block a user