added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.modbus.sunspec/.classpath
Normal file
32
bundles/org.openhab.binding.modbus.sunspec/.classpath
Normal 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>
|
||||
23
bundles/org.openhab.binding.modbus.sunspec/.project
Normal file
23
bundles/org.openhab.binding.modbus.sunspec/.project
Normal 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>
|
||||
22
bundles/org.openhab.binding.modbus.sunspec/DEVELOPERS.md
Normal file
22
bundles/org.openhab.binding.modbus.sunspec/DEVELOPERS.md
Normal 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.
|
||||
13
bundles/org.openhab.binding.modbus.sunspec/NOTICE
Normal file
13
bundles/org.openhab.binding.modbus.sunspec/NOTICE
Normal 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
|
||||
234
bundles/org.openhab.binding.modbus.sunspec/README.md
Normal file
234
bundles/org.openhab.binding.modbus.sunspec/README.md
Normal 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.
|
||||
32
bundles/org.openhab.binding.modbus.sunspec/pom.xml
Normal file
32
bundles/org.openhab.binding.modbus.sunspec/pom.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 = "";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user