[resol] Add Resol Controller Binding - Initial Contribution (#9449)

Signed-off-by: Raphael Mack <ramack@raphael-mack.de>
This commit is contained in:
Raphael Mack
2021-04-11 19:25:55 +02:00
committed by GitHub
parent 16ffeecb90
commit 9bfb2f4313
24 changed files with 2211 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.resol-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-resol" description="Resol Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.resol/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2021 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.resol.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.binding.BaseThingHandler;
import de.resol.vbus.Packet;
import de.resol.vbus.Specification;
import de.resol.vbus.SpecificationFile.Language;
/**
* The {@link ResolBaseThingHandler} class is a common ancestor for Resol thing handlers, capabale of handling vbus
* packets
*
* @author Raphael Mack - Initial contribution
*/
@NonNullByDefault
public abstract class ResolBaseThingHandler extends BaseThingHandler {
public ResolBaseThingHandler(Thing thing) {
super(thing);
}
protected abstract void packetReceived(Specification spec, Language lang, Packet packet);
}

View File

@@ -0,0 +1,341 @@
/**
* Copyright (c) 2010-2021 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.resol.handler;
import java.io.IOException;
import java.net.InetAddress;
import java.util.Collection;
import java.util.Collections;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.resol.internal.ResolBindingConstants;
import org.openhab.binding.resol.internal.ResolBridgeConfiguration;
import org.openhab.binding.resol.internal.discovery.ResolDeviceDiscoveryService;
import org.openhab.core.i18n.LocaleProvider;
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.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.resol.vbus.Connection;
import de.resol.vbus.Connection.ConnectionState;
import de.resol.vbus.ConnectionAdapter;
import de.resol.vbus.Packet;
import de.resol.vbus.Specification;
import de.resol.vbus.SpecificationFile;
import de.resol.vbus.SpecificationFile.Language;
import de.resol.vbus.TcpDataSource;
import de.resol.vbus.TcpDataSourceProvider;
/**
* The {@link ResolBridgeHandler} class handles the connection to the VBUS/LAN adapter.
*
* @author Raphael Mack - Initial contribution
*/
@NonNullByDefault
public class ResolBridgeHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(ResolBridgeHandler.class);
private String ipAddress = "";
private String password = "";
private int refreshInterval;
private boolean isConnected = false;
private String unconnectedReason = "";
// Background Runnable
private @Nullable ScheduledFuture<?> pollingJob;
private @Nullable Connection tcpConnection;
private final Specification spec;
// Managing Thing Discovery Service
private @Nullable ResolDeviceDiscoveryService discoveryService = null;
private boolean scanning;
private final @Nullable LocaleProvider localeProvider;
public ResolBridgeHandler(Bridge bridge, @Nullable LocaleProvider localeProvider) {
super(bridge);
spec = Specification.getDefaultSpecification();
this.localeProvider = localeProvider;
}
public void updateStatus() {
if (isConnected) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, unconnectedReason);
}
}
public void registerDiscoveryService(ResolDeviceDiscoveryService discoveryService) {
this.discoveryService = discoveryService;
}
public void unregisterDiscoveryService() {
discoveryService = null;
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(ResolDeviceDiscoveryService.class);
}
public void registerResolThingListener(ResolEmuEMThingHandler resolEmuEMThingHandler) {
synchronized (this) {
Connection con = tcpConnection;
if (con != null) {
resolEmuEMThingHandler.useConnection(con);
}
}
}
private void pollingRunnable() {
if (!isConnected) {
synchronized (ResolBridgeHandler.this) {
Connection connection = tcpConnection;
/* first cleanup in case there is an old but failed TCP connection around */
try {
if (connection != null) {
connection.disconnect();
getThing().getThings().stream().forEach(thing -> {
ThingHandler th = thing.getHandler();
if (th instanceof ResolEmuEMThingHandler) {
((ResolEmuEMThingHandler) th).stop();
}
});
connection = null;
tcpConnection = null;
}
} catch (IOException e) {
logger.warn("TCP disconnect failed: {}", e.getMessage());
}
TcpDataSource source = null;
/* now try to establish a new TCP connection */
try {
source = TcpDataSourceProvider.fetchInformation(InetAddress.getByName(ipAddress), 500);
if (source != null) {
source.setLivePassword(password);
}
} catch (IOException e) {
isConnected = false;
unconnectedReason = Objects.requireNonNullElse(e.getMessage(), "");
}
if (source != null) {
try {
logger.debug("Opening a new connection to {} {} @{}", source.getProduct(),
source.getDeviceName(), source.getAddress());
connection = source.connectLive(0, 0x0020);
tcpConnection = connection;
} catch (Exception e) {
// this generic Exception catch is required, as TcpDataSource.connectLive throws this
// generic type
isConnected = false;
unconnectedReason = Objects.requireNonNullElse(e.getMessage(), "");
}
if (connection != null) {
// Add a listener to the Connection to monitor state changes and
// read incoming frames
connection.addListener(new ResolConnectorAdapter());
}
}
// Establish the connection
if (connection != null) {
try {
connection.connect();
final Connection c = connection;
// now set the connection the thing handlers for the emulated EMs
getThing().getThings().stream().forEach(thing -> {
ThingHandler th = thing.getHandler();
if (th instanceof ResolEmuEMThingHandler) {
((ResolEmuEMThingHandler) th).useConnection(c);
}
});
} catch (IOException e) {
unconnectedReason = Objects.requireNonNullElse(e.getMessage(), "");
isConnected = false;
}
} else {
isConnected = false;
}
if (!isConnected) {
logger.debug("Cannot establish connection to {} ({})", ipAddress, unconnectedReason);
} else {
unconnectedReason = "";
}
updateStatus();
}
}
}
private synchronized void startAutomaticRefresh() {
ScheduledFuture<?> job = pollingJob;
if (job == null || job.isCancelled()) {
pollingJob = scheduler.scheduleWithFixedDelay(this::pollingRunnable, 0, refreshInterval, TimeUnit.SECONDS);
}
}
public ThingStatus getStatus() {
return getThing().getStatus();
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// No commands supported - nothing to do
}
@Override
public void initialize() {
updateStatus();
ResolBridgeConfiguration configuration = getConfigAs(ResolBridgeConfiguration.class);
ipAddress = configuration.ipAddress;
refreshInterval = configuration.refreshInterval;
password = configuration.password;
startAutomaticRefresh();
}
@Override
public void dispose() {
ScheduledFuture<?> job = pollingJob;
if (job != null) {
job.cancel(true);
pollingJob = null;
}
try {
Connection connection = tcpConnection;
if (connection != null) {
connection.disconnect();
getThing().getThings().stream().forEach(thing -> {
ThingHandler th = thing.getHandler();
if (th instanceof ResolEmuEMThingHandler) {
((ResolEmuEMThingHandler) th).stop();
}
});
}
} catch (IOException ioe) {
// we don't care about exceptions on disconnect in dispose
}
}
Locale getLocale() {
if (localeProvider != null) {
return localeProvider.getLocale();
} else {
return Locale.getDefault();
}
}
/* adapter to react on connection state changes and handle received packets */
private class ResolConnectorAdapter extends ConnectionAdapter {
@Override
public void connectionStateChanged(@Nullable Connection connection) {
synchronized (ResolBridgeHandler.this) {
if (connection == null) {
isConnected = false;
} else {
ConnectionState connState = connection.getConnectionState();
if (ConnectionState.CONNECTED.equals(connState)) {
isConnected = true;
} else if (ConnectionState.DISCONNECTED.equals(connState)
|| ConnectionState.INTERRUPTED.equals(connState)) {
isConnected = false;
}
logger.debug("Connection state changed to: {}", connState.toString());
if (isConnected) {
unconnectedReason = "";
} else {
unconnectedReason = "TCP connection failed: " + connState.toString();
}
}
updateStatus();
}
}
@Override
public void packetReceived(@Nullable Connection connection, @Nullable Packet packet) {
if (connection == null || packet == null) {
return;
}
Language lang = SpecificationFile.getLanguageForLocale(getLocale());
boolean packetHandled = false;
String thingType = spec.getSourceDeviceSpec(packet).getName(); // use En here
thingType = thingType.replace(" [", "-");
thingType = thingType.replace("]", "");
thingType = thingType.replace(" #", "-");
thingType = thingType.replace(" ", "_");
thingType = thingType.replace("/", "_");
thingType = thingType.replaceAll("[^A-Za-z0-9_-]+", "_");
/*
* It would be nice for the combination of MX and EM devices to filter only those with a peerAddress of
* 0x10, because the MX redelivers the data from the EM to the DFA.
* But the MX is the exception in this case and many other controllers do not redeliver data, so we keep it.
*/
if (logger.isTraceEnabled()) {
logger.trace("Received Data from {} (0x{}/0x{}) naming it {}",
spec.getSourceDeviceSpec(packet).getName(lang),
Integer.toHexString(spec.getSourceDeviceSpec(packet).getSelfAddress()),
Integer.toHexString(spec.getSourceDeviceSpec(packet).getPeerAddress()), thingType);
}
for (Thing t : getThing().getThings()) {
ResolBaseThingHandler th = (ResolBaseThingHandler) t.getHandler();
boolean isEM = t instanceof ResolEmuEMThingHandler;
if (t.getUID().getId().contentEquals(thingType)
|| (isEM && th != null && spec.getSourceDeviceSpec(packet)
.getPeerAddress() == ((ResolEmuEMThingHandler) th).getVbusAddress())) {
if (th != null) {
th.packetReceived(spec, lang, packet);
packetHandled = true;
}
}
}
ResolDeviceDiscoveryService discovery = discoveryService;
if (!packetHandled && scanning && discovery != null) {
// register the seen device
discovery.addThing(getThing().getUID(), ResolBindingConstants.THING_ID_DEVICE, thingType,
spec.getSourceDeviceSpec(packet).getName(lang));
}
}
}
public void startScan() {
scanning = true;
}
public void stopScan() {
scanning = false;
}
}

View File

@@ -0,0 +1,298 @@
/**
* Copyright (c) 2010-2021 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.resol.handler;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Objects;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.resol.internal.ResolEmuEMConfiguration;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.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.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.resol.vbus.Connection;
import de.resol.vbus.Connection.ConnectionState;
import de.resol.vbus.Packet;
import de.resol.vbus.Specification;
import de.resol.vbus.SpecificationFile.Language;
import de.resol.vbus.deviceemulators.EmDeviceEmulator;
/**
* The {@link ResolEmuEMThingHandler} is responsible for emulating a EM device
*
* @author Raphael Mack - Initial contribution
*/
@NonNullByDefault
public class ResolEmuEMThingHandler extends ResolBaseThingHandler implements PropertyChangeListener {
public static final String CHANNEL_RELAY = "relay_";
public static final String CHANNEL_TEMP = "temperature_";
public static final String CHANNEL_RESIST = "resistor_";
public static final String CHANNEL_SWITCH = "switch_";
public static final String CHANNEL_TEMP_ADJUST = "bas_temp_adjust_";
public static final String CHANNEL_MODE = "bas_mode_";
private final Logger logger = LoggerFactory.getLogger(ResolEmuEMThingHandler.class);
private int vbusAddress = 0x6650;
private int deviceId = 1;
private @Nullable EmDeviceEmulator device;
private @Nullable ResolBridgeHandler bridgeHandler;
private static class BasSetting {
float temperatureOffset = 0.0f;
int mode = 4;
}
private BasSetting[] basValues = { new BasSetting(), new BasSetting(), new BasSetting(), new BasSetting(),
new BasSetting(), new BasSetting() };
private long lastTime = System.currentTimeMillis();
// Background Runnable
private @Nullable ScheduledFuture<?> updateJob;
public ResolEmuEMThingHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
ResolEmuEMConfiguration configuration = getConfigAs(ResolEmuEMConfiguration.class);
deviceId = configuration.deviceId;
vbusAddress = 0x6650 + deviceId;
bridgeHandler = getBridgeHandler();
registerResolThingListener(bridgeHandler);
}
@Override
public void dispose() {
EmDeviceEmulator dev = device;
ScheduledFuture<?> job = updateJob;
if (job != null) {
job.cancel(true);
}
if (dev != null) {
dev.stop();
dev.removePropertyChangeListener(this);
}
}
private void updateRunnable() {
EmDeviceEmulator d = device;
if (d != null) {
long now = System.currentTimeMillis();
int diff = (int) (now - lastTime);
lastTime = now;
d.update(diff);
}
}
private void startAutomaticUpdate() {
ScheduledFuture<?> job = updateJob;
if (job == null || job.isCancelled()) {
updateJob = scheduler.scheduleWithFixedDelay(this::updateRunnable, 0, 1, TimeUnit.SECONDS);
}
}
private synchronized @Nullable ResolBridgeHandler getBridgeHandler() {
Bridge bridge = getBridge();
if (bridge == null) {
logger.debug("Required bridge not defined for thing {}.", thing.getThingTypeUID());
return null;
} else {
return getBridgeHandler(bridge);
}
}
private synchronized @Nullable ResolBridgeHandler getBridgeHandler(Bridge bridge) {
ResolBridgeHandler bridgeHandler = null;
ThingHandler handler = bridge.getHandler();
if (handler instanceof ResolBridgeHandler) {
bridgeHandler = (ResolBridgeHandler) handler;
} else {
logger.debug("No available bridge handler found yet. Bridge: {} .", bridge.getUID());
}
return bridgeHandler;
}
private void registerResolThingListener(@Nullable ResolBridgeHandler bridgeHandler) {
if (bridgeHandler != null) {
bridgeHandler.registerResolThingListener(this);
} else {
logger.debug("Can't register {} at bridge as bridgeHandler is null.", this.getThing().getUID());
}
}
public int getVbusAddress() {
return vbusAddress;
}
public void useConnection(Connection connection) {
EmDeviceEmulator device = this.device;
if (device != null) {
device.stop();
device.removePropertyChangeListener(this);
}
device = new EmDeviceEmulator(connection, deviceId);
this.device = device;
device.addPropertyChangeListener(this);
device.start();
for (int i = 1; i <= 5; i++) {
setRelayChannelValue(i, device.getRelayValueByNr(i));
}
startAutomaticUpdate();
}
public void stop() {
EmDeviceEmulator device = this.device;
if (device != null) {
device.stop();
}
ScheduledFuture<?> updateJob = this.updateJob;
if (updateJob != null) {
updateJob.cancel(false);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
String chID = channelUID.getId();
int channel = chID.charAt(chID.length() - 1) - '0';
float value = 0;
int intValue = 0;
if (command instanceof QuantityType<?>) {
value = Objects.requireNonNullElse(((QuantityType<?>) command).toUnit(SIUnits.CELSIUS),
new QuantityType<>(888.8, SIUnits.CELSIUS)).floatValue();
} else if (command instanceof OnOffType) {
intValue = ((OnOffType) command).equals(OnOffType.ON) ? 1 : 0;
} else if (command instanceof DecimalType) {
intValue = ((DecimalType) command).intValue();
value = intValue;
} else {
/* nothing to do */
return;
}
EmDeviceEmulator dev = device;
if (dev != null) {
if (chID.startsWith(CHANNEL_TEMP)) {
dev.setResistorValueByNrAndPt1000Temperatur(channel, value);
updateState(channelUID, new QuantityType<>(value, SIUnits.CELSIUS));
} else if (chID.startsWith(CHANNEL_SWITCH)) {
if (intValue == 0) {
/* switch is open => 1 megaohm */
dev.setResistorValueByNr(channel, 1000000000);
updateState(channelUID, OnOffType.OFF);
} else {
/* switch is closed */
dev.setResistorValueByNr(channel, 0);
updateState(channelUID, OnOffType.ON);
}
} else if (chID.startsWith(CHANNEL_RESIST)) {
dev.setResistorValueByNr(channel, (int) (value * 1000.0));
updateState(channelUID, new QuantityType<>(intValue, Units.OHM));
} else if (chID.startsWith(CHANNEL_TEMP_ADJUST)) {
basValues[channel - 1].temperatureOffset = value;
updateBas(channel);
updateState(channelUID, new QuantityType<>(value, SIUnits.CELSIUS));
} else if (chID.startsWith(CHANNEL_MODE)) {
basValues[channel - 1].mode = intValue;
updateBas(channel);
updateState(channelUID, new QuantityType<>(intValue, Units.ONE));
} else {
/* set resistor value for Open Connection, 1 megaohm */
dev.setResistorValueByNr(channel, 1000000000);
updateState(channelUID, new QuantityType<>(1000000, Units.OHM));
}
}
}
private void updateBas(int channel) {
int resistor = 0; /* in milliohm */
int delta = (int) ((basValues[channel - 1].temperatureOffset * 210.0f / 15.0f) * 1000.0f);
switch (basValues[channel - 1].mode) {
case 4: /* Automatic range 76 - 496 ohm */
resistor = 286 * 1000 + delta;
break;
case 0: /* OFF range 1840 - 2260 ohm */
resistor = 2050 * 1000 + delta;
break;
case 2: /* Night range 660 - 1080 ohm */
resistor = 870 * 1000 + delta;
break;
case 3: /* Party is automatic mode with +15K */
resistor = 286 * 1000 + 210 * 1000;
break;
case 1: /* Summer range 1240 - 1660 ohm */
resistor = 1450 * 1000 + delta;
break;
default:
/* signal a shortcut as error */
resistor = 0;
break;
}
EmDeviceEmulator device = this.device;
if (device != null) {
device.setResistorValueByNr(channel, resistor);
}
}
@Override
public void propertyChange(@Nullable PropertyChangeEvent evt) {
if (evt != null) {
String s = evt.getPropertyName();
if (s.startsWith("relay") && s.endsWith("Value")) {
int v = (Integer) evt.getNewValue();
int i = Integer.parseInt(s.substring(5, 6));
setRelayChannelValue(i, v);
} else if (s.contentEquals("connectionState")) {
ConnectionState ste = (ConnectionState) evt.getNewValue();
if (ste.equals(ConnectionState.CONNECTED)) {
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ste.toString());
}
}
}
}
private void setRelayChannelValue(int relay, double value) {
String channelId = CHANNEL_RELAY + relay;
updateState(channelId, new DecimalType(value));
}
@Override
public void packetReceived(Specification spec, Language lang, Packet packet) {
/* nothing to do here */
}
}

View File

@@ -0,0 +1,270 @@
/**
* Copyright (c) 2010-2021 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.resol.handler;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.resol.internal.ResolBindingConstants;
import org.openhab.binding.resol.internal.ResolStateDescriptionOptionProvider;
import org.openhab.binding.resol.internal.providers.ResolChannelTypeProvider;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.StateOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.resol.vbus.Packet;
import de.resol.vbus.Specification;
import de.resol.vbus.Specification.PacketFieldSpec;
import de.resol.vbus.Specification.PacketFieldValue;
import de.resol.vbus.SpecificationFile;
import de.resol.vbus.SpecificationFile.Enum;
import de.resol.vbus.SpecificationFile.EnumVariant;
import de.resol.vbus.SpecificationFile.Language;
/**
* The {@link ResolThingHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Raphael Mack - Initial contribution
*/
@NonNullByDefault
public class ResolThingHandler extends ResolBaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(ResolThingHandler.class);
private ResolStateDescriptionOptionProvider stateDescriptionProvider;
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(
DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS_GENERAL);
static {
synchronized (DATE_FORMAT) {
DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
}
public ResolThingHandler(Thing thing, ResolStateDescriptionOptionProvider stateDescriptionProvider) {
super(thing);
this.stateDescriptionProvider = stateDescriptionProvider;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
/* we ignore the commands for now on purpose */
}
@Override
public void initialize() {
ResolBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler != null) {
updateStatus(bridgeHandler.getStatus());
}
}
private synchronized @Nullable ResolBridgeHandler getBridgeHandler() {
Bridge bridge = getBridge();
if (bridge == null) {
logger.debug("Required bridge not defined for device.");
return null;
} else {
return getBridgeHandler(bridge);
}
}
private synchronized @Nullable ResolBridgeHandler getBridgeHandler(Bridge bridge) {
ResolBridgeHandler bridgeHandler = null;
ThingHandler handler = bridge.getHandler();
if (handler instanceof ResolBridgeHandler) {
bridgeHandler = (ResolBridgeHandler) handler;
} else {
logger.debug("No available bridge handler found yet. Bridge: {} .", bridge.getUID());
}
return bridgeHandler;
}
@Override
protected void packetReceived(Specification spec, Language lang, Packet packet) {
PacketFieldValue[] pfvs = spec.getPacketFieldValuesForHeaders(new Packet[] { packet });
for (PacketFieldValue pfv : pfvs) {
logger.trace("Id: {}, Name: {}, Raw: {}, Text: {}", pfv.getPacketFieldId(), pfv.getName(lang),
pfv.getRawValueDouble(), pfv.formatTextValue(null, Locale.getDefault()));
String channelId = pfv.getName(); // use English name as channel
channelId = channelId.replace(" [", "-");
channelId = channelId.replace("]", "");
channelId = channelId.replace("(", "-");
channelId = channelId.replace(")", "");
channelId = channelId.replace(" #", "-");
channelId = channelId.replaceAll("[^A-Za-z0-9_-]+", "_");
channelId = channelId.toLowerCase(Locale.ENGLISH);
ChannelTypeUID channelTypeUID;
if (pfv.getPacketFieldSpec().getUnit().getUnitId() >= 0) {
channelTypeUID = new ChannelTypeUID(ResolBindingConstants.BINDING_ID,
pfv.getPacketFieldSpec().getUnit().getUnitCodeText());
} else if (pfv.getPacketFieldSpec().getType() == SpecificationFile.Type.DateTime) {
channelTypeUID = new ChannelTypeUID(ResolBindingConstants.BINDING_ID, "DateTime");
} else {
/* used for enums and the numeric types without unit */
channelTypeUID = new ChannelTypeUID(ResolBindingConstants.BINDING_ID, "None");
}
String acceptedItemType;
Thing thing = getThing();
switch (pfv.getPacketFieldSpec().getType()) {
case DateTime:
acceptedItemType = "DateTime";
break;
case WeekTime:
case Number:
acceptedItemType = ResolChannelTypeProvider.itemTypeForUnit(pfv.getPacketFieldSpec().getUnit());
break;
case Time:
default:
acceptedItemType = "String";
break;
}
Channel a = thing.getChannel(channelId);
if (a == null) {
/* channel doesn't exit, let's create it */
ThingBuilder thingBuilder = editThing();
ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
if (pfv.getEnumVariant() != null) {
/* create a state option channel */
List<StateOption> options = new ArrayList<>();
PacketFieldSpec ff = pfv.getPacketFieldSpec();
Enum e = ff.getEnum();
for (long l : e.getValues()) {
EnumVariant v = e.getEnumVariantForValue(l);
options.add(new StateOption(Long.toString(l), v.getText(lang)));
}
stateDescriptionProvider.setStateOptions(channelUID, options);
Channel channel = ChannelBuilder.create(channelUID, "Number").withType(channelTypeUID)
.withLabel(pfv.getName(lang)).build();
thingBuilder.withChannel(channel).withLabel(thing.getLabel());
updateThing(thingBuilder.build());
} else if (pfv.getRawValueDouble() != null) {
/* a number channel */
Channel channel = ChannelBuilder.create(channelUID, acceptedItemType).withType(channelTypeUID)
.withLabel(pfv.getName(lang)).build();
thingBuilder.withChannel(channel).withLabel(thing.getLabel());
updateThing(thingBuilder.build());
}
logger.debug("Creating channel: {}", channelUID);
}
if (pfv.getEnumVariant() != null) {
/* update the enum / State channel */
this.updateState(channelId, new StringType(Long.toString(pfv.getRawValueLong())));
} else {
switch (pfv.getPacketFieldSpec().getType()) {
case Number:
Double dd = pfv.getRawValueDouble();
if (dd != null) {
if (!isSpecialValue(dd)) {
/* only set the value if no error occurred */
String str = pfv.formatText();
if (str.endsWith("RH")) {
/* unit %RH for relative humidity is not known in openHAB UoM, so we remove it */
str = str.substring(0, str.length() - 2);
}
if (str.endsWith("")) {
QuantityType<?> q = new QuantityType<>(dd, Units.OHM);
this.updateState(channelId, q);
} else {
try {
QuantityType<?> q = new QuantityType<>(str);
this.updateState(channelId, q);
} catch (IllegalArgumentException e) {
logger.debug("unit of '{}' unknown in openHAB", str);
QuantityType<?> q = new QuantityType<>(dd.toString());
this.updateState(channelId, q);
}
}
}
}
/*
* else {
* field not available in this packet, e. g. old firmware version not (yet) transmitting it
* }
*/
break;
case DateTime:
synchronized (DATE_FORMAT) {
DateTimeType d = new DateTimeType(DATE_FORMAT.format(pfv.getRawValueDate()));
this.updateState(channelId, d);
}
break;
case WeekTime:
case Time:
default:
Bridge b = getBridge();
if (b != null) {
String value = pfv.formatTextValue(pfv.getPacketFieldSpec().getUnit(),
((ResolBridgeHandler) b).getLocale());
try {
QuantityType<?> q = new QuantityType<>(value);
this.updateState(channelId, q);
} catch (IllegalArgumentException e) {
this.updateState(channelId, new StringType(value));
logger.debug("unit of '{}' unknown in openHAB, using string", value);
}
}
}
}
}
}
/* check if the given value is a special one like 888.8 or 999.9 for shortcut or open load on a sensor wire */
private boolean isSpecialValue(Double dd) {
if ((Math.abs(dd - 888.8) < 0.1) || (Math.abs(dd - (-888.8)) < 0.1)) {
/* value out of range */
return true;
}
if (Math.abs(dd - 999.9) < 0.1) {
/* sensor not reachable */
return true;
}
return false;
}
}

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2010-2021 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.resol.internal;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link ResolBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Raphael Mack - Initial contribution
*/
@NonNullByDefault
public class ResolBindingConstants {
private static final String BRIDGE_VBUSLAN = "vbuslan";
public static final String BINDING_ID = "resol";
// List of all ChannelTypeUIDs is empty, as we got totally rid of static channel types.
// ChannelTypeUIDs are constructed from the BINDING_ID and the UnitCodeTextIndex from the VSF
// List of all Thing Type
public static final String THING_ID_DEVICE = "device";
public static final String THING_ID_EMU_EM = "emulatedEM";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_UID_BRIDGE = new ThingTypeUID(BINDING_ID, BRIDGE_VBUSLAN);
public static final ThingTypeUID THING_TYPE_UID_DEVICE = new ThingTypeUID(BINDING_ID, THING_ID_DEVICE);
public static final ThingTypeUID THING_TYPE_UID_EMU_EM = new ThingTypeUID(BINDING_ID, THING_ID_EMU_EM);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_UID_BRIDGE,
THING_TYPE_UID_DEVICE, THING_TYPE_UID_EMU_EM);
public static final Set<ThingTypeUID> SUPPORTED_BRIDGE_THING_TYPES_UIDS = Set.of(THING_TYPE_UID_BRIDGE);
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2021 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.resol.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link ResolBridgeConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Raphael Mack - Initial contribution
*/
@NonNullByDefault
public class ResolBridgeConfiguration {
public String ipAddress = "";
public String password = "vbus";
public Integer port = 7053;
public String adapterSerial = "";
public Integer refreshInterval = 300;
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2021 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.resol.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link ResolEmuEMConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Raphael Mack - Initial contribution
*/
@NonNullByDefault
public class ResolEmuEMConfiguration {
public Integer deviceId = 1;
}

View File

@@ -0,0 +1,77 @@
/**
* Copyright (c) 2010-2021 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.resol.internal;
import static org.openhab.binding.resol.internal.ResolBindingConstants.SUPPORTED_THING_TYPES_UIDS;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.resol.handler.ResolBridgeHandler;
import org.openhab.binding.resol.handler.ResolEmuEMThingHandler;
import org.openhab.binding.resol.handler.ResolThingHandler;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link ResolHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Raphael Mack - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.resol", service = ThingHandlerFactory.class)
public class ResolHandlerFactory extends BaseThingHandlerFactory {
private final LocaleProvider localeProvider;
private final ResolStateDescriptionOptionProvider stateDescriptionProvider;
@Activate
public ResolHandlerFactory(final @Reference ResolStateDescriptionOptionProvider stateDescriptionProvider,
final @Reference LocaleProvider localeProvider) {
this.stateDescriptionProvider = stateDescriptionProvider;
this.localeProvider = localeProvider;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(ResolBindingConstants.THING_TYPE_UID_DEVICE)) {
return new ResolThingHandler(thing, stateDescriptionProvider);
}
if (thingTypeUID.equals(ResolBindingConstants.THING_TYPE_UID_EMU_EM)) {
return new ResolEmuEMThingHandler(thing);
}
if (thingTypeUID.equals(ResolBindingConstants.THING_TYPE_UID_BRIDGE)) {
return new ResolBridgeHandler((Bridge) thing, localeProvider);
}
return null;
}
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2021 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.resol.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Dynamic provider of state options for the Resol binding.
*
* @author Raphael Mack - Initial contribution
*/
@Component(service = { DynamicStateDescriptionProvider.class, ResolStateDescriptionOptionProvider.class })
@NonNullByDefault
public class ResolStateDescriptionOptionProvider extends BaseDynamicStateDescriptionProvider {
@Reference
protected void setChannelTypeI18nLocalizationService(
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
}
protected void unsetChannelTypeI18nLocalizationService(
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.channelTypeI18nLocalizationService = null;
}
}

View File

@@ -0,0 +1,122 @@
/**
* Copyright (c) 2010-2021 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.resol.internal.discovery;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.resol.handler.ResolBridgeHandler;
import org.openhab.binding.resol.internal.ResolBindingConstants;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ResolDeviceDiscoveryService} class handles the discovery of things.
*
*
* @author Raphael Mack - Initial contribution
*/
@NonNullByDefault
public class ResolDeviceDiscoveryService extends AbstractDiscoveryService
implements DiscoveryService, ThingHandlerService {
private static final String THING_PROPERTY_TYPE = "type";
private final Logger logger = LoggerFactory.getLogger(ResolDeviceDiscoveryService.class);
private @Nullable ResolBridgeHandler resolBridgeHandler;
public ResolDeviceDiscoveryService() throws IllegalArgumentException {
super(Set.of(ResolBindingConstants.THING_TYPE_UID_DEVICE), 15, false);
}
public void addThing(ThingUID bridgeUID, String thingType, String type, String name) {
logger.trace("Adding new Resol thing: {}", type);
ThingUID thingUID = null;
switch (thingType) {
case ResolBindingConstants.THING_ID_DEVICE:
thingUID = new ThingUID(ResolBindingConstants.THING_TYPE_UID_DEVICE, bridgeUID, type);
break;
}
if (thingUID != null) {
logger.trace("Adding new Discovery thingType: {} bridgeType: {}", thingUID.getAsString(),
bridgeUID.getAsString());
Map<String, Object> properties = new HashMap<>(1);
properties.put(THING_PROPERTY_TYPE, type);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
.withRepresentationProperty(THING_PROPERTY_TYPE).withProperties(properties).withLabel(name).build();
logger.trace("call register: {} label: {}", discoveryResult.getBindingId(), discoveryResult.getLabel());
thingDiscovered(discoveryResult);
} else {
logger.debug("Discovered Thing is unsupported: type '{}'", type);
}
}
@Override
public void activate() {
ResolBridgeHandler resolBridgeHandler = this.resolBridgeHandler;
if (resolBridgeHandler != null) {
resolBridgeHandler.registerDiscoveryService(this);
}
}
@Override
public void deactivate() {
ResolBridgeHandler resolBridgeHandler = this.resolBridgeHandler;
if (resolBridgeHandler != null) {
resolBridgeHandler.unregisterDiscoveryService();
}
}
@Override
protected void startScan() {
ResolBridgeHandler resolBridgeHandler = this.resolBridgeHandler;
if (resolBridgeHandler != null) {
resolBridgeHandler.startScan();
}
}
@Override
protected void stopScan() {
ResolBridgeHandler resolBridgeHandler = this.resolBridgeHandler;
if (resolBridgeHandler != null) {
resolBridgeHandler.stopScan();
}
super.stopScan();
}
@Override
public void setThingHandler(ThingHandler handler) {
if (handler instanceof ResolBridgeHandler) {
resolBridgeHandler = (ResolBridgeHandler) handler;
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return resolBridgeHandler;
}
}

View File

@@ -0,0 +1,123 @@
/**
* Copyright (c) 2010-2021 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.resol.internal.discovery;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Future;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.resol.internal.ResolBindingConstants;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.resol.vbus.TcpDataSource;
import de.resol.vbus.TcpDataSourceProvider;
/**
* The {@link ResolVBusBridgeDiscovery} class provides the DiscoverySerivce to
* discover Resol VBus-LAN adapters
*
* @author Raphael Mack - Initial contribution
*/
@Component(service = DiscoveryService.class)
@NonNullByDefault
public class ResolVBusBridgeDiscovery extends AbstractDiscoveryService {
public static final String THING_PROPERTY_IPADDRESS = "ipAddress";
public static final String THING_PROPERTY_PORT = "port";
public static final String THING_PROPERTY_ADAPTER_SERIAL = "adapterSerial";
private final Logger logger = LoggerFactory.getLogger(ResolVBusBridgeDiscovery.class);
private volatile boolean discoveryRunning = false;
private @Nullable Future<?> searchFuture;
public ResolVBusBridgeDiscovery() throws IllegalArgumentException {
super(ResolBindingConstants.SUPPORTED_BRIDGE_THING_TYPES_UIDS, 35, false);
}
@Override
protected void startScan() {
discoveryRunning = true;
searchFuture = scheduler.submit(this::searchRunnable);
}
@Override
protected void stopScan() {
discoveryRunning = false;
if (searchFuture != null) {
searchFuture.cancel(true);
}
}
/*
* The runnable for the search routine.
*/
public void searchRunnable() {
try {
InetAddress broadcastAddress = InetAddress
.getByAddress(new byte[] { (byte) 255, (byte) 255, (byte) 255, (byte) 255 });
TcpDataSource[] dataSources = TcpDataSourceProvider.discoverDataSources(broadcastAddress, 3, 500, false);
Map<String, TcpDataSource> currentDataSourceById = new HashMap<String, TcpDataSource>();
for (TcpDataSource ds : dataSources) {
if (!discoveryRunning) {
break;
}
InetAddress address = ds.getAddress();
String addressId = address.getHostAddress();
TcpDataSource dsWithInfo;
try {
dsWithInfo = TcpDataSourceProvider.fetchInformation(address, 1500);
logger.trace("Discovered Resol VBus-LAN interface @{} {} ({})", addressId,
dsWithInfo.getDeviceName(), dsWithInfo.getSerial());
currentDataSourceById.put(addressId, dsWithInfo);
addAdapter(addressId, dsWithInfo);
// here we can add the detection of Multi-Channel interfaces like DL3
} catch (InterruptedIOException ex) {
/* openHAB interrupted the io thread and wants to shutdown */
break;
} catch (IOException ex) {
/* address is no valid adapter */
}
}
} catch (UnknownHostException e) {
logger.debug("Could not resolve IPv4 broadcast address");
}
}
private void addAdapter(String remoteIP, TcpDataSource dsWithInfo) {
String adapterSerial = dsWithInfo.getSerial();
Map<String, Object> properties = new HashMap<>(3);
properties.put(THING_PROPERTY_IPADDRESS, remoteIP);
properties.put(THING_PROPERTY_PORT, dsWithInfo.getLivePort());
properties.put(THING_PROPERTY_ADAPTER_SERIAL, adapterSerial);
ThingUID uid = new ThingUID(ResolBindingConstants.THING_TYPE_UID_BRIDGE, adapterSerial);
thingDiscovered(DiscoveryResultBuilder.create(uid).withRepresentationProperty(THING_PROPERTY_IPADDRESS)
.withProperties(properties).withLabel(dsWithInfo.getName()).build());
}
}

View File

@@ -0,0 +1,142 @@
/**
* Copyright (c) 2010-2021 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.resol.internal.providers;
import java.util.Collection;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.resol.internal.ResolBindingConstants;
import org.openhab.core.thing.type.ChannelType;
import org.openhab.core.thing.type.ChannelTypeBuilder;
import org.openhab.core.thing.type.ChannelTypeProvider;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.StateDescriptionFragmentBuilder;
import org.osgi.service.component.annotations.Component;
import de.resol.vbus.Specification;
import de.resol.vbus.SpecificationFile.Unit;
/**
* @author Raphael Mack - Initial Contribution
*
*/
@Component(service = { ChannelTypeProvider.class, ResolChannelTypeProvider.class })
@NonNullByDefault
public class ResolChannelTypeProvider implements ChannelTypeProvider {
private Map<ChannelTypeUID, ChannelType> channelTypes = new ConcurrentHashMap<ChannelTypeUID, ChannelType>();
public ResolChannelTypeProvider() {
// let's add all channel types from known by the resol-vbus java library
Specification spec = Specification.getDefaultSpecification();
Unit[] units = spec.getUnits();
for (Unit u : units) {
ChannelTypeUID channelTypeUID = new ChannelTypeUID(ResolBindingConstants.BINDING_ID, u.getUnitCodeText());
// maybe we could use pfv.getPacketFieldSpec().getPrecision() here
int precision = 1;
if (u.getUnitId() >= 0) {
ChannelType ctype = ChannelTypeBuilder
.state(channelTypeUID, u.getUnitFamily().toString(), itemTypeForUnit(u))
.withStateDescriptionFragment(StateDescriptionFragmentBuilder.create()
.withPattern("%." + precision + "f " + u.getUnitTextText().replace("%", "%%"))
.withReadOnly(true).build())
.build();
channelTypes.put(channelTypeUID, ctype);
}
}
}
@Override
public Collection<ChannelType> getChannelTypes(@Nullable Locale locale) {
return channelTypes.values();
}
@Override
public @Nullable ChannelType getChannelType(ChannelTypeUID channelTypeUID, @Nullable Locale locale) {
if (channelTypes.containsKey(channelTypeUID)) {
return channelTypes.get(channelTypeUID);
} else {
return null;
}
}
public static String itemTypeForUnit(Unit u) {
String itemType = "Number";
switch (u.getUnitFamily()) {
case Temperature:
itemType += ":Temperature";
break;
case Energy:
itemType += ":Energy";
break;
case VolumeFlow:
itemType += ":VolumetricFlowRate";
break;
case Pressure:
itemType += ":Pressure";
break;
case Volume:
itemType += ":Volume";
break;
case Time:
itemType += ":Time";
break;
case Power:
itemType += ":Power";
break;
case None:
switch (u.getUnitCodeText()) {
case "Hertz":
itemType += ":Frequency";
break;
case "Hectopascals":
itemType += ":Pressure";
break;
case "MetersPerSecond":
itemType += ":Speed";
break;
case "Milliamperes":
itemType += ":ElectricCurrent";
break;
case "Milliseconds":
itemType += ":Time";
break;
case "Ohms":
itemType += ":ElectricResistance";
break;
case "Percent":
itemType += ":Dimensionless";
break;
case "PercentRelativeHumidity":
itemType += ":Dimensionless";
break;
case "Volts":
itemType += ":ElectricPotential";
break;
case "WattsPerSquareMeter":
itemType += ":Intensity";
break;
}
break;
default:
}
return itemType;
}
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="resol" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Resol Binding</name>
<description>This is the binding for Resol solar and system controllers (including branded versions).</description>
</binding:binding>

View File

@@ -0,0 +1,80 @@
# binding
binding.resol.name = Resol Binding
binding.resol.description = Verbindet Solar- und Systemregler des Herstellers Resol und weitere, die für andere Marken von Resol produziert werden.
# thing types
thing-type.resol.device.label = Resol Gerät
thing-type.resol.device.description = Solar- oder Systemregler oder ein anderes Gerät welches mit dem Resol VBus verbunden ist.
thing-type.resol.emulatedEM.label = Emuliertes EM Modul
thing-type.resol.emulatedEM.description = Emulation eines Erweiterungs-Modules (EM) welches über den VBus an dafür ausgelegten Resol Reglern angebunden wird. Es ersetzt ein physikalisch vorhandenes EM durch openHAB.
thing-type.resol.emulatedEM.channel.relay_1.label = Relais Status 1
thing-type.resol.emulatedEM.channel.relay_2.label = Relais Status 2
thing-type.resol.emulatedEM.channel.relay_3.label = Relais Status 3
thing-type.resol.emulatedEM.channel.relay_4.label = Relais Status 4
thing-type.resol.emulatedEM.channel.relay_5.label = Relais Status 5
thing-type.resol.emulatedEM.channel.switch_1.label = Schalter 1
thing-type.resol.emulatedEM.channel.switch_2.label = Schalter 2
thing-type.resol.emulatedEM.channel.switch_3.label = Schalter 3
thing-type.resol.emulatedEM.channel.switch_4.label = Schalter 4
thing-type.resol.emulatedEM.channel.switch_5.label = Schalter 5
thing-type.resol.emulatedEM.channel.switch_6.label = Schalter 6
thing-type.resol.emulatedEM.channel.temperature_1.label = Temperatur 1
thing-type.resol.emulatedEM.channel.temperature_2.label = Temperatur 2
thing-type.resol.emulatedEM.channel.temperature_3.label = Temperatur 3
thing-type.resol.emulatedEM.channel.temperature_4.label = Temperatur 4
thing-type.resol.emulatedEM.channel.temperature_5.label = Temperatur 5
thing-type.resol.emulatedEM.channel.temperature_6.label = Temperatur 6
thing-type.resol.emulatedEM.channel.resistor_1.label = Widerstand 1
thing-type.resol.emulatedEM.channel.resistor_2.label = Widerstand 2
thing-type.resol.emulatedEM.channel.resistor_3.label = Widerstand 3
thing-type.resol.emulatedEM.channel.resistor_4.label = Widerstand 4
thing-type.resol.emulatedEM.channel.resistor_5.label = Widerstand 5
thing-type.resol.emulatedEM.channel.resistor_6.label = Widerstand 6
thing-type.resol.emulatedEM.channel.bas_temp_adjust_1.label = Temperatur Anpassung 1
thing-type.resol.emulatedEM.channel.bas_temp_adjust_2.label = Temperatur Anpassung 2
thing-type.resol.emulatedEM.channel.bas_temp_adjust_3.label = Temperatur Anpassung 3
thing-type.resol.emulatedEM.channel.bas_temp_adjust_4.label = Temperatur Anpassung 4
thing-type.resol.emulatedEM.channel.bas_temp_adjust_5.label = Temperatur Anpassung 5
thing-type.resol.emulatedEM.channel.bas_temp_adjust_6.label = Temperatur Anpassung 6
thing-type.resol.emulatedEM.channel.bas_mode_1.label = Betriebsart 1
thing-type.resol.emulatedEM.channel.bas_mode_2.label = Betriebsart 2
thing-type.resol.emulatedEM.channel.bas_mode_3.label = Betriebsart 3
thing-type.resol.emulatedEM.channel.bas_mode_4.label = Betriebsart 4
thing-type.resol.emulatedEM.channel.bas_mode_5.label = Betriebsart 5
thing-type.resol.emulatedEM.channel.bas_mode_6.label = Betriebsart 6
thing-type.resol.vbuslan.label = VBusLAN Adapter
thing-type.resol.vbuslan.description = Diese bridge verwendet ein Gerät mit TCP/IP live port als Schnittstelle zum Resol VBus. Dies kann als eigenständiger VBus-LAN Adapter aber auch ein andere Gerät mit integriertem VBus live port wie einem KM2, DL2/3 oder sonstiges sein.
.
# thing type config description
# thing-type.config.resol.device does not have configuration parameters
thing-type.config.resol.emulatedEM.deviceId.label = Modul ID
thing-type.config.resol.emulatedEM.deviceId.description = Subaddress des emulierten EM Moduls. Der verwendbare Bereich hängt vom verwendeten Regler ab.
thing-type.config.resol.vbuslan.ipAddress.label = IP-Adresse
thing-type.config.resol.vbuslan.ipAddress.description = IP-Adresse der VBus-LAN Schnittstelle.
thing-type.config.resol.vbuslan.port.label = Live Data Port
thing-type.config.resol.vbuslan.port.description = TCP-Port der Live-Data-Schnittstelle des VBus Gateways.
thing-type.config.resol.vbuslan.adapterSerial.label = Adapter Seriennummer
thing-type.config.resol.vbuslan.adapterSerial.description = Seriennummer des VBus-LAN-Schnittstellengerätes (nur zur Information).
thing-type.config.resol.vbuslan.password.label = Passwort
thing-type.config.resol.vbuslan.password.description = Passwort für die VBus-LAN Schnittstelle.
thing-type.config.resol.vbuslan.refreshInterval.label = Aktualisierungsintervall
thing-type.config.resol.vbuslan.refreshInterval.description = Zeitintervall in Sekunden um die Verbindung zur VBus-LAN-Schnittstelle zu prüfen. Die Aktualisierung der Daten geschieht unabhängig hiervon sobald sie auf dem VBus empfangen werden.
# channel types
channel-type.resol.relay.label = Relais Zustand
channel-type.resol.relay.description = Virtueller Relay Ausgang welcher vom Regler gesetzt wird und verwendet werden kann um den Zustand vom Resol Regler zu openHAB zu übertragen.
channel-type.resol.temperature.label = Temperatur
channel-type.resol.temperature.description = Virtueller Temperatur Sensor Eingang.
channel-type.resol.resistance.label = Widerstand
channel-type.resol.resistance.description = Virtueller Widerstandseingang.
channel-type.resol.switch.label = Schalter
channel-type.resol.switch.description = Virtueller Schaltereingang.
channel-type.resol.temperatureAdjust.label = Temperatur Anpassung
channel-type.resol.temperatureAdjust.description = Virtueller Eingang zur Parallelverschiebung der Temperatur eines Heizkreises an einem emulierten BAS (Raumbediengerät RCP12).
channel-type.resol.operationmode.label = Betriebsart
channel-type.resol.operationmode.description = Virtueller Eingang zur Betriebsartumschaltung eines Heizkreises an einem emulierten BAS (Raumbediengerät RCP12).
channel-type.resol.operationmode.state.option.0 = Aus
channel-type.resol.operationmode.state.option.1 = Sommer
channel-type.resol.operationmode.state.option.2 = Nacht
channel-type.resol.operationmode.state.option.3 = Party
channel-type.resol.operationmode.state.option.4 = Automatik

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="resol"
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">
<bridge-type id="vbuslan">
<label>Bridge VBusLAN Adapter</label>
<description>This bridge represents the Resol VBus-LAN adapter which can be any device with a TCP/IP live port, either
the standalone device VBus-LAN Adapter, KM2, DL2/3 or similar.
</description>
<representation-property>ipAddress</representation-property>
<config-description>
<parameter name="ipAddress" type="text" required="true">
<context>network-address</context>
<label>IP Address</label>
<description>The IP address of the of the VBus-LAN gateway.</description>
</parameter>
<parameter name="port" type="integer" required="false" min="1024" max="65535">
<label>Live Data Port</label>
<description>Port for live data on the VBUS-LAN gateway.</description>
<default>7053</default>
</parameter>
<parameter name="adapterSerial" type="text" required="false">
<label>Adapter Serial Number</label>
<description>The serial number of the adapter (informative).</description>
</parameter>
<parameter name="password" type="text" required="true">
<label>Password</label>
<description>The password for the VBusLAN connection.</description>
<context>password</context>
</parameter>
<parameter name="refreshInterval" type="integer" required="false" min="5" max="1800" unit="s">
<label>Refresh Interval</label>
<description>Refresh time in seconds to check the connection to the VBus gateway. Data updates are propagated to
openHAB independently from this setting as soon as they are received on the VBus.</description>
<default>300</default>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,212 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="resol"
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="device">
<supported-bridge-type-refs>
<bridge-type-ref id="vbuslan"/>
</supported-bridge-type-refs>
<label>Resol Device</label>
<description>Solar or system controller (or any other real device on the VBus) from Resol.</description>
</thing-type>
<thing-type id="emulatedEM">
<supported-bridge-type-refs>
<bridge-type-ref id="vbuslan"/>
</supported-bridge-type-refs>
<label>Emulated EM Device</label>
<description>Emulation of an Extension Module (EM) device which can be connected through the VBUS to Resol controllers
which support the EM devices. Replaces a physically available EM by openHAB.</description>
<channels>
<channel id="relay_1" typeId="relay">
<label>Relay 1</label>
</channel>
<channel id="relay_2" typeId="relay">
<label>Relay 2</label>
</channel>
<channel id="relay_3" typeId="relay">
<label>Relay 3</label>
</channel>
<channel id="relay_4" typeId="relay">
<label>Relay 4</label>
</channel>
<channel id="relay_5" typeId="relay">
<label>Relay 5</label>
</channel>
<channel id="switch_1" typeId="switch">
<label>Switch 1</label>
</channel>
<channel id="switch_2" typeId="switch">
<label>Switch 2</label>
</channel>
<channel id="switch_3" typeId="switch">
<label>Switch 3</label>
</channel>
<channel id="switch_4" typeId="switch">
<label>Switch 4</label>
</channel>
<channel id="switch_5" typeId="switch">
<label>Switch 5</label>
</channel>
<channel id="switch_6" typeId="switch">
<label>Switch 6</label>
</channel>
<channel id="temperature_1" typeId="temperature">
<label>Temperature 1</label>
</channel>
<channel id="temperature_2" typeId="temperature">
<label>Temperature 2</label>
</channel>
<channel id="temperature_3" typeId="temperature">
<label>Temperature 3</label>
</channel>
<channel id="temperature_4" typeId="temperature">
<label>Temperature 4</label>
</channel>
<channel id="temperature_5" typeId="temperature">
<label>Temperature 5</label>
</channel>
<channel id="temperature_6" typeId="temperature">
<label>Temperature 6</label>
</channel>
<channel id="resistor_1" typeId="resistance">
<label>Resistor 1</label>
</channel>
<channel id="resistor_2" typeId="resistance">
<label>Resistor 2</label>
</channel>
<channel id="resistor_3" typeId="resistance">
<label>Resistor 3</label>
</channel>
<channel id="resistor_4" typeId="resistance">
<label>Resistor 4</label>
</channel>
<channel id="resistor_5" typeId="resistance">
<label>Resistor 5</label>
</channel>
<channel id="resistor_6" typeId="resistance">
<label>Resistor 6</label>
</channel>
<channel id="bas_temp_adjust_1" typeId="temperatureAdjust">
<label>Temperature Adjustment 1</label>
</channel>
<channel id="bas_temp_adjust_2" typeId="temperatureAdjust">
<label>Temperature Adjustment 2</label>
</channel>
<channel id="bas_temp_adjust_3" typeId="temperatureAdjust">
<label>Temperature Adjustment 3</label>
</channel>
<channel id="bas_temp_adjust_4" typeId="temperatureAdjust">
<label>Temperature Adjustment 4</label>
</channel>
<channel id="bas_temp_adjust_5" typeId="temperatureAdjust">
<label>Temperature Adjustment 5</label>
</channel>
<channel id="bas_temp_adjust_6" typeId="temperatureAdjust">
<label>Temperature Adjustment 6</label>
</channel>
<channel id="bas_mode_1" typeId="operationmode">
<label>Operating Mode 1</label>
</channel>
<channel id="bas_mode_2" typeId="operationmode">
<label>Operating Mode 2</label>
</channel>
<channel id="bas_mode_3" typeId="operationmode">
<label>Operating Mode 3</label>
</channel>
<channel id="bas_mode_4" typeId="operationmode">
<label>Operating Mode 4</label>
</channel>
<channel id="bas_mode_5" typeId="operationmode">
<label>Operating Mode 5</label>
</channel>
<channel id="bas_mode_6" typeId="operationmode">
<label>Operating Mode 6</label>
</channel>
</channels>
<config-description>
<parameter name="deviceId" type="integer" required="true" min="1" max="15">
<label>Module ID</label>
<description>The (sub-)address of the emulated EM device, usable range depends on the used controller.</description>
<default>1</default>
</parameter>
</config-description>
</thing-type>
<channel-type id="None">
<item-type>Number</item-type>
<label>Any</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="NoneHidden" advanced="true">
<item-type>Number</item-type>
<label>Any</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="relay" advanced="false">
<item-type>Number:Dimensionless</item-type>
<label>Relay State</label>
<description>Virtual relay output, will be set by the controller and can be used to communicate data from the Resol
controller to openHAB.</description>
<category>Switch</category>
<state pattern="%d %%" readOnly="true"/>
</channel-type>
<channel-type id="temperature" advanced="false">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>Virtual temperature sensor input.</description>
<category>Temperature</category>
<state pattern="%.1f %unit%" readOnly="false"/>
</channel-type>
<channel-type id="resistance" advanced="false">
<item-type>Number:ElectricResistance</item-type>
<label>Resistance</label>
<description>Virtual resistance input.</description>
<state pattern="%.1f %unit%" readOnly="false"/>
</channel-type>
<channel-type id="switch" advanced="false">
<item-type>Switch</item-type>
<label>Switch</label>
<description>Virtual switch input.</description>
</channel-type>
<channel-type id="temperatureAdjust" advanced="false">
<item-type>Number:Temperature</item-type>
<label>Temperature Adjustment</label>
<description>Virtual temperature offset on heating circuit of an emulated BAS (RCP12 room control unit).</description>
<category>Temperature</category>
<state pattern="%.1f %unit%" readOnly="false" min="-15" max="15"/>
</channel-type>
<channel-type id="operationmode" advanced="false">
<item-type>Number</item-type>
<label>Operating Mode</label>
<description>Virtual operating mode of the heating circuit controlled by the emulated BAS (RCP12 room control unit).</description>
<state pattern="%d" readOnly="false" min="0" max="4" step="1">
<options>
<option value="0">OFF</option>
<option value="1">Summer</option>
<option value="2">Night</option>
<option value="3">Party</option>
<option value="4">Automatic</option>
</options>
</state>
</channel-type>
</thing:thing-descriptions>