added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.modbus.sunspec</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,22 @@
## For Developers
SunSpec is a big specification with many different type of devices.
If you own or have access to an appliance that is not supported at the moment then your help is welcome.
If you want to extend the bundle yourself, you have to do the followings:
- Define your thing type, channel types and channel groups according to openHAB development practices.
You can look at the meter and inverter types to get ideas how you can avoid repeating the same configuration over and over.
- Extend the `AbstractSunSpecHandler` and implement the handlePolledData method.
This method will be regularly called with the register data read from the appliance.
The method should parse the data and update the channels with them.
- The preferred way to parse the raw data is to write a parser for you model block type.
Your class should implement the `SunspecParser` class and it is preferred to extend the `AbstractBaseParser` class.
This base class has methods to accurately extract fields from the register array.
- The parser should only retrieve the data from the register array and return them in a block descriptor class.
Scaling and other higher level transformation should be done by the handler itself.
- To include your block type in auto discovery you have to add its id to the `SUPPORTED_THING_TYPES_UIDS` map in `SunSpecConstants`. This is enough for our discovery process to include your thing type in the results.

View File

@@ -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/openhab2-addons

View File

@@ -0,0 +1,234 @@
# SunSpec
This extension adds support for the SunSpec protocol.
SunSpec is a format for inverters and smart meters to communicate over the Modbus protocol.
It defines how common parameters like AC/DC voltage and current, lifetime produced energy, device temperature etc can be read from the device.
SunSpec is supported by several manufacturers like ABB, Fronius, LG, SMA, SolarEdge, Schneider Electric.
For a list of certified products see this page: https://sunspec.org/sunspec-certified-products/
## Supported Things
This bundle adds the following thing types to the Modbus binding.
Note, that the things will show up under the Modbus binding.
| Thing | Description |
|-----------------------|-----------------------------------------------------------------------|
| inverter-single-phase | For simple, single phase inverters |
| inverter-split-phase | Split phase inverters (Japanese grid and 240V grid in North America) |
| inverter-three-phase | Three phase inverters |
| meter-single-phase | Single phase meters (AN or AB) |
| meter-split-phase | Split single phase meters (ABN) |
| meter-wye-phase | Wye connected three phase meters (ABCN) |
| meter-delta-phase | Delta connected three phase meters (ABC) |
### Auto Discovery
This extension fully supports modbus auto discovery.
It automatically detects the register addresses for each model.
Auto discovery is turned off by default in the modbus binding so you have to enable it manually.
You can set the `enableDiscovery=true` parameter in your bridge.
A typical bridge configuration would look like this:
```
Bridge modbus:tcp:bridge [ host="10.0.0.2", port=502, id=1, enableDiscovery=true ]
```
## Thing Configuration
You need first to set up either a TCP or a Serial Modbus bridge according to the Modbus documentation.
Things in this extension will use the selected bridge to connect to the device.
For defining a thing textually, you have to find out the start address of the model block and the length of it.
While the length is usually fixed, the address is not.
Please refer to your device's vendor documentation how model blocks are laid for your equipment.
The following parameters are valid for all thing types:
| Parameter | Type | Required | Default if omitted | Description |
|-----------|---------|----------|---------------------|-----------------------------------------|
| address | integer | yes | N/A | Start address of the model block. |
| length | integer | yes | N/A | Length of the model block. Setting this too short could cause problems during parsing |
| refresh | integer | no | 5 | Poll interval in seconds. Increase this if you encounter connection errors |
| maxTries | integer | no | 3 | Number of retries when before giving up reading from this thing. |
## Channels
Channels are grouped into channel groups.
Different things support a subset of the following groups.
### Device Information Group (deviceInformation)
This group contains general operational information about the device.
| Channel ID | Item Type | Description |
|-------------------------|-----------------------|------------------------------------------------------------------------------------|
| cabinet-temperature | Number:Temperature | Temperature of the cabinet if supported in Celsius |
| heatsink-temperature | Number:Temperature | Device heat sink temperature in Celsius |
| transformer-temperature | Number:Temperature | Temperature of the transformer in Celsius |
| other-temperature | Number:Temperature | Any other temperature reading not covered by the above items if available. Celsius |
| status | String | Device status: OFF=Off, SLEEP=Sleeping/night mode, ON=On - producing power |
Supported by: all inverter things
### AC Summary Group (acGeneral)
#### Inverters
This group contains summarized values for the AC side of the inverter.
Even if the inverter supports multiple phases this group will appear only once.
| Channel ID | Item Type | Description |
|----------------------|------------------------|---------------------------------------------------------------------|
| ac-total-current | Number:ElectricCurrent | Total AC current over all phases in Amperes |
| ac-power | Number:Power | Actual AC power over all phases in Watts |
| ac-frequency | Number:Frequency | Actual grid frequency |
| ac-apparent-power | Number:Power | Actual AC apparent power |
| ac-reactive-power | Number:Power | Actual AC reactive power |
| ac-power-factor | Number:Dimensionless | Actual AC power factor (%) |
| ac-lifetime-energy | Number:Energy | AC lifetime energy production for this device in WattHours |
Supported by: all inverter things
#### Meters
This group contains summarized values for the power meter over all phases.
| Channel ID | Item Type | Description |
|--------------------------------------|--------------------------|---------------------------------------------------------------------|
| ac-total-current | Number:ElectricCurrent | Total AC current over all phases in Amperes |
| ac-average-voltage-to-n | Number:ElectricPotential | Average Line to Neutral AC Voltage over all phases |
| ac-average-voltage-to-next | Number:ElectricPotential | Average Line to Line AC Voltage over all phases |
| ac-frequency | Number:Frequency | Actual grid frequency |
| ac-total-real-power | Number:Power | Total Real Power over all phases(W) |
| ac-total-apparent-power | Number:Power | Total Apparent Power over all phases (W) |
| ac-total-reactive-power | Number:Power | Total Reactive Power over all phases (W) |
| ac-average-power-factor | Number:Dimensionless | Average AC Power Factor over all phases (%) |
| ac-total-exported-real-energy | Number:Energy | Total Real Energy Exported over all phases (Wh) |
| ac-total-imported-real-energy | Number:Energy | Total Real Energy Imported over all phases (Wh) |
| ac-total-exported-apparent-energy | Number:Energy | Total Apparent Energy Exported over all phases (VAh) |
| ac-total-imported-apparent-energy | Number:Energy | Total Apparent Energy Imported over all phases (VAh) |
| ac-total-imported-reactive-energy-q1 | Number:Energy | Total Reactive Energy Imported Quadrant 1 over all phases (VARh) |
| ac-total-imported-reactive-energy-q2 | Number:Energy | Total Reactive Energy Imported Quadrant 2 over all phases (VARh) |
| ac-total-exported-reactive-energy-q3 | Number:Energy | Total Reactive Energy Exported Quadrant 3 over all phases (VARh) |
| ac-total-exported-reactive-energy-q4 | Number:Energy | Total Reactive Energy Exported Quadrant 4 over all phases (VARh) |
Supported by: all meter things
### AC Phase Specific Group
#### Inverters
This group describes values for a single phase of the inverter.
There can be a maximum of three of this group named:
acPhaseA: available for all inverter types
acPhaseB: available for inverter-slit-phase and inverter-three-phase type inverters
acPhaseC: available only for inverter-three-phase type inverters.
| Channel ID | Item Type | Description |
|----------------------|--------------------------|---------------------------------------------------------------------|
| ac-phase-current | Number:ElectricCurrent | Actual current over this phase in Watts |
| ac-voltage-to-next | Number:ElectricPotential | Voltage of this phase relative to the next phase, or to the ground in case of single phase inverter. Note: some single phase SolarEdge inverters incorrectly use this value to report the voltage to neutral value|
| ac-voltage-to-n | Number:ElectricPotential | Voltage of this phase relative to the ground |
Supported by: all inverter things
#### Meters
This group holds values for a given line of the meter.
There can be a maximum of three of this group named:
acPhaseA: available for all meter types
acPhaseB: available for meter-split-phase, meter-wye-phase and meter-delta-phase meters
acPhaseC: available only for meter-wye-phase and meter-delta-phase meters type inverters.
| Channel ID | Item Type | Description |
|--------------------------------|--------------------------|---------------------------------------------------------------------|
| ac-phase-current | Number:ElectricCurrent | Actual current over this line in Watts |
| ac-voltage-to-n | Number:ElectricPotential | Voltage of this line relative to the neutral line |
| ac-voltage-to-next | Number:ElectricPotential | Voltage of this line relative to the next line |
| ac-real-power | Number:Power | AC Real Power value (W) |
| ac-apparent-power | Number:Power | AC Apparent Power value |
| ac-reactive-power | Number:Power | AC Reactive Power value |
| ac-power-factor | Number:Dimensionless | AC Power Factor (%) |
| ac-exported-real-energy | Number:Energy | Real Energy Exported (Wh |
| ac-imported-real-energy | Number:Energy | Real Energy Imported (Wh) |
| ac-exported-apparent-energy | Number:Energy | Apparent Energy Exported (VAh) |
| ac-imported-apparent-energy | Number:Energy | Apparent Energy Imported (VAh) |
| ac-imported-reactive-energy-q1 | Number:Energy | Reactive Energy Imported Quadrant 1 (VARh) |
| ac-imported-reactive-energy-q2 | Number:Energy | Reactive Energy Imported Quadrant 2 (VARh) |
| ac-exported-reactive-energy-q3 | Number:Energy | Reactive Energy Exported Quadrant 3 (VARh) |
| ac-exported-reactive-energy-q4 | Number:Energy | Reactive Energy Exported Quadrant 4 (VARh) |
Supported by: all meter things
### DC General Group
This group contains summarized data for the DC side of the inverter.
DC information is summarized even if the inverter has multiple strings.
| Channel ID | Item Type | Description |
|----------------------|--------------------------|---------------------------------------------------------------------|
| dc-current | Number:ElectricCurrent | Actual DC current in Amperes |
| dc-voltage | Number:ElectricPotential | Actual DC voltage |
| dc-power | Number:Power | Actual DC power produced |
Supported by: all inverter things
## Full Example
### Thing Configuration
```
Bridge modbus:tcp:bridge [ host="hostname|ip", port=502, id=1, enableDiscovery=true ]
Thing modbus:inverter-single-phase:bridge:se4000h "SE4000h" (modbus:tcp:modbusbridge) [ address=40069, length=52, refresh=15 ]
```
Note: Make sure that refresh, port and id values are numerical, without quotes.
### Item Configuration
```
Number Inverter_Temperature "Temperature [%.1f C]" {channel="modbus:inverter-single-phase:bridge:se4000h:deviceInformation#heatsink-temperature"}
Number Inverter_AC_Power "AC Power [%d W]" {channel="modbus:inverter-single-phase:bridge:se4000h:acGeneral#ac-power"}
Number Inverter_AC1_A "AC Current Phase 1 [%0.2f A]" {channel="modbus:inverter-single-phase:bridge:se4000h:acPhaseA#ac-phase-current"}
```
### Sitemap Configuration
```
Text item=Inverter_Temperature
Text item=Inverter_AC_Current
Text item=Inverter_AC_Power
Chart item=Inverter_Temperature period=D refresh=600000
Chart item=Inverter_AC_Power period=D refresh=30000
```
## Vendor Specific Information
### SolarEdge
Newer models of SolarEdge inverters can be monitored over TCP, but you need to enable support in the inverter first.
Refer to the "Modbus over TCP Configuration" chapter in this documentation: https://www.solaredge.com/sites/default/files/sunspec-implementation-technical-note.pdf
Modbus connection is limited to a single client at a time, so make sure no other clients are using the port.

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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 http://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>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.modbus.sunspec</artifactId>
<name>openHAB Add-ons :: Bundles :: SunSpec Bundle</name>
<dependencies>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.modbus</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.io.transport.modbus</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.modbus.sunspec-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>file:${basedirRoot}/bundles/org.openhab.io.transport.modbus/target/feature/feature.xml</repository>
<feature name="openhab-binding-modbus-sunspec" description="Modbus Binding SunSpec" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-modbus</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.modbus/${project.version}</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.modbus.sunspec/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2020 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.sunspec.internal;
/**
* Possible values for an inverter's status field
*
* @author Nagy Attila Gábor - Initial contribution
*/
public enum InverterStatus {
OFF(1),
SLEEP(2),
ON(4),
UNKNOWN(-1);
private final int code;
InverterStatus(int code) {
this.code = code;
}
public int code() {
return this.code;
}
public static InverterStatus getByCode(int code) {
switch (code) {
case 1:
return InverterStatus.OFF;
case 2:
return InverterStatus.SLEEP;
case 4:
return InverterStatus.ON;
default:
return InverterStatus.UNKNOWN;
}
}
}

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2010-2020 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.sunspec.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link SunSpecConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Nagy Attila Gábor - Initial contribution
*/
@NonNullByDefault
public class SunSpecConfiguration {
/**
* Refresh interval in seconds
*/
public long refresh = 60;
public int maxTries = 3;// backwards compatibility and tests
/**
* Base address of the block to parse. Only used at manual setup
*/
public int address;
/**
* Length of the block to parse. Only used at manual setup
*/
public int length;
/**
* Gets refresh period in milliseconds
*/
public long getRefreshMillis() {
return refresh * 1000;
}
}

View File

@@ -0,0 +1,149 @@
/**
* Copyright (c) 2010-2020 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.sunspec.internal;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.ModbusBindingConstants;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link SunSpecConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Nagy Attila Gábor - Initial contribution
*/
@NonNullByDefault
public class SunSpecConstants {
private static final String BINDING_ID = ModbusBindingConstants.BINDING_ID;
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_INVERTER_SINGLE_PHASE = new ThingTypeUID(BINDING_ID,
"inverter-single-phase");
public static final ThingTypeUID THING_TYPE_INVERTER_SPLIT_PHASE = new ThingTypeUID(BINDING_ID,
"inverter-split-phase");
public static final ThingTypeUID THING_TYPE_INVERTER_THREE_PHASE = new ThingTypeUID(BINDING_ID,
"inverter-three-phase");
public static final ThingTypeUID THING_TYPE_METER_SINGLE_PHASE = new ThingTypeUID(BINDING_ID, "meter-single-phase");
public static final ThingTypeUID THING_TYPE_METER_SPLIT_PHASE = new ThingTypeUID(BINDING_ID, "meter-split-phase");
public static final ThingTypeUID THING_TYPE_METER_WYE_PHASE = new ThingTypeUID(BINDING_ID, "meter-wye-phase");
public static final ThingTypeUID THING_TYPE_METER_DELTA_PHASE = new ThingTypeUID(BINDING_ID, "meter-delta-phase");
// Block types
public static final int COMMON_BLOCK = 1;
public static final int INVERTER_SINGLE_PHASE = 101;
public static final int INVERTER_SPLIT_PHASE = 102;
public static final int INVERTER_THREE_PHASE = 103;
public static final int METER_SINGLE_PHASE = 201;
public static final int METER_SPLIT_PHASE = 202;
public static final int METER_WYE_PHASE = 203;
public static final int METER_DELTA_PHASE = 204;
public static final int FINAL_BLOCK = 0xffff;
/**
* Map of the supported thing type uids, with their block type id
*/
public static final Map<Integer, ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashMap<>();
static {
SUPPORTED_THING_TYPES_UIDS.put(INVERTER_SINGLE_PHASE, THING_TYPE_INVERTER_SINGLE_PHASE);
SUPPORTED_THING_TYPES_UIDS.put(INVERTER_SPLIT_PHASE, THING_TYPE_INVERTER_SPLIT_PHASE);
SUPPORTED_THING_TYPES_UIDS.put(INVERTER_THREE_PHASE, THING_TYPE_INVERTER_THREE_PHASE);
SUPPORTED_THING_TYPES_UIDS.put(METER_SINGLE_PHASE, THING_TYPE_METER_SINGLE_PHASE);
SUPPORTED_THING_TYPES_UIDS.put(METER_SPLIT_PHASE, THING_TYPE_METER_SPLIT_PHASE);
SUPPORTED_THING_TYPES_UIDS.put(METER_WYE_PHASE, THING_TYPE_METER_WYE_PHASE);
SUPPORTED_THING_TYPES_UIDS.put(METER_DELTA_PHASE, THING_TYPE_METER_DELTA_PHASE);
}
// properties
public static final String PROPERTY_VENDOR = "vendor";
public static final String PROPERTY_MODEL = "model";
public static final String PROPERTY_VERSION = "version";
public static final String PROPERTY_PHASE_COUNT = "phaseCount";
public static final String PROPERTY_SERIAL_NUMBER = "serialNumber";
public static final String PROPERTY_BLOCK_ADDRESS = "blockAddress";
public static final String PROPERTY_BLOCK_LENGTH = "blockLength";
public static final String PROPERTY_UNIQUE_ADDRESS = "uniqueAddress";
// Channel group ids
public static final String GROUP_DEVICE_INFO = "deviceInformation";
public static final String GROUP_AC_GENERAL = "acGeneral";
public static final String GROUP_AC_PHASE_A = "acPhaseA";
public static final String GROUP_AC_PHASE_B = "acPhaseB";
public static final String GROUP_AC_PHASE_C = "acPhaseC";
public static final String GROUP_DC_GENERAL = "dcGeneral";
// List of all Channel ids in device information group
public static final String CHANNEL_PHASE_CONFIGURATION = "phase-configuration";
public static final String CHANNEL_CABINET_TEMPERATURE = "cabinet-temperature";
public static final String CHANNEL_HEATSINK_TEMPERATURE = "heatsink-temperature";
public static final String CHANNEL_TRANSFORMER_TEMPERATURE = "transformer-temperature";
public static final String CHANNEL_OTHER_TEMPERATURE = "other-temperature";
public static final String CHANNEL_STATUS = "status";
// List of channel ids in AC general group for inverter
public static final String CHANNEL_AC_TOTAL_CURRENT = "ac-total-current";
public static final String CHANNEL_AC_POWER = "ac-power";
public static final String CHANNEL_AC_FREQUENCY = "ac-frequency";
public static final String CHANNEL_AC_APPARENT_POWER = "ac-apparent-power";
public static final String CHANNEL_AC_REACTIVE_POWER = "ac-reactive-power";
public static final String CHANNEL_AC_POWER_FACTOR = "ac-power-factor";
public static final String CHANNEL_AC_LIFETIME_ENERGY = "ac-lifetime-energy";
// List of channels ids in AC general group for meter
public static final String CHANNEL_AC_AVERAGE_VOLTAGE_TO_N = "ac-average-voltage-to-n";
public static final String CHANNEL_AC_AVERAGE_VOLTAGE_TO_NEXT = "ac-average-voltage-to-next";
public static final String CHANNEL_AC_TOTAL_REAL_POWER = "ac-total-real-power";
public static final String CHANNEL_AC_TOTAL_APPARENT_POWER = "ac-total-apparent-power";
public static final String CHANNEL_AC_TOTAL_REACTIVE_POWER = "ac-total-reactive-power";
public static final String CHANNEL_AC_AVERAGE_POWER_FACTOR = "ac-average-power-factor";
public static final String CHANNEL_AC_TOTAL_EXPORTED_REAL_ENERGY = "ac-total-exported-real-energy";
public static final String CHANNEL_AC_TOTAL_IMPORTED_REAL_ENERGY = "ac-total-imported-real-energy";
public static final String CHANNEL_AC_TOTAL_EXPORTED_APPARENT_ENERGY = "ac-total-exported-apparent-energy";
public static final String CHANNEL_AC_TOTAL_IMPORTED_APPARENT_ENERGY = "ac-total-imported-apparent-energy";
public static final String CHANNEL_AC_TOTAL_IMPORTED_REACTIVE_ENERGY_Q1 = "ac-total-imported-reactive-energy-q1";
public static final String CHANNEL_AC_TOTAL_IMPORTED_REACTIVE_ENERGY_Q2 = "ac-total-imported-reactive-energy-q2";
public static final String CHANNEL_AC_TOTAL_EXPORTED_REACTIVE_ENERGY_Q3 = "ac-total-exported-reactive-energy-q3";
public static final String CHANNEL_AC_TOTAL_EXPORTED_REACTIVE_ENERGY_Q4 = "ac-total-exported-reactive-energy-q4";
// List of channel ids in AC phase group for inverter
public static final String CHANNEL_AC_PHASE_CURRENT = "ac-phase-current";
public static final String CHANNEL_AC_VOLTAGE_TO_NEXT = "ac-voltage-to-next";
public static final String CHANNEL_AC_VOLTAGE_TO_N = "ac-voltage-to-n";
// List of channel ids in DC group for inverter
public static final String CHANNEL_DC_CURRENT = "dc-current";
public static final String CHANNEL_DC_VOLTAGE = "dc-voltage";
public static final String CHANNEL_DC_POWER = "dc-power";
// List of channel ids in AC phase group for meter
public static final String CHANNEL_AC_REAL_POWER = "ac-real-power";
public static final String CHANNEL_AC_EXPORTED_REAL_ENERGY = "ac-exported-real-energy";
public static final String CHANNEL_AC_IMPORTED_REAL_ENERGY = "ac-imported-real-energy";
public static final String CHANNEL_AC_EXPORTED_APPARENT_ENERGY = "ac-exported-apparent-energy";
public static final String CHANNEL_AC_IMPORTED_APPARENT_ENERGY = "ac-imported-apparent-energy";
public static final String CHANNEL_AC_IMPORTED_REACTIVE_ENERGY_Q1 = "ac-imported-reactive-energy-q1";
public static final String CHANNEL_AC_IMPORTED_REACTIVE_ENERGY_Q2 = "ac-imported-reactive-energy-q2";
public static final String CHANNEL_AC_EXPORTED_REACTIVE_ENERGY_Q3 = "ac-exported-reactive-energy-q3";
public static final String CHANNEL_AC_EXPORTED_REACTIVE_ENERGY_Q4 = "ac-exported-reactive-energy-q4";
// Expected SunSpec ID This is a magic constant to distinguish SunSpec compatible
// devices from other modbus devices
public static final long SUNSPEC_ID = 0x53756e53;
// Size of SunSpect ID in words
public static final int SUNSPEC_ID_SIZE = 2;
// Size of any block header in words
public static final int MODEL_HEADER_SIZE = 2;
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2010-2020 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.sunspec.internal;
import static org.openhab.binding.modbus.sunspec.internal.SunSpecConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.modbus.sunspec.internal.handler.InverterHandler;
import org.openhab.binding.modbus.sunspec.internal.handler.MeterHandler;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SunSpecHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Nagy Attila Gábor - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.sunspec", service = ThingHandlerFactory.class)
public class SunSpecHandlerFactory extends BaseThingHandlerFactory {
/**
* Logger instance
*/
private final Logger logger = LoggerFactory.getLogger(SunSpecHandlerFactory.class);
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.containsValue(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(THING_TYPE_INVERTER_SINGLE_PHASE)
|| thingTypeUID.equals(THING_TYPE_INVERTER_SPLIT_PHASE)
|| thingTypeUID.equals(THING_TYPE_INVERTER_THREE_PHASE)) {
logger.debug("New InverterHandler created");
return new InverterHandler(thing);
} else if (thingTypeUID.equals(THING_TYPE_METER_SINGLE_PHASE)
|| thingTypeUID.equals(THING_TYPE_METER_SPLIT_PHASE) || thingTypeUID.equals(THING_TYPE_METER_WYE_PHASE)
|| thingTypeUID.equals(THING_TYPE_METER_DELTA_PHASE)) {
logger.debug("New MeterHandler created");
return new MeterHandler(thing);
}
return null;
}
}

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2020 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.sunspec.internal.discovery;
import static org.openhab.binding.modbus.sunspec.internal.SunSpecConstants.SUPPORTED_THING_TYPES_UIDS;
import java.util.HashSet;
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.thing.ThingTypeUID;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Discovery service for sunspec
*
* @author Nagy Attila Gabor - initial contribution
*
*/
@Component(immediate = true)
@NonNullByDefault
public class SunspecDiscoveryParticipant implements ModbusDiscoveryParticipant {
private final Logger logger = LoggerFactory.getLogger(SunspecDiscoveryParticipant.class);
@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return new HashSet<ThingTypeUID>(SUPPORTED_THING_TYPES_UIDS.values());
}
@Override
public void startDiscovery(ModbusEndpointThingHandler handler, ModbusDiscoveryListener listener) {
logger.trace("Starting sunspec discovery");
try {
new SunspecDiscoveryProcess(handler, listener).detectModel();
} catch (EndpointNotInitializedException ex) {
logger.debug("Could not start discovery process");
listener.discoveryFinished();
}
}
}

View File

@@ -0,0 +1,336 @@
/**
* Copyright (c) 2010-2020 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.sunspec.internal.discovery;
import static org.openhab.binding.modbus.sunspec.internal.SunSpecConstants.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.modbus.discovery.ModbusDiscoveryListener;
import org.openhab.binding.modbus.handler.EndpointNotInitializedException;
import org.openhab.binding.modbus.handler.ModbusEndpointThingHandler;
import org.openhab.binding.modbus.sunspec.internal.dto.CommonModelBlock;
import org.openhab.binding.modbus.sunspec.internal.dto.ModelBlock;
import org.openhab.binding.modbus.sunspec.internal.parser.CommonModelParser;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.thing.ThingUID;
import org.openhab.io.transport.modbus.AsyncModbusFailure;
import org.openhab.io.transport.modbus.ModbusBitUtilities;
import org.openhab.io.transport.modbus.ModbusCommunicationInterface;
import org.openhab.io.transport.modbus.ModbusConstants.ValueType;
import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
import org.openhab.io.transport.modbus.ModbusRegisterArray;
import org.openhab.io.transport.modbus.exception.ModbusSlaveErrorResponseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is used by the SunspecDiscoveryParticipant to detect
* the model blocks defined by the given device.
* It scans trough the defined model items and notifies the
* discovery service about the discovered devices
*
* @author Nagy Attila Gabor - Initial contribution
*/
@NonNullByDefault
public class SunspecDiscoveryProcess {
/**
* Logger instance
*/
private final Logger logger = LoggerFactory.getLogger(SunspecDiscoveryProcess.class);
/**
* The handler instance for this device
*/
private final ModbusEndpointThingHandler handler;
/**
* Listener for the discovered devices. We get this
* from the main discovery service, and it is used to
* submit any discovered Sunspec devices
*/
private final ModbusDiscoveryListener listener;
/**
* The endpoint's slave id
*/
private int slaveId;
/**
* Number of maximum retries
*/
private static final int maxTries = 3;
/**
* List of start addresses to try
*/
private Queue<Integer> possibleAddresses;
/**
* This is the base address where the next block should be searched for
*/
private int baseAddress = 40000;
/**
* Count of valid Sunspec blocks found
*/
private int blocksFound = 0;
/**
* Parser for commonblock
*/
private final CommonModelParser commonBlockParser;
/**
* The last common block found. This is used
* to get the details of any found devices
*/
private @Nullable CommonModelBlock lastCommonBlock = null;
/**
* Communication interface to the endpoint
*/
private ModbusCommunicationInterface comms;
/**
* New instances of this class should get a reference to the handler
*
* @throws EndpointNotInitializedException
*/
public SunspecDiscoveryProcess(ModbusEndpointThingHandler handler, ModbusDiscoveryListener listener)
throws EndpointNotInitializedException {
this.handler = handler;
ModbusCommunicationInterface localComms = handler.getCommunicationInterface();
if (localComms != null) {
this.comms = localComms;
} else {
throw new EndpointNotInitializedException();
}
slaveId = handler.getSlaveId();
this.listener = listener;
commonBlockParser = new CommonModelParser();
possibleAddresses = new ConcurrentLinkedQueue<>();
// Preferred and alternate base registers
// @see SunSpec Information Model Overview
possibleAddresses.add(40000);
possibleAddresses.add(50000);
possibleAddresses.add(0);
}
/**
* Start model detection
*
* @param uid the thing type to look for
* @throws EndpointNotInitializedException
*/
public void detectModel() {
if (possibleAddresses.isEmpty()) {
parsingFinished();
return;
}
// Try the next address from the possibles
baseAddress = possibleAddresses.poll();
logger.trace("Beginning scan for SunSpec device at address {}", baseAddress);
ModbusReadRequestBlueprint request = new ModbusReadRequestBlueprint(slaveId,
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, baseAddress, // Start address
SUNSPEC_ID_SIZE, // number or words to return
maxTries);
comms.submitOneTimePoll(request, result -> result.getRegisters().ifPresent(this::headerReceived),
this::handleError);
}
/**
* We received the first two words, that should equal to SunS
*/
private void headerReceived(ModbusRegisterArray registers) {
logger.trace("Received response from device {}", registers.toString());
Optional<DecimalType> id = ModbusBitUtilities.extractStateFromRegisters(registers, 0, ValueType.UINT32);
if (!id.isPresent() || id.get().longValue() != SUNSPEC_ID) {
logger.debug("Could not find SunSpec DID at address {}, received: {}, expected: {}", baseAddress, id,
SUNSPEC_ID);
detectModel();
return;
}
logger.trace("Header looks correct");
baseAddress += SUNSPEC_ID_SIZE;
lookForModelBlock();
}
/**
* Look for a valid model block at the current base address
*/
private void lookForModelBlock() {
ModbusReadRequestBlueprint request = new ModbusReadRequestBlueprint(slaveId,
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, baseAddress, // Start address
MODEL_HEADER_SIZE, // number or words to return
maxTries);
comms.submitOneTimePoll(request, result -> result.getRegisters().ifPresent(this::modelBlockReceived),
this::handleError);
}
/**
* We received a model block header
*/
private void modelBlockReceived(ModbusRegisterArray registers) {
logger.debug("Received response from device {}", registers.toString());
Optional<DecimalType> moduleID = ModbusBitUtilities.extractStateFromRegisters(registers, 0, ValueType.UINT16);
Optional<DecimalType> blockLength = ModbusBitUtilities.extractStateFromRegisters(registers, 1,
ValueType.UINT16);
if (!moduleID.isPresent() || !blockLength.isPresent()) {
logger.info("Could not find valid module id or block length field.");
parsingFinished();
return;
}
ModelBlock block = new ModelBlock();
block.address = baseAddress;
block.moduleID = moduleID.get().intValue();
block.length = blockLength.get().intValue() + MODEL_HEADER_SIZE;
logger.debug("SunSpec detector found block {}", block);
blocksFound++;
if (block.moduleID == FINAL_BLOCK) {
parsingFinished();
} else {
baseAddress += block.length;
if (block.moduleID == COMMON_BLOCK) {
readCommonBlock(block); // This is an asynchronous task
return;
} else {
createDiscoveryResult(block);
lookForModelBlock();
}
}
}
/**
* Start reading common block
*
* @param block
*/
private void readCommonBlock(ModelBlock block) {
ModbusReadRequestBlueprint request = new ModbusReadRequestBlueprint(slaveId,
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, block.address, // Start address
block.length, // number or words to return
maxTries);
comms.submitOneTimePoll(request, result -> result.getRegisters().ifPresent(this::parseCommonBlock),
this::handleError);
}
/**
* We've read the details of a common block now parse it, and
* store for later use
*
* @param registers
*/
private void parseCommonBlock(ModbusRegisterArray registers) {
logger.trace("Got common block data: {}", registers);
lastCommonBlock = commonBlockParser.parse(registers);
lookForModelBlock(); // Continue parsing
}
/**
* Create a discovery result from a model block
*
* @param block the block we've found
*/
private void createDiscoveryResult(ModelBlock block) {
if (!SUPPORTED_THING_TYPES_UIDS.containsKey(block.moduleID)) {
logger.debug("ModuleID {} is not supported, skipping this block", block.moduleID);
return;
}
CommonModelBlock commonBlock = lastCommonBlock;
if (commonBlock == null) {
logger.warn(
"Found model block without a preceding common block. Can't add device because details are unkown");
return;
}
ThingUID thingUID = new ThingUID(SUPPORTED_THING_TYPES_UIDS.get(block.moduleID), handler.getUID(),
Integer.toString(block.address));
Map<String, Object> properties = new HashMap<>();
properties.put(PROPERTY_VENDOR, commonBlock.manufacturer);
properties.put(PROPERTY_MODEL, commonBlock.model);
properties.put(PROPERTY_SERIAL_NUMBER, commonBlock.serialNumber);
properties.put(PROPERTY_VERSION, commonBlock.version);
properties.put(PROPERTY_BLOCK_ADDRESS, block.address);
properties.put(PROPERTY_BLOCK_LENGTH, block.length);
properties.put(PROPERTY_UNIQUE_ADDRESS, handler.getUID().getAsString() + ":" + block.address);
DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
.withRepresentationProperty(PROPERTY_UNIQUE_ADDRESS).withBridge(handler.getUID())
.withLabel(commonBlock.manufacturer + " " + commonBlock.model).build();
listener.thingDiscovered(result);
}
/**
* Parsing of model blocks finished
* Now we have to report back to the handler the common block and the block we were looking for
*/
private void parsingFinished() {
listener.discoveryFinished();
}
/**
* Handle errors received during communication
*/
private void handleError(AsyncModbusFailure<ModbusReadRequestBlueprint> failure) {
if (blocksFound > 1 && failure.getCause() instanceof ModbusSlaveErrorResponseException) {
int code = ((ModbusSlaveErrorResponseException) failure.getCause()).getExceptionCode();
if (code == ModbusSlaveErrorResponseException.ILLEGAL_DATA_ACCESS
|| code == ModbusSlaveErrorResponseException.ILLEGAL_DATA_VALUE) {
// It is very likely that the slave does not report an end block (0xffff) after the main blocks
// so we treat this situation as normal.
logger.debug(
"Seems like slave device does not report an end block. Continuing with the dectected blocks");
parsingFinished();
return;
}
}
String cls = failure.getCause().getClass().getName();
String msg = failure.getCause().getMessage();
logger.warn("Error with read at address {}: {} {}", baseAddress, cls, msg);
detectModel();
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 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.sunspec.internal.dto;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* This class contains information parsed from the Common Model block
*
* @author Nagy Attila Gabor - Initial contribution
*
*/
@NonNullByDefault
public class CommonModelBlock {
/**
* Value = 0x0001. Uniquely identifies this as a SunSpec Common Model Block
*/
public int sunSpecDID = 0x0001;
/**
* Length of block in 16-bit registers
*/
public int length = 0;
/**
* Modbus unit ID - this is a unique identifier of the device
*/
public int deviceAddress = 0;
// Manufacturer specific values
public String manufacturer = "";
public String model = "";
public String version = "";
public String serialNumber = "";
}

View File

@@ -0,0 +1,219 @@
/**
* Copyright (c) 2010-2020 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.sunspec.internal.dto;
import java.util.Optional;
/**
* Model for SunSpec compatible inverter data
*
* @author Nagy Attila Gabor - Initial contribution
*
*/
public class InverterModelBlock {
/**
* Type of inverter (single phase, split phase, three phase)
*/
public Integer phaseConfiguration;
/**
* Length of the block in 16bit words
*/
public Integer length;
/**
* AC Total Current value
*/
public Integer acCurrentTotal;
/**
* AC Phase A Current value
*/
public Integer acCurrentPhaseA;
/**
* AC Phase B Current value
*/
public Optional<Integer> acCurrentPhaseB;
/**
* AC Phase C Current value
*/
public Optional<Integer> acCurrentPhaseC;
/**
* AC Current scale factor
*/
public Short acCurrentSF;
/**
* AC Voltage Phase AB value
*/
public Optional<Integer> acVoltageAB;
/**
* AC Voltage Phase BC value
*/
public Optional<Integer> acVoltageBC;
/**
* AC Voltage Phase CA value
*/
public Optional<Integer> acVoltageCA;
/**
* AC Voltage Phase A to N value
*/
public Integer acVoltageAtoN;
/**
* AC Voltage Phase B to N value
*/
public Optional<Integer> acVoltageBtoN;
/**
* AC Voltage Phase C to N value
*/
public Optional<Integer> acVoltageCtoN;
/**
* AC Voltage scale factor
*/
public Short acVoltageSF;
/**
* AC Power value
*/
public Short acPower;
/**
* AC Power scale factor
*/
public Short acPowerSF;
/**
* AC Frequency value
*/
public Integer acFrequency;
/**
* AC Frequency scale factor
*/
public Short acFrequencySF;
/**
* Apparent power
*/
public Optional<Short> acApparentPower;
/**
* Apparent power scale factor
*/
public Optional<Short> acApparentPowerSF;
/**
* Reactive power
*/
public Optional<Short> acReactivePower;
/**
* Reactive power scale factor
*/
public Optional<Short> acReactivePowerSF;
/**
* Power factor
*/
public Optional<Short> acPowerFactor;
/**
* Power factor scale factor
*/
public Optional<Short> acPowerFactorSF;
/**
* AC Lifetime Energy production
*/
public Long acEnergyLifetime;
/**
* AC Lifetime Energy scale factor
*/
public Short acEnergyLifetimeSF;
/**
* DC Current value
*/
public Optional<Integer> dcCurrent;
/**
* DC Current scale factor
*/
public Optional<Short> dcCurrentSF;
/**
* DC Voltage value
*/
public Optional<Integer> dcVoltage;
/**
* DC Voltage scale factor
*/
public Optional<Short> dcVoltageSF;
/**
* DC Power value
*/
public Optional<Short> dcPower;
/**
* DC Power scale factor
*/
public Optional<Short> dcPowerSF;
/**
* Cabinet temperature
*/
public Short temperatureCabinet;
/**
* Heat sink temperature
*/
public Optional<Short> temperatureHeatsink;
/**
* Transformer temperature
*/
public Optional<Short> temperatureTransformer;
/**
* Other temperature
*/
public Optional<Short> temperatureOther;
/**
* Heat sink temperature scale factor
*/
public Short temperatureSF;
/**
* Current operating state
*/
public Integer status;
/**
* Vendor defined operating state or error code
*/
public Optional<Integer> statusVendor;
}

View File

@@ -0,0 +1,261 @@
/**
* Copyright (c) 2010-2020 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.sunspec.internal.dto;
import java.util.Optional;
/**
*
* Data object for the parsed information from a sunspec meter
*
* @author Nagy Attila Gabor - Initial contribution
*
*/
public class MeterModelBlock {
/**
* Sunspec device type id
*/
public Integer sunspecDID;
/**
* Block length
*/
public Integer length;
/**
* AC Total Current value
*/
public Short acCurrentTotal;
/**
* Descriptors for phase A
*/
public PhaseBlock phaseA = new PhaseBlock();
/**
* Descriptors for phase B
*/
public PhaseBlock phaseB = new PhaseBlock();
/**
* Descriptors for phase C
*/
public PhaseBlock phaseC = new PhaseBlock();
/**
* AC Current scale factor
*/
public Short acCurrentSF;
/**
* AC Voltage Line to line value
*/
public Optional<Short> acVoltageLineToNAverage;
/**
* AC Voltage Line to N value
*/
public Optional<Short> acVoltageLineToLineAverage;
/**
* AC Voltage scale factor
*/
public Short acVoltageSF;
/**
* AC Frequency value
*/
public Short acFrequency;
/**
* AC Frequency scale factor
*/
public Optional<Short> acFrequencySF;
/**
* Total real power
*/
public Short acRealPowerTotal;
/**
* AC Real Power Scale Factor
*/
public Short acRealPowerSF;
/**
* Total apparent power
*/
public Optional<Short> acApparentPowerTotal;
/**
* AC Apparent Power Scale Factor
*/
public Optional<Short> acApparentPowerSF;
/**
* Total reactive power
*/
public Optional<Short> acReactivePowerTotal;
/**
* AC Reactive Power Scale Factor
*/
public Optional<Short> acReactivePowerSF;
/**
* Power factor
*/
public Optional<Short> acPowerFactor;
/**
* Power factor scale factor
*/
public Optional<Short> acPowerFactorSF;
/**
* Total exported real energy
*/
public Optional<Long> acExportedRealEnergyTotal;
/**
* Total imported real energy
*/
public Long acImportedRealEnergyTotal;
/**
* Real Energy Scale Factor
*/
public Short acRealEnergySF;
/**
* Total exported apparent energy
*/
public Optional<Long> acExportedApparentEnergyTotal;
/**
* Total imported apparent energy
*/
public Optional<Long> acImportedApparentEnergyTotal;
/**
* Apparent Energy Scale Factor
*/
public Optional<Short> acApparentEnergySF;
/**
* Quadrant 1: Total imported reactive energy
*/
public Optional<Long> acImportedReactiveEnergyQ1Total;
/**
* Quadrant 2: Total imported reactive energy
*/
public Optional<Long> acImportedReactiveEnergyQ2Total;
/**
* Quadrant 3: Total exported reactive energy
*/
public Optional<Long> acExportedReactiveEnergyQ3Total;
/**
* Quadrant 4: Total exported reactive energy
*/
public Optional<Long> acExportedReactiveEnergyQ4Total;
/**
* Reactive Energy Scale Factor
*/
public Optional<Short> acReactiveEnergySF;
/**
* This subclass is used to store raw data for a single phase in
* multi phase meters.
*/
public static class PhaseBlock {
/**
* AC Phase A Current value
*/
public Optional<Short> acPhaseCurrent;
/**
* AC Voltage Phase Phase to N value
*/
public Optional<Short> acVoltageToN;
/**
* AC Voltage Phase Line to next Line value
*/
public Optional<Short> acVoltageToNext;
/**
* Phase A AC real power
*/
public Optional<Short> acRealPower;
/**
* Phase A AC apparent power
*/
public Optional<Short> acApparentPower;
/**
* Phase A AC reactive power
*/
public Optional<Short> acReactivePower;
/**
* Phase A Power factor
*/
public Optional<Short> acPowerFactor;
/**
* Phase A exported real energy
*/
public Optional<Long> acExportedRealEnergy;
/**
* Phase A imported real energy
*/
public Optional<Long> acImportedRealEnergy;
/**
* Phase A exported apparent energy
*/
public Optional<Long> acExportedApparentEnergy;
/**
* Phase A imported apparent energy
*/
public Optional<Long> acImportedApparentEnergy;
/**
* Quadrant 1: Phase A imported reactive energy
*/
public Optional<Long> acImportedReactiveEnergyQ1;
/**
* Quadrant 2: Phase A imported reactive energy
*/
public Optional<Long> acImportedReactiveEnergyQ2;
/**
* Quadrant 3: Phase A exported reactive energy
*/
public Optional<Long> acExportedReactiveEnergyQ3;
/**
* Quadrant 4: Phase A exported reactive energy
*/
public Optional<Long> acExportedReactiveEnergyQ4;
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2020 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.sunspec.internal.dto;
/**
* Descriptor for a model block found on the device
* This DTO contains only the metadata required to
* address the block at the modbus register space
*
* @author Nagy Attila Gabor - Initial contribution
*/
public class ModelBlock {
/**
* Base address of this block in 16bit words
*/
public int address;
/**
* Length of this block in 16bit words
*/
public int length;
/**
* Module identifier
*/
public int moduleID;
@Override
public String toString() {
return String.format("ModelBlock type=%d address=%d length=%d", moduleID, address, length);
}
}

View File

@@ -0,0 +1,467 @@
/**
* Copyright (c) 2010-2020 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.sunspec.internal.handler;
import static org.openhab.binding.modbus.sunspec.internal.SunSpecConstants.*;
import java.math.BigDecimal;
import java.util.Map;
import java.util.Optional;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.modbus.handler.EndpointNotInitializedException;
import org.openhab.binding.modbus.handler.ModbusEndpointThingHandler;
import org.openhab.binding.modbus.sunspec.internal.SunSpecConfiguration;
import org.openhab.binding.modbus.sunspec.internal.dto.ModelBlock;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.Bridge;
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.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.openhab.io.transport.modbus.AsyncModbusFailure;
import org.openhab.io.transport.modbus.ModbusCommunicationInterface;
import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
import org.openhab.io.transport.modbus.ModbusRegisterArray;
import org.openhab.io.transport.modbus.PollTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AbstractSunSpecHandler} is the base class for any sunspec handlers
* Common things are handled here:
*
* - loads the configuration either from the configuration file or
* from the properties that have been set by the auto discovery
* - sets up a regular poller to the device
* - handles incoming messages from the device:
* - common properties are parsed and published
* - other values are submitted to child implementations
* - handles disposal of the device by removing any handlers
* - implements some tool methods
*
* @author Nagy Attila Gabor - Initial contribution
*/
@NonNullByDefault
public abstract class AbstractSunSpecHandler extends BaseThingHandler {
/**
* Logger instance
*/
private final Logger logger = LoggerFactory.getLogger(AbstractSunSpecHandler.class);
/**
* Configuration instance
*/
protected @Nullable SunSpecConfiguration config = null;
/**
* This is the task used to poll the device
*/
private volatile @Nullable PollTask pollTask = null;
/**
* Communication interface to the slave endpoint we're connecting to
*/
protected volatile @Nullable ModbusCommunicationInterface comms = null;
/**
* This is the slave id, we store this once initialization is complete
*/
private volatile int slaveId;
/**
* Instances of this handler should get a reference to the modbus manager
*
* @param thing the thing to handle
* @param managerRef the modbus manager
*/
public AbstractSunSpecHandler(Thing thing) {
super(thing);
}
/**
* Handle incoming commands. This binding is read-only by default
*/
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// Currently we do not support any commands
}
/**
* Initialization:
* Load the config object of the block
* Connect to the slave bridge
* Start the periodic polling
*/
@Override
public void initialize() {
config = getConfigAs(SunSpecConfiguration.class);
logger.debug("Initializing thing with properties: {}", thing.getProperties());
startUp();
}
/*
* This method starts the operation of this handler
* Load the config object of the block
* Connect to the slave bridge
* Start the periodic polling
*/
private void startUp() {
connectEndpoint();
if (comms == null || config == null) {
logger.debug("Invalid endpoint/config/manager ref for sunspec handler");
return;
}
if (pollTask != null) {
return;
}
// Try properties first
@Nullable
ModelBlock mainBlock = getAddressFromProperties();
if (mainBlock == null) {
mainBlock = getAddressFromConfig();
}
if (mainBlock != null) {
publishUniqueAddress(mainBlock);
updateStatus(ThingStatus.UNKNOWN);
registerPollTask(mainBlock);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"SunSpec item should either have the address and length configuration set or should been created by auto discovery");
return;
}
}
/**
* Load and parse configuration from the properties
* These will be set by the auto discovery process
*/
private @Nullable ModelBlock getAddressFromProperties() {
Map<String, String> properties = thing.getProperties();
if (!properties.containsKey(PROPERTY_BLOCK_ADDRESS) || !properties.containsKey(PROPERTY_BLOCK_LENGTH)) {
return null;
}
try {
ModelBlock block = new ModelBlock();
block.address = (int) Double.parseDouble(thing.getProperties().get(PROPERTY_BLOCK_ADDRESS));
block.length = (int) Double.parseDouble(thing.getProperties().get(PROPERTY_BLOCK_LENGTH));
return block;
} catch (NumberFormatException ex) {
logger.debug("Could not parse address and length properties, error: {}", ex.getMessage());
return null;
}
}
/**
* Load configuration from main configuration
*/
private @Nullable ModelBlock getAddressFromConfig() {
@Nullable
SunSpecConfiguration myconfig = config;
if (myconfig == null) {
return null;
}
ModelBlock block = new ModelBlock();
block.address = myconfig.address;
block.length = myconfig.length;
return block;
}
/**
* Publish the unique address property if it has not been set before
*/
private void publishUniqueAddress(ModelBlock block) {
Map<String, String> properties = getThing().getProperties();
if (properties.containsKey(PROPERTY_UNIQUE_ADDRESS) && !properties.get(PROPERTY_UNIQUE_ADDRESS).isEmpty()) {
logger.debug("Current unique address is: {}", properties.get(PROPERTY_UNIQUE_ADDRESS));
return;
}
ModbusEndpointThingHandler handler = getEndpointThingHandler();
if (handler == null) {
return;
}
getThing().setProperty(PROPERTY_UNIQUE_ADDRESS, handler.getUID().getAsString() + ":" + block.address);
}
/**
* Dispose the binding correctly
*/
@Override
public void dispose() {
tearDown();
}
/**
* Unregister the poll task and release the endpoint reference
*/
private void tearDown() {
unregisterPollTask();
unregisterEndpoint();
}
/**
* Returns the current slave id from the bridge
*/
public int getSlaveId() {
return slaveId;
}
/**
* Get the endpoint handler from the bridge this handler is connected to
* Checks that we're connected to the right type of bridge
*
* @return the endpoint handler or null if the bridge does not exist
*/
private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
Bridge bridge = getBridge();
if (bridge == null) {
logger.debug("Bridge is null");
return null;
}
if (bridge.getStatus() != ThingStatus.ONLINE) {
logger.debug("Bridge is not online");
return null;
}
ThingHandler handler = bridge.getHandler();
if (handler == null) {
logger.debug("Bridge handler is null");
return null;
}
if (handler instanceof ModbusEndpointThingHandler) {
ModbusEndpointThingHandler slaveEndpoint = (ModbusEndpointThingHandler) handler;
return slaveEndpoint;
} else {
logger.debug("Unexpected bridge handler: {}", handler);
return null;
}
}
/**
* Get a reference to the modbus endpoint
*/
private void connectEndpoint() {
if (comms != null) {
return;
}
ModbusEndpointThingHandler slaveEndpointThingHandler = getEndpointThingHandler();
if (slaveEndpointThingHandler == null) {
@SuppressWarnings("null")
String label = Optional.ofNullable(getBridge()).map(b -> b.getLabel()).orElse("<null>");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
String.format("Bridge '%s' is offline", label));
logger.debug("No bridge handler available -- aborting init for {}", label);
return;
}
try {
slaveId = slaveEndpointThingHandler.getSlaveId();
comms = slaveEndpointThingHandler.getCommunicationInterface();
} catch (EndpointNotInitializedException e) {
// this will be handled below as endpoint remains null
}
if (comms == null) {
@SuppressWarnings("null")
String label = Optional.ofNullable(getBridge()).map(b -> b.getLabel()).orElse("<null>");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
String.format("Bridge '%s' not completely initialized", label));
logger.debug("Bridge not initialized fully (no endpoint) -- aborting init for {}", this);
return;
}
}
/**
* Remove the endpoint if exists
*/
private void unregisterEndpoint() {
// Comms will be close()'d by endpoint thing handler
comms = null;
}
/**
* Register poll task
* This is where we set up our regular poller
*/
private synchronized void registerPollTask(ModelBlock mainBlock) {
if (pollTask != null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
throw new IllegalStateException("pollTask should be unregistered before registering a new one!");
}
@Nullable
ModbusCommunicationInterface mycomms = comms;
@Nullable
SunSpecConfiguration myconfig = config;
if (myconfig == null || mycomms == null) {
throw new IllegalStateException("registerPollTask called without proper configuration");
}
logger.debug("Setting up regular polling");
ModbusReadRequestBlueprint request = new ModbusReadRequestBlueprint(getSlaveId(),
ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, mainBlock.address, mainBlock.length, myconfig.maxTries);
long refreshMillis = myconfig.getRefreshMillis();
pollTask = mycomms.registerRegularPoll(request, refreshMillis, 1000, result -> {
result.getRegisters().ifPresent(this::handlePolledData);
if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
}, this::handleError);
}
/**
* This method should handle incoming poll data, and update the channels
* with the values received
*/
protected abstract void handlePolledData(ModbusRegisterArray registers);
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
super.bridgeStatusChanged(bridgeStatusInfo);
logger.debug("Thing status changed to {}", this.getThing().getStatus().name());
if (getThing().getStatus() == ThingStatus.ONLINE) {
startUp();
} else if (getThing().getStatus() == ThingStatus.OFFLINE) {
tearDown();
}
}
/**
* Unregister poll task.
*
* No-op in case no poll task is registered, or if the initialization is incomplete.
*/
private synchronized void unregisterPollTask() {
@Nullable
PollTask task = pollTask;
if (task == null) {
return;
}
logger.debug("Unregistering polling from ModbusManager");
@Nullable
ModbusCommunicationInterface mycomms = comms;
if (mycomms != null) {
mycomms.unregisterRegularPoll(task);
}
pollTask = null;
}
/**
* Handle errors received during communication
*/
protected void handleError(AsyncModbusFailure<ModbusReadRequestBlueprint> failure) {
// Ignore all incoming data and errors if configuration is not correct
if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
return;
}
String msg = failure.getCause().getMessage();
String cls = failure.getCause().getClass().getName();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Error with read: %s: %s", cls, msg));
}
/**
* Returns true, if we're in a CONFIGURATION_ERROR state
*
* @return
*/
protected boolean hasConfigurationError() {
ThingStatusInfo statusInfo = getThing().getStatusInfo();
return statusInfo.getStatus() == ThingStatus.OFFLINE
&& statusInfo.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR;
}
/**
* Reset communication status to ONLINE if we're in an OFFLINE state
*/
protected void resetCommunicationError() {
ThingStatusInfo statusInfo = thing.getStatusInfo();
if (ThingStatus.OFFLINE.equals(statusInfo.getStatus())
&& ThingStatusDetail.COMMUNICATION_ERROR.equals(statusInfo.getStatusDetail())) {
updateStatus(ThingStatus.ONLINE);
}
}
/**
* Returns the channel UID for the specified group and channel id
*
* @param string the channel group
* @param string the channel id in that group
* @return the globally unique channel uid
*/
ChannelUID channelUID(String group, String id) {
return new ChannelUID(getThing().getUID(), group, id);
}
/**
* Returns value multiplied by the 10 on the power of scaleFactory
*
* @param value the value to alter
* @param scaleFactor the scale factor to use (may be negative)
* @return the scaled value as a DecimalType
*/
protected State getScaled(Optional<? extends Number> value, Optional<Short> scaleFactor, Unit<?> unit) {
if (!value.isPresent() || !scaleFactor.isPresent()) {
return UnDefType.UNDEF;
}
return getScaled(value.get().longValue(), scaleFactor.get(), unit);
}
/**
* Returns value multiplied by the 10 on the power of scaleFactory
*
* @param value the value to alter
* @param scaleFactor the scale factor to use (may be negative)
* @return the scaled value as a DecimalType
*/
protected State getScaled(Optional<? extends Number> value, Short scaleFactor, Unit<?> unit) {
return getScaled(value, Optional.of(scaleFactor), unit);
}
/**
* Returns value multiplied by the 10 on the power of scaleFactory
*
* @param value the value to alter
* @param scaleFactor the scale factor to use (may be negative)
* @return the scaled value as a DecimalType
*/
protected State getScaled(Number value, Short scaleFactor, Unit<?> unit) {
if (scaleFactor == 0) {
return new QuantityType<>(value.longValue(), unit);
}
return new QuantityType<>(BigDecimal.valueOf(value.longValue(), scaleFactor * -1), unit);
}
}

View File

@@ -0,0 +1,151 @@
/**
* Copyright (c) 2010-2020 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.sunspec.internal.handler;
import static org.openhab.binding.modbus.sunspec.internal.SunSpecConstants.*;
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
import static org.openhab.core.library.unit.SmartHomeUnits.*;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.sunspec.internal.InverterStatus;
import org.openhab.binding.modbus.sunspec.internal.dto.InverterModelBlock;
import org.openhab.binding.modbus.sunspec.internal.parser.InverterModelParser;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.UnDefType;
import org.openhab.io.transport.modbus.ModbusRegisterArray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link InverterHandler} is responsible for handling commands, which are
* sent to an inverter and publishing the received values to OpenHAB.
*
* @author Nagy Attila Gabor - Initial contribution
*/
@NonNullByDefault
public class InverterHandler extends AbstractSunSpecHandler {
/**
* Parser used to convert incoming raw messages into model blocks
*/
private final InverterModelParser parser = new InverterModelParser();
/**
* Logger instance
*/
private final Logger logger = LoggerFactory.getLogger(InverterHandler.class);
public InverterHandler(Thing thing) {
super(thing);
}
/**
* This method is called each time new data has been polled from the modbus slave
* The register array is first parsed, then each of the channels are updated
* to the new values
*
* @param registers byte array read from the modbus slave
*/
@Override
protected void handlePolledData(ModbusRegisterArray registers) {
logger.trace("Model block received, size: {}", registers.size());
InverterModelBlock block = parser.parse(registers);
// Device information group
updateState(channelUID(GROUP_DEVICE_INFO, CHANNEL_CABINET_TEMPERATURE),
getScaled(block.temperatureCabinet, block.temperatureSF, CELSIUS));
updateState(channelUID(GROUP_DEVICE_INFO, CHANNEL_HEATSINK_TEMPERATURE),
getScaled(block.temperatureHeatsink, Optional.of(block.temperatureSF), CELSIUS));
updateState(channelUID(GROUP_DEVICE_INFO, CHANNEL_TRANSFORMER_TEMPERATURE),
getScaled(block.temperatureTransformer, Optional.of(block.temperatureSF), CELSIUS));
updateState(channelUID(GROUP_DEVICE_INFO, CHANNEL_OTHER_TEMPERATURE),
getScaled(block.temperatureOther, Optional.of(block.temperatureSF), CELSIUS));
InverterStatus status = InverterStatus.getByCode(block.status);
updateState(channelUID(GROUP_DEVICE_INFO, CHANNEL_STATUS),
status == null ? UnDefType.UNDEF : new StringType(status.name()));
// AC General group
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_CURRENT),
getScaled(block.acCurrentTotal, block.acCurrentSF, AMPERE));
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_POWER), getScaled(block.acPower, block.acPowerSF, WATT));
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_FREQUENCY),
getScaled(block.acFrequency, block.acFrequencySF, HERTZ));
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_APPARENT_POWER),
getScaled(block.acApparentPower, block.acApparentPowerSF, WATT)); // TODO: VA currently not supported,
// see:
// https://github.com/openhab/openhab-core/pull/1347
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_REACTIVE_POWER),
getScaled(block.acReactivePower, block.acReactivePowerSF, WATT)); // TODO: var currently not supported,
// see:
// https://github.com/openhab/openhab-core/pull/1347
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_POWER_FACTOR),
getScaled(block.acPowerFactor, block.acPowerFactorSF, PERCENT));
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_LIFETIME_ENERGY),
getScaled(block.acEnergyLifetime, block.acEnergyLifetimeSF, WATT_HOUR));
// DC General group
updateState(channelUID(GROUP_DC_GENERAL, CHANNEL_DC_CURRENT),
getScaled(block.dcCurrent, block.dcCurrentSF, AMPERE));
updateState(channelUID(GROUP_DC_GENERAL, CHANNEL_DC_VOLTAGE),
getScaled(block.dcVoltage, block.dcVoltageSF, VOLT));
updateState(channelUID(GROUP_DC_GENERAL, CHANNEL_DC_POWER), getScaled(block.dcPower, block.dcPowerSF, WATT));
// AC Phase specific groups
// All types of inverters
updateState(channelUID(GROUP_AC_PHASE_A, CHANNEL_AC_PHASE_CURRENT),
getScaled(block.acCurrentPhaseA, block.acCurrentSF, AMPERE));
updateState(channelUID(GROUP_AC_PHASE_A, CHANNEL_AC_VOLTAGE_TO_NEXT),
getScaled(block.acVoltageAB, block.acVoltageSF, VOLT));
updateState(channelUID(GROUP_AC_PHASE_A, CHANNEL_AC_VOLTAGE_TO_N),
getScaled(block.acVoltageAtoN, block.acVoltageSF, VOLT));
// Split phase and three phase
if ((thing.getThingTypeUID().equals(THING_TYPE_INVERTER_SPLIT_PHASE)
|| thing.getThingTypeUID().equals(THING_TYPE_INVERTER_THREE_PHASE))
&& block.phaseConfiguration >= INVERTER_SPLIT_PHASE) {
updateState(channelUID(GROUP_AC_PHASE_B, CHANNEL_AC_PHASE_CURRENT),
getScaled(block.acCurrentPhaseB, block.acCurrentSF, AMPERE));
updateState(channelUID(GROUP_AC_PHASE_B, CHANNEL_AC_VOLTAGE_TO_NEXT),
getScaled(block.acVoltageBC, block.acVoltageSF, VOLT));
updateState(channelUID(GROUP_AC_PHASE_B, CHANNEL_AC_VOLTAGE_TO_N),
getScaled(block.acVoltageBtoN, block.acVoltageSF, VOLT));
}
// Three phase only
if (thing.getThingTypeUID().equals(THING_TYPE_INVERTER_THREE_PHASE)
&& block.phaseConfiguration >= INVERTER_THREE_PHASE) {
updateState(channelUID(GROUP_AC_PHASE_C, CHANNEL_AC_PHASE_CURRENT),
getScaled(block.acCurrentPhaseC, block.acCurrentSF, AMPERE));
updateState(channelUID(GROUP_AC_PHASE_C, CHANNEL_AC_VOLTAGE_TO_NEXT),
getScaled(block.acVoltageCA, block.acVoltageSF, VOLT));
updateState(channelUID(GROUP_AC_PHASE_C, CHANNEL_AC_VOLTAGE_TO_N),
getScaled(block.acVoltageCtoN, block.acVoltageSF, VOLT));
}
resetCommunicationError();
}
}

View File

@@ -0,0 +1,211 @@
/**
* Copyright (c) 2010-2020 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.sunspec.internal.handler;
import static org.openhab.binding.modbus.sunspec.internal.SunSpecConstants.*;
import static org.openhab.core.library.unit.SmartHomeUnits.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.sunspec.internal.dto.MeterModelBlock;
import org.openhab.binding.modbus.sunspec.internal.parser.MeterModelParser;
import org.openhab.core.thing.Thing;
import org.openhab.io.transport.modbus.ModbusRegisterArray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This handler is responsible for handling data recieved from a sunspec meter
*
* @author Nagy Attila Gabor - Initial contribution
*
*/
@NonNullByDefault
public class MeterHandler extends AbstractSunSpecHandler {
/**
* Parser used to convert incoming raw messages into model blocks
*/
private final MeterModelParser parser = new MeterModelParser();
/**
* Logger instance
*/
private final Logger logger = LoggerFactory.getLogger(MeterHandler.class);
public MeterHandler(Thing thing) {
super(thing);
}
/**
* Receive polled data, parse then update states
*/
@Override
protected void handlePolledData(ModbusRegisterArray registers) {
logger.trace("Model block received, size: {}", registers.size());
MeterModelBlock block = parser.parse(registers);
// AC General group
updateTotalValues(block);
updatePhaseValues(block, block.phaseA, GROUP_AC_PHASE_A);
// Split phase, wye/delta phase
if (block.sunspecDID >= METER_SPLIT_PHASE && (thing.getThingTypeUID().equals(THING_TYPE_METER_SPLIT_PHASE)
|| thing.getThingTypeUID().equals(THING_TYPE_METER_WYE_PHASE)
|| thing.getThingTypeUID().equals(THING_TYPE_METER_DELTA_PHASE))) {
updatePhaseValues(block, block.phaseB, GROUP_AC_PHASE_B);
}
// Three phase (wye/delta) only
if (block.sunspecDID >= INVERTER_THREE_PHASE && (thing.getThingTypeUID().equals(THING_TYPE_METER_WYE_PHASE)
|| thing.getThingTypeUID().equals(THING_TYPE_METER_DELTA_PHASE))) {
updatePhaseValues(block, block.phaseC, GROUP_AC_PHASE_C);
}
resetCommunicationError();
}
/**
* Update the total states from the received block
*
* @param block
*/
private void updateTotalValues(MeterModelBlock block) {
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_CURRENT),
getScaled(block.acCurrentTotal, block.acCurrentSF, AMPERE));
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_AVERAGE_VOLTAGE_TO_N),
getScaled(block.acVoltageLineToNAverage, block.acVoltageSF, VOLT));
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_AVERAGE_VOLTAGE_TO_NEXT),
getScaled(block.acVoltageLineToLineAverage, block.acVoltageSF, VOLT));
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_FREQUENCY),
getScaled(block.acFrequency, block.acFrequencySF.orElse((short) 1), HERTZ));
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_REAL_POWER),
getScaled(block.acRealPowerTotal, block.acRealPowerSF, WATT));
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_APPARENT_POWER),
getScaled(block.acApparentPowerTotal, block.acApparentPowerSF, WATT)); // TODO: this should be VA
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_REACTIVE_POWER),
getScaled(block.acReactivePowerTotal, block.acReactivePowerSF, WATT)); // TODO: this should be VAR
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_AVERAGE_POWER_FACTOR),
getScaled(block.acPowerFactor, block.acPowerFactorSF, PERCENT));
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_EXPORTED_REAL_ENERGY),
getScaled(block.acExportedRealEnergyTotal, block.acRealEnergySF, WATT_HOUR));
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_IMPORTED_REAL_ENERGY),
getScaled(block.acImportedRealEnergyTotal, block.acRealEnergySF, WATT_HOUR));
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_EXPORTED_APPARENT_ENERGY),
getScaled(block.acExportedApparentEnergyTotal, block.acApparentEnergySF, WATT_HOUR)); // TODO: this
// should be
// VA_HOUR
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_IMPORTED_APPARENT_ENERGY),
getScaled(block.acImportedApparentEnergyTotal, block.acApparentEnergySF, WATT_HOUR)); // TODO: this
// should be
// VA_HOUR
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_IMPORTED_REACTIVE_ENERGY_Q1),
getScaled(block.acImportedReactiveEnergyQ1Total, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this
// should be
// VAR_HOUR
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_IMPORTED_REACTIVE_ENERGY_Q2),
getScaled(block.acImportedReactiveEnergyQ2Total, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this
// should be
// VAR_HOUR
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_EXPORTED_REACTIVE_ENERGY_Q3),
getScaled(block.acExportedReactiveEnergyQ3Total, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this
// should be
// VAR_HOUR
updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_EXPORTED_REACTIVE_ENERGY_Q4),
getScaled(block.acExportedReactiveEnergyQ4Total, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this
// should be
// VAR_HOUR
}
/**
* Update phase related channels for the selected phase.
*
* @param block the main block for scale
* @param phaseBlock the block containing the raw values for the selected phase
* @param group channel group id for the output
*/
private void updatePhaseValues(MeterModelBlock block, MeterModelBlock.PhaseBlock phaseBlock, String group) {
updateState(channelUID(group, CHANNEL_AC_PHASE_CURRENT),
getScaled(phaseBlock.acPhaseCurrent, block.acCurrentSF, AMPERE));
updateState(channelUID(group, CHANNEL_AC_VOLTAGE_TO_N),
getScaled(phaseBlock.acVoltageToN, block.acVoltageSF, VOLT));
updateState(channelUID(group, CHANNEL_AC_VOLTAGE_TO_NEXT),
getScaled(phaseBlock.acVoltageToNext, block.acVoltageSF, VOLT));
updateState(channelUID(group, CHANNEL_AC_REAL_POWER),
getScaled(phaseBlock.acRealPower, block.acRealPowerSF, WATT));
updateState(channelUID(group, CHANNEL_AC_APPARENT_POWER),
getScaled(phaseBlock.acApparentPower, block.acApparentPowerSF, WATT)); // TODO: this should be VA
updateState(channelUID(group, CHANNEL_AC_REACTIVE_POWER),
getScaled(phaseBlock.acReactivePower, block.acReactivePowerSF, WATT)); // TODO: this should be VAR
updateState(channelUID(group, CHANNEL_AC_POWER_FACTOR),
getScaled(phaseBlock.acPowerFactor, block.acPowerFactorSF, PERCENT));
updateState(channelUID(group, CHANNEL_AC_EXPORTED_REAL_ENERGY),
getScaled(phaseBlock.acExportedRealEnergy, block.acRealEnergySF, WATT_HOUR));
updateState(channelUID(group, CHANNEL_AC_IMPORTED_REAL_ENERGY),
getScaled(phaseBlock.acImportedRealEnergy, block.acRealEnergySF, WATT_HOUR));
updateState(channelUID(group, CHANNEL_AC_EXPORTED_APPARENT_ENERGY),
getScaled(phaseBlock.acExportedApparentEnergy, block.acApparentEnergySF, WATT_HOUR)); // TODO: this
// should be
// VA_HOUR
updateState(channelUID(group, CHANNEL_AC_IMPORTED_APPARENT_ENERGY),
getScaled(phaseBlock.acImportedApparentEnergy, block.acApparentEnergySF, WATT_HOUR)); // TODO: this
// should be
// VA_HOUR
updateState(channelUID(group, CHANNEL_AC_IMPORTED_REACTIVE_ENERGY_Q1),
getScaled(phaseBlock.acImportedReactiveEnergyQ1, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this
// should be
// VAR_HOUR
updateState(channelUID(group, CHANNEL_AC_IMPORTED_REACTIVE_ENERGY_Q2),
getScaled(phaseBlock.acImportedReactiveEnergyQ2, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this
// should be
// VAR_HOUR
updateState(channelUID(group, CHANNEL_AC_EXPORTED_REACTIVE_ENERGY_Q3),
getScaled(phaseBlock.acExportedReactiveEnergyQ3, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this
// should be
// VAR_HOUR
updateState(channelUID(group, CHANNEL_AC_EXPORTED_REACTIVE_ENERGY_Q4),
getScaled(phaseBlock.acExportedReactiveEnergyQ4, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this
// should be
// VAR_HOUR
}
}

View File

@@ -0,0 +1,126 @@
/**
* Copyright (c) 2010-2020 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.sunspec.internal.parser;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.DecimalType;
import org.openhab.io.transport.modbus.ModbusBitUtilities;
import org.openhab.io.transport.modbus.ModbusConstants.ValueType;
import org.openhab.io.transport.modbus.ModbusRegisterArray;
/**
* Base class for parsers with some helper methods
*
* @author Nagy Attila Gabor - Initial contribution
*
*/
@NonNullByDefault
public class AbstractBaseParser {
/**
* Extract an optional int16 value
*
* @param raw the register array to extract from
* @param index the address of the field
* @return the parsed value or empty if the field is not implemented
*/
protected Optional<Short> extractOptionalInt16(ModbusRegisterArray raw, int index) {
return ModbusBitUtilities.extractStateFromRegisters(raw, index, ValueType.INT16).map(DecimalType::shortValue)
.filter(value -> value != (short) 0x8000);
}
/**
* Extract a mandatory int16 value
*
* @param raw the register array to extract from
* @param index the address of the field
* @param def the default value
* @return the parsed value or the default if the field is not implemented
*/
protected Short extractInt16(ModbusRegisterArray raw, int index, short def) {
return extractOptionalInt16(raw, index).orElse(def);
}
/**
* Extract an optional uint16 value
*
* @param raw the register array to extract from
* @param index the address of the field
* @return the parsed value or empty if the field is not implemented
*/
protected Optional<Integer> extractOptionalUInt16(ModbusRegisterArray raw, int index) {
return ModbusBitUtilities.extractStateFromRegisters(raw, index, ValueType.UINT16).map(DecimalType::intValue)
.filter(value -> value != 0xffff);
}
/**
* Extract a mandatory uint16 value
*
* @param raw the register array to extract from
* @param index the address of the field
* @param def the default value
* @return the parsed value or the default if the field is not implemented
*/
protected Integer extractUInt16(ModbusRegisterArray raw, int index, int def) {
return extractOptionalUInt16(raw, index).orElse(def);
}
/**
* Extract an optional acc32 value
*
* @param raw the register array to extract from
* @param index the address of the field
* @return the parsed value or empty if the field is not implemented
*/
protected Optional<Long> extractOptionalAcc32(ModbusRegisterArray raw, int index) {
return ModbusBitUtilities.extractStateFromRegisters(raw, index, ValueType.INT32).map(DecimalType::longValue)
.filter(value -> value != 0);
}
/**
* Extract a mandatory acc32 value
*
* @param raw the register array to extract from
* @param index the address of the field
* @param def the default value
* @return the parsed value or default if the field is not implemented
*/
protected Long extractAcc32(ModbusRegisterArray raw, int index, long def) {
return extractOptionalAcc32(raw, index).orElse(def);
}
/**
* Extract an optional scale factor
*
* @param raw the register array to extract from
* @param index the address of the field
* @return the parsed value or empty if the field is not implemented
*/
protected Optional<Short> extractOptionalSunSSF(ModbusRegisterArray raw, int index) {
return ModbusBitUtilities.extractStateFromRegisters(raw, index, ValueType.INT16).map(DecimalType::shortValue)
.filter(value -> value != (short) 0x8000);
}
/**
* Extract an mandatory scale factor
*
* @param raw the register array to extract from
* @param index the address of the field
* @return the parsed value or 1 if the field is not implemented
*/
protected Short extractSunSSF(ModbusRegisterArray raw, int index) {
return extractOptionalSunSSF(raw, index).orElse((short) 0);
}
}

View File

@@ -0,0 +1,66 @@
/**
* Copyright (c) 2010-2020 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.sunspec.internal.parser;
import java.nio.charset.Charset;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.sunspec.internal.SunSpecConstants;
import org.openhab.binding.modbus.sunspec.internal.dto.CommonModelBlock;
import org.openhab.io.transport.modbus.ModbusBitUtilities;
import org.openhab.io.transport.modbus.ModbusRegisterArray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Parser for the Common Message block
*
* @author Nagy Attila Gabor - Initial contribution
*
*/
@NonNullByDefault
public class CommonModelParser extends AbstractBaseParser implements SunspecParser<CommonModelBlock> {
/**
* Logger instance
*/
private final Logger logger = LoggerFactory.getLogger(CommonModelParser.class);
@Override
public CommonModelBlock parse(ModbusRegisterArray raw) {
CommonModelBlock block = new CommonModelBlock();
block.sunSpecDID = extractUInt16(raw, 0, 0);
block.length = extractUInt16(raw, 1, raw.size() - SunSpecConstants.MODEL_HEADER_SIZE);
if (block.length + SunSpecConstants.MODEL_HEADER_SIZE != raw.size()) {
logger.warn("Short read on common block loding. Expected size: {}, got: {}",
block.length + SunSpecConstants.MODEL_HEADER_SIZE, raw.size());
return block;
}
// parse manufacturer, model and version
block.manufacturer = ModbusBitUtilities.extractStringFromRegisters(raw, 2, 32, Charset.forName("UTF-8"))
.toString();
block.model = ModbusBitUtilities.extractStringFromRegisters(raw, 18, 32, Charset.forName("UTF-8")).toString();
block.version = ModbusBitUtilities.extractStringFromRegisters(raw, 42, 16, Charset.forName("UTF-8")).toString();
block.serialNumber = ModbusBitUtilities.extractStringFromRegisters(raw, 50, 32, Charset.forName("UTF-8"))
.toString();
block.deviceAddress = extractUInt16(raw, 66, 1);
return block;
}
}

View File

@@ -0,0 +1,79 @@
/**
* Copyright (c) 2010-2020 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.sunspec.internal.parser;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.sunspec.internal.SunSpecConstants;
import org.openhab.binding.modbus.sunspec.internal.dto.InverterModelBlock;
import org.openhab.io.transport.modbus.ModbusRegisterArray;
/**
* Parses inverter modbus data into an InverterModelBlock
*
* @author Nagy Attila Gabor - Initial contribution
*
*/
@NonNullByDefault
public class InverterModelParser extends AbstractBaseParser implements SunspecParser<InverterModelBlock> {
@Override
public InverterModelBlock parse(ModbusRegisterArray raw) {
InverterModelBlock block = new InverterModelBlock();
block.phaseConfiguration = extractUInt16(raw, 0, SunSpecConstants.INVERTER_SINGLE_PHASE);
block.length = extractUInt16(raw, 1, raw.size());
block.acCurrentTotal = extractUInt16(raw, 2, 0);
block.acCurrentPhaseA = extractUInt16(raw, 3, 0);
block.acCurrentPhaseB = extractOptionalUInt16(raw, 4);
block.acCurrentPhaseC = extractOptionalUInt16(raw, 5);
block.acCurrentSF = extractSunSSF(raw, 6);
block.acVoltageAB = extractOptionalUInt16(raw, 7);
block.acVoltageBC = extractOptionalUInt16(raw, 8);
block.acVoltageCA = extractOptionalUInt16(raw, 9);
block.acVoltageAtoN = extractUInt16(raw, 10, 0);
block.acVoltageBtoN = extractOptionalUInt16(raw, 11);
block.acVoltageCtoN = extractOptionalUInt16(raw, 12);
block.acVoltageSF = extractSunSSF(raw, 13);
block.acPower = extractInt16(raw, 14, (short) 0);
block.acPowerSF = extractSunSSF(raw, 15);
block.acFrequency = extractUInt16(raw, 16, 0);
block.acFrequencySF = extractSunSSF(raw, 17);
block.acApparentPower = extractOptionalInt16(raw, 18);
block.acApparentPowerSF = extractOptionalSunSSF(raw, 19);
block.acReactivePower = extractOptionalInt16(raw, 20);
block.acReactivePowerSF = extractOptionalSunSSF(raw, 21);
block.acPowerFactor = extractOptionalInt16(raw, 22);
block.acPowerFactorSF = extractOptionalSunSSF(raw, 23);
block.acEnergyLifetime = extractAcc32(raw, 24, 0);
block.acEnergyLifetimeSF = extractSunSSF(raw, 26);
block.dcCurrent = extractOptionalUInt16(raw, 27);
block.dcCurrentSF = extractOptionalSunSSF(raw, 28);
block.dcVoltage = extractOptionalUInt16(raw, 29);
block.dcVoltageSF = extractOptionalSunSSF(raw, 30);
block.dcPower = extractOptionalInt16(raw, 31);
block.dcPowerSF = extractOptionalSunSSF(raw, 32);
block.temperatureCabinet = extractInt16(raw, 33, (short) 0);
block.temperatureHeatsink = extractOptionalInt16(raw, 34);
block.temperatureTransformer = extractOptionalInt16(raw, 35);
block.temperatureOther = extractOptionalInt16(raw, 36);
block.temperatureSF = extractSunSSF(raw, 37);
block.status = extractUInt16(raw, 38, 1);
block.statusVendor = extractOptionalUInt16(raw, 39);
return block;
}
}

View File

@@ -0,0 +1,118 @@
/**
* Copyright (c) 2010-2020 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.sunspec.internal.parser;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.modbus.sunspec.internal.SunSpecConstants;
import org.openhab.binding.modbus.sunspec.internal.dto.MeterModelBlock;
import org.openhab.io.transport.modbus.ModbusRegisterArray;
/**
* Parser for sunspec compatible meters
*
* @author Nagy Attila Gabor - Initial contribution
*
*/
@NonNullByDefault
public class MeterModelParser extends AbstractBaseParser implements SunspecParser<MeterModelBlock> {
@Override
public MeterModelBlock parse(ModbusRegisterArray raw) {
MeterModelBlock block = new MeterModelBlock();
block.sunspecDID = extractUInt16(raw, 0, SunSpecConstants.METER_SINGLE_PHASE);
block.length = extractUInt16(raw, 1, raw.size());
block.acCurrentTotal = extractInt16(raw, 2, (short) 0);
block.phaseA.acPhaseCurrent = extractOptionalInt16(raw, 3);
block.phaseB.acPhaseCurrent = extractOptionalInt16(raw, 4);
block.phaseC.acPhaseCurrent = extractOptionalInt16(raw, 5);
block.acCurrentSF = extractSunSSF(raw, 6);
block.acVoltageLineToNAverage = extractOptionalInt16(raw, 7);
block.phaseA.acVoltageToN = extractOptionalInt16(raw, 8);
block.phaseB.acVoltageToN = extractOptionalInt16(raw, 9);
block.phaseC.acVoltageToN = extractOptionalInt16(raw, 10);
block.acVoltageLineToLineAverage = extractOptionalInt16(raw, 11);
block.phaseA.acVoltageToNext = extractOptionalInt16(raw, 12);
block.phaseB.acVoltageToNext = extractOptionalInt16(raw, 13);
block.phaseC.acVoltageToNext = extractOptionalInt16(raw, 14);
block.acVoltageSF = extractSunSSF(raw, 15);
block.acFrequency = extractInt16(raw, 16, (short) 0);
block.acFrequencySF = extractOptionalSunSSF(raw, 17);
block.acRealPowerTotal = extractInt16(raw, 18, (short) 0);
block.phaseA.acRealPower = extractOptionalInt16(raw, 19);
block.phaseB.acRealPower = extractOptionalInt16(raw, 20);
block.phaseC.acRealPower = extractOptionalInt16(raw, 21);
block.acRealPowerSF = extractSunSSF(raw, 22);
block.acApparentPowerTotal = extractOptionalInt16(raw, 23);
block.phaseA.acApparentPower = extractOptionalInt16(raw, 24);
block.phaseB.acApparentPower = extractOptionalInt16(raw, 25);
block.phaseC.acApparentPower = extractOptionalInt16(raw, 26);
block.acApparentPowerSF = extractOptionalSunSSF(raw, 27);
block.acReactivePowerTotal = extractOptionalInt16(raw, 28);
block.phaseA.acReactivePower = extractOptionalInt16(raw, 29);
block.phaseB.acReactivePower = extractOptionalInt16(raw, 30);
block.phaseC.acReactivePower = extractOptionalInt16(raw, 31);
block.acReactivePowerSF = extractOptionalSunSSF(raw, 32);
block.acPowerFactor = extractOptionalInt16(raw, 33);
block.phaseA.acPowerFactor = extractOptionalInt16(raw, 34);
block.phaseB.acPowerFactor = extractOptionalInt16(raw, 35);
block.phaseC.acPowerFactor = extractOptionalInt16(raw, 36);
block.acPowerFactorSF = extractOptionalSunSSF(raw, 37);
block.acExportedRealEnergyTotal = extractOptionalAcc32(raw, 38);
block.phaseA.acExportedRealEnergy = extractOptionalAcc32(raw, 40);
block.phaseB.acExportedRealEnergy = extractOptionalAcc32(raw, 42);
block.phaseC.acExportedRealEnergy = extractOptionalAcc32(raw, 44);
block.acImportedRealEnergyTotal = extractAcc32(raw, 46, 0);
block.phaseA.acImportedRealEnergy = extractOptionalAcc32(raw, 48);
block.phaseB.acImportedRealEnergy = extractOptionalAcc32(raw, 50);
block.phaseC.acImportedRealEnergy = extractOptionalAcc32(raw, 52);
block.acRealEnergySF = extractSunSSF(raw, 54);
block.acExportedApparentEnergyTotal = extractOptionalAcc32(raw, 55);
block.phaseA.acExportedApparentEnergy = extractOptionalAcc32(raw, 57);
block.phaseB.acExportedApparentEnergy = extractOptionalAcc32(raw, 59);
block.phaseC.acExportedApparentEnergy = extractOptionalAcc32(raw, 61);
block.acImportedApparentEnergyTotal = extractOptionalAcc32(raw, 63);
block.phaseA.acImportedApparentEnergy = extractOptionalAcc32(raw, 65);
block.phaseB.acImportedApparentEnergy = extractOptionalAcc32(raw, 67);
block.phaseC.acImportedApparentEnergy = extractOptionalAcc32(raw, 69);
block.acApparentEnergySF = extractOptionalSunSSF(raw, 71);
block.acImportedReactiveEnergyQ1Total = extractOptionalAcc32(raw, 72);
block.phaseA.acImportedReactiveEnergyQ1 = extractOptionalAcc32(raw, 74);
block.phaseB.acImportedReactiveEnergyQ1 = extractOptionalAcc32(raw, 76);
block.phaseC.acImportedReactiveEnergyQ1 = extractOptionalAcc32(raw, 78);
block.acImportedReactiveEnergyQ2Total = extractOptionalAcc32(raw, 80);
block.phaseA.acImportedReactiveEnergyQ2 = extractOptionalAcc32(raw, 82);
block.phaseB.acImportedReactiveEnergyQ2 = extractOptionalAcc32(raw, 84);
block.phaseC.acImportedReactiveEnergyQ2 = extractOptionalAcc32(raw, 86);
block.acExportedReactiveEnergyQ3Total = extractOptionalAcc32(raw, 88);
block.phaseA.acExportedReactiveEnergyQ3 = extractOptionalAcc32(raw, 90);
block.phaseB.acExportedReactiveEnergyQ3 = extractOptionalAcc32(raw, 92);
block.phaseC.acExportedReactiveEnergyQ3 = extractOptionalAcc32(raw, 94);
block.acExportedReactiveEnergyQ4Total = extractOptionalAcc32(raw, 96);
block.phaseA.acExportedReactiveEnergyQ4 = extractOptionalAcc32(raw, 98);
block.phaseB.acExportedReactiveEnergyQ4 = extractOptionalAcc32(raw, 100);
block.phaseC.acExportedReactiveEnergyQ4 = extractOptionalAcc32(raw, 102);
block.acReactiveEnergySF = extractOptionalSunSSF(raw, 104);
return block;
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2020 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.sunspec.internal.parser;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.io.transport.modbus.ModbusRegisterArray;
/**
* General interface for sunspec parsers
*
* Parsers are responsible to take the raw register array
* that was read from the device and to parse them into a SunSpecMessageBlock
* They should parse all reasonable fields into separate properties
* in the message block.
*
* Fields with unsupported values should be parsed as null values.
*
* In no way should a parser handle value scaling or device specific
* workarounds. These should be done in the handler.
*
* @author Nagy Attila Gabor - Initial contribution
*
*/
@NonNullByDefault
public interface SunspecParser<T> {
/**
* This method should parser an incoming register array and
* return a not-null sunspec block
*/
T parse(ModbusRegisterArray raw);
}

View File

@@ -0,0 +1,38 @@
<?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:sunspec:modbusconfig">
<parameter name="refresh" type="integer" min="0" unit="s">
<label>Refresh Interval</label>
<description>Poll interval. Use zero to disable automatic polling.</description>
<default>5</default>
</parameter>
<parameter name="address" type="integer" min="0" max="65535">
<label>Start Address</label>
<description>Start address of the model block</description>
<default>40000</default>
<advanced>true</advanced>
</parameter>
<parameter name="length" type="integer" min="1" max="65535">
<label>Block Length</label>
<description>Length of the model block in 2 byte words</description>
<default>61</default>
<advanced>true</advanced>
</parameter>
<parameter name="maxTries" type="integer" min="1">
<label>Maximum Tries When Reading</label>
<default>3</default>
<description>Number of tries when reading data, if some of the reading fail. For single try, enter 1.</description>
<advanced>true</advanced>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1,49 @@
<?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">
<channel-group-type id="device-information">
<label>Device Information</label>
<channels>
<channel id="cabinet-temperature" typeId="cabinet-temperature-type"/>
<channel id="heatsink-temperature" typeId="heatsink-temperature-type"/>
<channel id="transformer-temperature" typeId="transformer-temperature-type"/>
<channel id="other-temperature" typeId="other-temperature-type"/>
<channel id="status" typeId="status-type"/>
</channels>
</channel-group-type>
<channel-group-type id="ac-general">
<label>AC Summary</label>
<channels>
<channel id="ac-total-current" typeId="ac-total-current-type"/>
<channel id="ac-power" typeId="ac-power-type"/>
<channel id="ac-frequency" typeId="ac-frequency-type"/>
<channel id="ac-apparent-power" typeId="ac-apparent-power-type"/>
<channel id="ac-reactive-power" typeId="ac-reactive-power-type"/>
<channel id="ac-power-factor" typeId="ac-power-factor-type"/>
<channel id="ac-lifetime-energy" typeId="ac-lifetime-energy-type"/>
</channels>
</channel-group-type>
<channel-group-type id="ac-phase">
<label>AC Phase</label>
<channels>
<channel id="ac-phase-current" typeId="ac-phase-current-type"/>
<channel id="ac-voltage-to-next" typeId="ac-voltage-to-next-type"/>
<channel id="ac-voltage-to-n" typeId="ac-voltage-to-n-type"/>
</channels>
</channel-group-type>
<channel-group-type id="dc-general">
<label>DC summary</label>
<channels>
<channel id="dc-current" typeId="dc-current-type"/>
<channel id="dc-voltage" typeId="dc-voltage-type"/>
<channel id="dc-power" typeId="dc-power-type"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,123 @@
<?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">
<channel-type id="ac-total-current-type">
<item-type>Number:ElectricCurrent</item-type>
<label>AC Total Current Value</label>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="ac-phase-current-type">
<item-type>Number:ElectricCurrent</item-type>
<label>AC Phase Current Value</label>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="ac-voltage-to-next-type">
<item-type>Number:ElectricPotential</item-type>
<label>AC Voltage</label>
<description>This phase's AC voltage relative to the next phase</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-voltage-to-n-type">
<item-type>Number:ElectricPotential</item-type>
<label>AC Voltage Phase To N Value</label>
<description>This phase's AC voltage relative to N line</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-power-type">
<item-type>Number:Power</item-type>
<label>AC Power Value</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-frequency-type">
<item-type>Number:Frequency</item-type>
<label>AC Frequency Value</label>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="ac-apparent-power-type" advanced="true">
<item-type>Number:Power</item-type>
<label>AC Apparent Power Value</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-reactive-power-type" advanced="true">
<item-type>Number:Power</item-type>
<label>AC Reactive Power Value</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-power-factor-type">
<item-type>Number:Dimensionless</item-type>
<label>AC Power Factor</label>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="ac-lifetime-energy-type">
<item-type>Number:Energy</item-type>
<label>AC Lifetime Energy Production</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="dc-current-type">
<item-type>Number:ElectricCurrent</item-type>
<label>DC Current Value</label>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="dc-voltage-type">
<item-type>Number:ElectricPotential</item-type>
<label>DC Voltage Value</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="dc-power-type">
<item-type>Number:Power</item-type>
<label>DC Power Value</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="cabinet-temperature-type">
<item-type>Number:Temperature</item-type>
<label>Cabinet Temperature</label>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="heatsink-temperature-type">
<item-type>Number:Temperature</item-type>
<label>Heat Sink Temperature</label>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="transformer-temperature-type">
<item-type>Number:Temperature</item-type>
<label>Transformer Temperature</label>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="other-temperature-type">
<item-type>Number:Temperature</item-type>
<label>Other Temperature</label>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="status-type">
<item-type>String</item-type>
<label>Status</label>
<description>Device status</description>
<state readOnly="true">
<options>
<option value="OFF">Off</option>
<option value="SLEEP">Sleeping / Night mode</option>
<option value="ON">On - producing power</option>
</options>
</state>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,116 @@
<?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="inverter-single-phase">
<supported-bridge-type-refs>
<bridge-type-ref id="tcp"/>
<bridge-type-ref id="serial"/>
</supported-bridge-type-refs>
<label>Single Phase Inverter</label>
<description>Single phase inverter supporting SunSpec mapping over tcp modbus connection.</description>
<category>Inverter</category>
<channel-groups>
<channel-group id="deviceInformation" typeId="device-information"/>
<channel-group id="acGeneral" typeId="ac-general"/>
<channel-group id="acPhaseA" typeId="ac-phase">
<label>AC Detail</label>
</channel-group>
<channel-group id="dcGeneral" typeId="dc-general"/>
</channel-groups>
<properties>
<property name="vendor"/>
<property name="model"/>
<property name="version"/>
<property name="serialNumber"/>
<property name="uniqueAddress"/>
</properties>
<representation-property>uniqueAddress</representation-property>
<config-description-ref uri="thing-type:sunspec:modbusconfig"/>
</thing-type>
<thing-type id="inverter-split-phase">
<supported-bridge-type-refs>
<bridge-type-ref id="tcp"/>
<bridge-type-ref id="serial"/>
</supported-bridge-type-refs>
<label>Split Phase Inverter</label>
<description>Split phase (Japanese grid and 240V grid in North America) inverter supporting SunSpec mapping over tcp
modbus connection</description>
<category>Inverter</category>
<channel-groups>
<channel-group id="deviceInformation" typeId="device-information"/>
<channel-group id="acGeneral" typeId="ac-general"/>
<channel-group id="acPhaseA" typeId="ac-phase">
<label>AC Phase A (L1)</label>
</channel-group>
<channel-group id="acPhaseB" typeId="ac-phase">
<label>AC Phase B (L2)</label>
</channel-group>
<channel-group id="dcGeneral" typeId="dc-general"/>
</channel-groups>
<properties>
<property name="vendor"/>
<property name="model"/>
<property name="version"/>
<property name="serialNumber"/>
<property name="uniqueAddress"/>
</properties>
<representation-property>uniqueAddress</representation-property>
<config-description-ref uri="thing-type:sunspec:modbusconfig"/>
</thing-type>
<thing-type id="inverter-three-phase">
<supported-bridge-type-refs>
<bridge-type-ref id="tcp"/>
<bridge-type-ref id="serial"/>
</supported-bridge-type-refs>
<label>Three Phase Inverter</label>
<description>Three phase inverter supporting SunSpec mapping over tcp modbus connection</description>
<category>Inverter</category>
<channel-groups>
<channel-group id="deviceInformation" typeId="device-information"/>
<channel-group id="acGeneral" typeId="ac-general"/>
<channel-group id="acPhaseA" typeId="ac-phase">
<label>AC Phase A (L1)</label>
</channel-group>
<channel-group id="acPhaseB" typeId="ac-phase">
<label>AC Phase B (L2)</label>
</channel-group>
<channel-group id="acPhaseC" typeId="ac-phase">
<label>AC Phase C (L3)</label>
</channel-group>
<channel-group id="dcGeneral" typeId="dc-general"/>
</channel-groups>
<properties>
<property name="vendor"/>
<property name="model"/>
<property name="version"/>
<property name="serialNumber"/>
<property name="uniqueAddress"/>
</properties>
<representation-property>uniqueAddress</representation-property>
<config-description-ref uri="thing-type:sunspec:modbusconfig"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,50 @@
<?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">
<channel-group-type id="meter-ac-general">
<label>Summary</label>
<channels>
<channel id="ac-total-current" typeId="ac-total-current-type"/>
<channel id="ac-average-voltage-to-n" typeId="ac-average-voltage-to-n-type"/>
<channel id="ac-average-voltage-to-next" typeId="ac-average-voltage-to-next-type"/>
<channel id="ac-frequency" typeId="ac-frequency-type"/>
<channel id="ac-total-real-power" typeId="ac-total-real-power-type"/>
<channel id="ac-total-apparent-power" typeId="ac-total-apparent-power-type"/>
<channel id="ac-total-reactive-power" typeId="ac-total-reactive-power-type"/>
<channel id="ac-average-power-factor" typeId="ac-average-power-factor-type"/>
<channel id="ac-total-exported-real-energy" typeId="ac-total-exported-real-energy-type"/>
<channel id="ac-total-imported-real-energy" typeId="ac-total-imported-real-energy-type"/>
<channel id="ac-total-exported-apparent-energy" typeId="ac-total-exported-apparent-energy-type"/>
<channel id="ac-total-imported-apparent-energy" typeId="ac-total-imported-apparent-energy-type"/>
<channel id="ac-total-imported-reactive-energy-q1" typeId="ac-total-imported-reactive-energy-q1-type"/>
<channel id="ac-total-imported-reactive-energy-q2" typeId="ac-total-imported-reactive-energy-q2-type"/>
<channel id="ac-total-exported-reactive-energy-q3" typeId="ac-total-exported-reactive-energy-q3-type"/>
<channel id="ac-total-exported-reactive-energy-q4" typeId="ac-total-exported-reactive-energy-q4-type"/>
</channels>
</channel-group-type>
<channel-group-type id="meter-ac-phase">
<label>Phase</label>
<channels>
<channel id="ac-phase-current" typeId="ac-phase-current-type"/>
<channel id="ac-voltage-to-n" typeId="ac-voltage-to-n-type"/>
<channel id="ac-voltage-to-next" typeId="ac-voltage-to-next-type"/>
<channel id="ac-real-power" typeId="ac-real-power-type"/>
<channel id="ac-apparent-power" typeId="ac-apparent-power-type"/>
<channel id="ac-reactive-power" typeId="ac-reactive-power-type"/>
<channel id="ac-power-factor" typeId="ac-power-factor-type"/>
<channel id="ac-exported-real-energy" typeId="ac-exported-real-energy-type"/>
<channel id="ac-imported-real-energy" typeId="ac-imported-real-energy-type"/>
<channel id="ac-exported-apparent-energy" typeId="ac-exported-apparent-energy-type"/>
<channel id="ac-imported-apparent-energy" typeId="ac-imported-apparent-energy-type"/>
<channel id="ac-imported-reactive-energy-q1" typeId="ac-imported-reactive-energy-q1-type"/>
<channel id="ac-imported-reactive-energy-q2" typeId="ac-imported-reactive-energy-q2-type"/>
<channel id="ac-exported-reactive-energy-q3" typeId="ac-exported-reactive-energy-q3-type"/>
<channel id="ac-exported-reactive-energy-q4" typeId="ac-exported-reactive-energy-q4-type"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,126 @@
<?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">
<channel-type id="ac-average-voltage-to-n-type">
<item-type>Number:ElectricPotential</item-type>
<label>Average Line To Neutral AC Voltage</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-average-voltage-to-next-type">
<item-type>Number:ElectricPotential</item-type>
<label>Average Line To Line AC Voltage</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-total-real-power-type">
<item-type>Number:Power</item-type>
<label>Total Real Power Value</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-real-power-type">
<item-type>Number:Power</item-type>
<label>AC Real Power Value</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-total-apparent-power-type">
<item-type>Number:Power</item-type>
<label>Total Apparent Power Value</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-total-reactive-power-type">
<item-type>Number:Power</item-type>
<label>Total Reactive Power Value</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-average-power-factor-type">
<item-type>Number:Dimensionless</item-type>
<label>Average AC Power Factor</label>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="ac-total-exported-real-energy-type">
<item-type>Number:Energy</item-type>
<label>Total Real Energy Exported</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-exported-real-energy-type">
<item-type>Number:Energy</item-type>
<label>Real Energy Exported</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-total-imported-real-energy-type">
<item-type>Number:Energy</item-type>
<label>Total Real Energy Imported</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-imported-real-energy-type">
<item-type>Number:Energy</item-type>
<label>Real Energy Imported</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-total-exported-apparent-energy-type">
<item-type>Number:Energy</item-type>
<label>Total Apparent Energy Exported</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-exported-apparent-energy-type">
<item-type>Number:Energy</item-type>
<label>Apparent Energy Exported</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-total-imported-apparent-energy-type">
<item-type>Number:Energy</item-type>
<label>Total Apparent Energy Imported</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-imported-apparent-energy-type">
<item-type>Number:Energy</item-type>
<label>Apparent Energy Imported</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-total-imported-reactive-energy-q1-type">
<item-type>Number:Energy</item-type>
<label>Total Reactive Energy Imported Quadrant 1</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-imported-reactive-energy-q1-type">
<item-type>Number:Energy</item-type>
<label>Reactive Energy Imported Quadrant 1</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-total-imported-reactive-energy-q2-type">
<item-type>Number:Energy</item-type>
<label>Total Reactive Energy Imported Quadrant 2</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-imported-reactive-energy-q2-type">
<item-type>Number:Energy</item-type>
<label>Reactive Energy Imported Quadrant 2</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-total-exported-reactive-energy-q3-type">
<item-type>Number:Energy</item-type>
<label>Total Reactive Energy Exported Quadrant 3</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-exported-reactive-energy-q3-type">
<item-type>Number:Energy</item-type>
<label>Reactive Energy Exported Quadrant 3</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-total-exported-reactive-energy-q4-type">
<item-type>Number:Energy</item-type>
<label>Total Reactive Energy Exported Quadrant 4</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ac-exported-reactive-energy-q4-type">
<item-type>Number:Energy</item-type>
<label>Reactive Energy Exported Quadrant 4</label>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,141 @@
<?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="meter-single-phase">
<supported-bridge-type-refs>
<bridge-type-ref id="tcp"/>
<bridge-type-ref id="serial"/>
</supported-bridge-type-refs>
<label>Single Phase Meter</label>
<description>Single phase (AN or AB) meter supporting SunSpec mapping over modbus connection</description>
<category>Meter</category>
<channel-groups>
<channel-group id="acGeneral" typeId="meter-ac-general"/>
<channel-group id="acPhaseA" typeId="meter-ac-phase">
<label>AC Phase A</label>
</channel-group>
</channel-groups>
<properties>
<property name="vendor"/>
<property name="model"/>
<property name="version"/>
<property name="serialNumber"/>
<property name="uniqueAddress"/>
</properties>
<representation-property>uniqueAddress</representation-property>
<config-description-ref uri="thing-type:sunspec:modbusconfig"/>
</thing-type>
<thing-type id="meter-split-phase">
<supported-bridge-type-refs>
<bridge-type-ref id="tcp"/>
<bridge-type-ref id="serial"/>
</supported-bridge-type-refs>
<label>Split Phase Meter</label>
<description>Split phase (ABN) meter supporting SunSpec mapping over modbus connection</description>
<category>Meter</category>
<channel-groups>
<channel-group id="acGeneral" typeId="meter-ac-general"/>
<channel-group id="acPhaseA" typeId="meter-ac-phase">
<label>AC Phase A</label>
</channel-group>
<channel-group id="acPhaseB" typeId="meter-ac-phase">
<label>AC Phase B</label>
</channel-group>
</channel-groups>
<properties>
<property name="vendor"/>
<property name="model"/>
<property name="version"/>
<property name="serialNumber"/>
<property name="uniqueAddress"/>
</properties>
<representation-property>uniqueAddress</representation-property>
<config-description-ref uri="thing-type:sunspec:modbusconfig"/>
</thing-type>
<thing-type id="meter-wye-phase">
<supported-bridge-type-refs>
<bridge-type-ref id="tcp"/>
<bridge-type-ref id="serial"/>
</supported-bridge-type-refs>
<label>Three Phase Meter, Wye-Connected</label>
<description>Wye-connected three phase (ABCN) meter supporting SunSpec mapping over modbus connection</description>
<category>Meter</category>
<channel-groups>
<channel-group id="acGeneral" typeId="meter-ac-general"/>
<channel-group id="acPhaseA" typeId="meter-ac-phase">
<label>AC Phase A</label>
</channel-group>
<channel-group id="acPhaseB" typeId="meter-ac-phase">
<label>AC Phase B</label>
</channel-group>
<channel-group id="acPhaseC" typeId="meter-ac-phase">
<label>AC Phase C</label>
</channel-group>
</channel-groups>
<properties>
<property name="vendor"/>
<property name="model"/>
<property name="version"/>
<property name="serialNumber"/>
<property name="uniqueAddress"/>
</properties>
<representation-property>uniqueAddress</representation-property>
<config-description-ref uri="thing-type:sunspec:modbusconfig"/>
</thing-type>
<thing-type id="meter-delta-phase">
<supported-bridge-type-refs>
<bridge-type-ref id="tcp"/>
<bridge-type-ref id="serial"/>
</supported-bridge-type-refs>
<label>Three Phase Meter, Delta-Connected</label>
<description>Delta-connected three phase (ABC) meter supporting SunSpec mapping over modbus connection</description>
<category>Meter</category>
<channel-groups>
<channel-group id="acGeneral" typeId="meter-ac-general"/>
<channel-group id="acPhaseA" typeId="meter-ac-phase">
<label>AC Phase A</label>
</channel-group>
<channel-group id="acPhaseB" typeId="meter-ac-phase">
<label>AC Phase B</label>
</channel-group>
<channel-group id="acPhaseC" typeId="meter-ac-phase">
<label>AC Phase C</label>
</channel-group>
</channel-groups>
<properties>
<property name="vendor"/>
<property name="model"/>
<property name="version"/>
<property name="serialNumber"/>
<property name="uniqueAddress"/>
</properties>
<representation-property>uniqueAddress</representation-property>
<config-description-ref uri="thing-type:sunspec:modbusconfig"/>
</thing-type>
</thing:thing-descriptions>