added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -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