2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.resol.handler;
15 import java.beans.PropertyChangeEvent;
16 import java.beans.PropertyChangeListener;
17 import java.util.Objects;
18 import java.util.concurrent.ScheduledFuture;
19 import java.util.concurrent.TimeUnit;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.resol.internal.ResolEmuEMConfiguration;
24 import org.openhab.core.library.types.DecimalType;
25 import org.openhab.core.library.types.OnOffType;
26 import org.openhab.core.library.types.QuantityType;
27 import org.openhab.core.library.unit.SIUnits;
28 import org.openhab.core.library.unit.Units;
29 import org.openhab.core.thing.Bridge;
30 import org.openhab.core.thing.ChannelUID;
31 import org.openhab.core.thing.Thing;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.ThingStatusDetail;
34 import org.openhab.core.thing.binding.ThingHandler;
35 import org.openhab.core.types.Command;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
39 import de.resol.vbus.Connection;
40 import de.resol.vbus.Connection.ConnectionState;
41 import de.resol.vbus.Packet;
42 import de.resol.vbus.Specification;
43 import de.resol.vbus.SpecificationFile.Language;
44 import de.resol.vbus.deviceemulators.EmDeviceEmulator;
47 * The {@link ResolEmuEMThingHandler} is responsible for emulating an EM device
49 * @author Raphael Mack - Initial contribution
52 public class ResolEmuEMThingHandler extends ResolBaseThingHandler implements PropertyChangeListener {
53 public static final String CHANNEL_RELAY = "relay_";
54 public static final String CHANNEL_TEMP = "temperature_";
55 public static final String CHANNEL_RESIST = "resistor_";
56 public static final String CHANNEL_SWITCH = "switch_";
57 public static final String CHANNEL_TEMP_ADJUST = "bas_temp_adjust_";
58 public static final String CHANNEL_MODE = "bas_mode_";
60 private final Logger logger = LoggerFactory.getLogger(ResolEmuEMThingHandler.class);
62 private int vbusAddress = 0x6650;
63 private int deviceId = 1;
64 private @Nullable EmDeviceEmulator device;
66 private @Nullable ResolBridgeHandler bridgeHandler;
68 private static class BasSetting {
69 float temperatureOffset = 0.0f;
73 private BasSetting[] basValues = { new BasSetting(), new BasSetting(), new BasSetting(), new BasSetting(),
74 new BasSetting(), new BasSetting() };
75 private long lastTime = System.currentTimeMillis();
77 // Background Runnable
78 private @Nullable ScheduledFuture<?> updateJob;
80 public ResolEmuEMThingHandler(Thing thing) {
85 public void initialize() {
86 ResolEmuEMConfiguration configuration = getConfigAs(ResolEmuEMConfiguration.class);
87 deviceId = configuration.deviceId;
88 vbusAddress = 0x6650 + deviceId;
90 bridgeHandler = getBridgeHandler();
91 registerResolThingListener(bridgeHandler);
95 public void dispose() {
96 EmDeviceEmulator dev = device;
97 ScheduledFuture<?> job = updateJob;
103 dev.removePropertyChangeListener(this);
107 private void updateRunnable() {
108 EmDeviceEmulator d = device;
110 long now = System.currentTimeMillis();
111 int diff = (int) (now - lastTime);
118 private void startAutomaticUpdate() {
119 ScheduledFuture<?> job = updateJob;
120 if (job == null || job.isCancelled()) {
121 updateJob = scheduler.scheduleWithFixedDelay(this::updateRunnable, 0, 1, TimeUnit.SECONDS);
125 private synchronized @Nullable ResolBridgeHandler getBridgeHandler() {
126 Bridge bridge = getBridge();
127 if (bridge == null) {
128 logger.debug("Required bridge not defined for thing {}.", thing.getThingTypeUID());
131 return getBridgeHandler(bridge);
135 private synchronized @Nullable ResolBridgeHandler getBridgeHandler(Bridge bridge) {
136 ResolBridgeHandler bridgeHandler = null;
138 ThingHandler handler = bridge.getHandler();
139 if (handler instanceof ResolBridgeHandler) {
140 bridgeHandler = (ResolBridgeHandler) handler;
142 logger.debug("No available bridge handler found yet. Bridge: {} .", bridge.getUID());
144 return bridgeHandler;
147 private void registerResolThingListener(@Nullable ResolBridgeHandler bridgeHandler) {
148 if (bridgeHandler != null) {
149 bridgeHandler.registerResolThingListener(this);
151 logger.debug("Can't register {} at bridge as bridgeHandler is null.", this.getThing().getUID());
155 public int getVbusAddress() {
159 public void useConnection(Connection connection) {
160 EmDeviceEmulator device = this.device;
161 if (device != null) {
163 device.removePropertyChangeListener(this);
165 device = new EmDeviceEmulator(connection, deviceId);
166 this.device = device;
167 device.addPropertyChangeListener(this);
169 for (int i = 1; i <= 5; i++) {
170 setRelayChannelValue(i, device.getRelayValueByNr(i));
172 startAutomaticUpdate();
176 EmDeviceEmulator device = this.device;
177 if (device != null) {
180 ScheduledFuture<?> updateJob = this.updateJob;
181 if (updateJob != null) {
182 updateJob.cancel(false);
187 public void handleCommand(ChannelUID channelUID, Command command) {
188 String chID = channelUID.getId();
189 int channel = chID.charAt(chID.length() - 1) - '0';
193 if (command instanceof QuantityType<?>) {
194 value = Objects.requireNonNullElse(((QuantityType<?>) command).toUnit(SIUnits.CELSIUS),
195 new QuantityType<>(888.8, SIUnits.CELSIUS)).floatValue();
196 } else if (command instanceof OnOffType) {
197 intValue = ((OnOffType) command).equals(OnOffType.ON) ? 1 : 0;
198 } else if (command instanceof DecimalType) {
199 intValue = ((DecimalType) command).intValue();
206 EmDeviceEmulator dev = device;
208 if (chID.startsWith(CHANNEL_TEMP)) {
209 dev.setResistorValueByNrAndPt1000Temperatur(channel, value);
210 updateState(channelUID, new QuantityType<>(value, SIUnits.CELSIUS));
211 } else if (chID.startsWith(CHANNEL_SWITCH)) {
213 /* switch is open => 1 megaohm */
214 dev.setResistorValueByNr(channel, 1000000000);
215 updateState(channelUID, OnOffType.OFF);
217 /* switch is closed */
218 dev.setResistorValueByNr(channel, 0);
219 updateState(channelUID, OnOffType.ON);
221 } else if (chID.startsWith(CHANNEL_RESIST)) {
222 dev.setResistorValueByNr(channel, (int) (value * 1000.0));
223 updateState(channelUID, new QuantityType<>(intValue, Units.OHM));
224 } else if (chID.startsWith(CHANNEL_TEMP_ADJUST)) {
225 basValues[channel - 1].temperatureOffset = value;
227 updateState(channelUID, new QuantityType<>(value, SIUnits.CELSIUS));
228 } else if (chID.startsWith(CHANNEL_MODE)) {
229 basValues[channel - 1].mode = intValue;
231 updateState(channelUID, new QuantityType<>(intValue, Units.ONE));
233 /* set resistor value for Open Connection, 1 megaohm */
234 dev.setResistorValueByNr(channel, 1000000000);
235 updateState(channelUID, new QuantityType<>(1000000, Units.OHM));
240 private void updateBas(int channel) {
241 int resistor = 0; /* in milliohm */
242 int delta = (int) ((basValues[channel - 1].temperatureOffset * 210.0f / 15.0f) * 1000.0f);
243 switch (basValues[channel - 1].mode) {
244 case 4: /* Automatic range 76 - 496 ohm */
245 resistor = 286 * 1000 + delta;
247 case 0: /* OFF range 1840 - 2260 ohm */
248 resistor = 2050 * 1000 + delta;
250 case 2: /* Night range 660 - 1080 ohm */
251 resistor = 870 * 1000 + delta;
253 case 3: /* Party is automatic mode with +15K */
254 resistor = 286 * 1000 + 210 * 1000;
256 case 1: /* Summer range 1240 - 1660 ohm */
257 resistor = 1450 * 1000 + delta;
260 /* signal a shortcut as error */
264 EmDeviceEmulator device = this.device;
265 if (device != null) {
266 device.setResistorValueByNr(channel, resistor);
271 public void propertyChange(@Nullable PropertyChangeEvent evt) {
273 String s = evt.getPropertyName();
274 if (s.startsWith("relay") && s.endsWith("Value")) {
275 int v = (Integer) evt.getNewValue();
276 int i = Integer.parseInt(s.substring(5, 6));
277 setRelayChannelValue(i, v);
278 } else if (s.contentEquals("connectionState")) {
279 ConnectionState ste = (ConnectionState) evt.getNewValue();
280 if (ste.equals(ConnectionState.CONNECTED)) {
281 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
283 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ste.toString());
289 private void setRelayChannelValue(int relay, double value) {
290 String channelId = CHANNEL_RELAY + relay;
291 updateState(channelId, new DecimalType(value));
295 public void packetReceived(Specification spec, Language lang, Packet packet) {
296 /* nothing to do here */