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.valloxmv.internal;
15 import java.io.ByteArrayOutputStream;
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.math.BigDecimal;
20 import java.net.URISyntaxException;
21 import java.nio.ByteBuffer;
22 import java.time.ZonedDateTime;
23 import java.util.Calendar;
24 import java.util.HashMap;
26 import java.util.TimeZone;
27 import java.util.concurrent.CountDownLatch;
28 import java.util.concurrent.Future;
29 import java.util.concurrent.TimeUnit;
31 import org.eclipse.jetty.websocket.api.Session;
32 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
33 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
34 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
35 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
36 import org.eclipse.jetty.websocket.api.annotations.WebSocket;
37 import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
38 import org.eclipse.jetty.websocket.client.WebSocketClient;
39 import org.openhab.core.library.types.DateTimeType;
40 import org.openhab.core.library.types.DecimalType;
41 import org.openhab.core.library.types.OnOffType;
42 import org.openhab.core.library.types.QuantityType;
43 import org.openhab.core.library.unit.MetricPrefix;
44 import org.openhab.core.library.unit.SIUnits;
45 import org.openhab.core.library.unit.Units;
46 import org.openhab.core.thing.ChannelUID;
47 import org.openhab.core.thing.ThingStatus;
48 import org.openhab.core.thing.ThingStatusDetail;
49 import org.openhab.core.types.State;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
54 * The {@link ValloxMVWebSocket} is responsible for socket communication with the vallox ventilation unit
56 * @author Björn Brings - Initial contribution
58 public class ValloxMVWebSocket {
59 private final ValloxMVHandler voHandler;
60 private final WebSocketClient client;
61 private final URI destUri;
62 private ValloxMVWebSocketListener socket;
64 private int iBoostTime;
65 private OnOffType ooBoostTimerEnabled;
66 private int iFireplaceTime;
67 private OnOffType ooFireplaceTimerEnabled;
69 private final Logger logger = LoggerFactory.getLogger(ValloxMVWebSocket.class);
71 public ValloxMVWebSocket(WebSocketClient webSocketClient, ValloxMVHandler voHandler, String ip) {
72 this.voHandler = voHandler;
73 this.client = webSocketClient;
76 tempUri = new URI("ws://" + ip + ":80");
77 } catch (URISyntaxException e) {
84 public void request(ChannelUID channelUID, String updateState) {
85 Future<?> sessionFuture = null;
87 socket = new ValloxMVWebSocketListener(channelUID, updateState);
89 ClientUpgradeRequest request = new ClientUpgradeRequest();
90 logger.debug("Connecting to: {}", destUri);
91 sessionFuture = client.connect(socket, destUri, request);
92 socket.awaitClose(2, TimeUnit.SECONDS);
93 } catch (InterruptedException | IOException e) {
95 } catch (Exception e) {
96 logger.debug("Unexpected error");
99 if (sessionFuture != null && !sessionFuture.isDone()) {
100 sessionFuture.cancel(true);
105 public void connectionError(Exception e) {
106 logger.debug("Error connecting vallox unit.", e);
107 voHandler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR);
111 public class ValloxMVWebSocketListener {
112 private final CountDownLatch closeLatch = new CountDownLatch(1);
114 private final Logger logger = LoggerFactory.getLogger(ValloxMVWebSocketListener.class);
115 private final String updateState;
116 private ChannelUID channelUID;
117 private int iValloxCmd;
119 public ValloxMVWebSocketListener(ChannelUID channelUID, String updateState) {
120 this.updateState = updateState;
121 this.channelUID = channelUID;
125 public void onConnect(Session session) {
127 logger.debug("Connected to: {}", session.getRemoteAddress().getAddress());
128 ByteBuffer buf = generateRequest();
129 session.getRemote().sendBytes(buf);
130 } catch (IOException e) {
136 * Method to generate ByteBuffer request to be sent to vallox online websocket
137 * to request or set data
139 * @param mode 246 for data request, 249 for setting data
140 * @param hmParameters HashMap for setting data with register as key and value as value
141 * @return ByteBuffer to be sent to websocket
143 public ByteBuffer generateCustomRequest(Integer mode, Map<Integer, Integer> hmParameters) {
144 // If we just want data the format is different so its just hardcoded here
146 // requestData (Length 3, Command to get data 246, 0, checksum [sum of everything before])
147 return ByteBuffer.wrap(new byte[] { 3, 0, (byte) 246, 0, 0, 0, (byte) 249, 0 });
150 int numberParameters = (hmParameters.size() * 2) + 2; // Parameters (key + value) + Mode + Checksum
151 int checksum = numberParameters;
152 // Allocate for bytebuffer incl. Length
153 ByteBuffer bb = ByteBuffer.allocate((numberParameters + 1) * 2)
154 .put(convertIntegerIntoByteBuffer(numberParameters));
156 // Put Mode, HashMap and checksum to ByteArray
157 bb.put(convertIntegerIntoByteBuffer(mode));
160 for (Map.Entry<Integer, Integer> i : hmParameters.entrySet()) {
161 bb.put(convertIntegerIntoByteBuffer(i.getKey()));
162 bb.put(convertIntegerIntoByteBuffer(i.getValue()));
163 checksum += i.getKey() + i.getValue();
166 // We have to make sure that checksum is within the range
167 // Checksum may hold larger value than 65535 from the above loop
168 // We will never reach integer max value in the loop so it is ok to do this after the loop
169 // This is not needed if we make sure that conversion to bytes will take care of it
170 // bitwise AND inside convertIntegerIntoByteBuffer() takes care of this
171 // checksum = checksum & 0xffff;
173 bb.put(convertIntegerIntoByteBuffer(checksum));
178 // Convert Integer to ByteBuffer in Little-endian
179 public ByteBuffer convertIntegerIntoByteBuffer(Integer i) {
180 // Use bitwise operators to extract two rightmost bytes from the integer
181 byte b1 = (byte) (i & 0xff); // Rightmost byte
182 byte b2 = (byte) ((i >> 8) & 0xff); // Second rightmost byte
183 return ByteBuffer.wrap(new byte[] { b1, b2 });
186 public ByteBuffer generateRequest() {
187 if ((updateState == null) || (channelUID == null)) {
188 // requestData (Length 3, Command to get data 246, empty set, checksum [sum of everything before])
190 return generateCustomRequest(246, new HashMap<>());
192 String strChannelUIDid = channelUID.getId();
193 int iUpdateState = Integer.parseInt(updateState);
194 Map<Integer, Integer> request = new HashMap<>();
195 switch (strChannelUIDid) {
196 case ValloxMVBindingConstants.CHANNEL_STATE:
197 switch (iUpdateState) {
198 case ValloxMVBindingConstants.STATE_FIREPLACE:
199 // Fireplace (Length 6, Command to set data 249, CYC_BOOST_TIMER (4612) = 0,
200 // CYC_FIREPLACE_TIMER (4613) = value from CYC_FIREPLACE_TIME, checksum)
201 // CYC_FIREPLACE_TIME is read during READ_TABLES and stored into outer class variable
202 if (iFireplaceTime < 1) {
203 // use 15 minutes in case not initialized (should never happen)
206 if (OnOffType.ON.equals(ooFireplaceTimerEnabled)) {
207 logger.debug("Changing to Fireplace profile, timer {} minutes", iFireplaceTime);
209 logger.debug("Changing to Fireplace profile, timer not enabled");
211 request.put(4612, 0);
212 request.put(4613, iFireplaceTime);
214 case ValloxMVBindingConstants.STATE_ATHOME:
215 // At Home (Length 8, Command to set data 249, CYC_STATE (4609) = 0,
216 // CYC_BOOST_TIMER (4612) = 0, CYC_FIREPLACE_TIMER (4613) = 0, checksum)
217 logger.debug("Changing to At Home profile");
218 request.put(4609, 0);
219 request.put(4612, 0);
220 request.put(4613, 0);
222 case ValloxMVBindingConstants.STATE_AWAY:
223 // Away (Length 8, Command to set data 249, CYC_STATE (4609) = 1,
224 // CYC_BOOST_TIMER (4612) = 0, CYC_FIREPLACE_TIMER (4613) = 0, checksum)
225 logger.debug("Changing to Away profile");
226 request.put(4609, 1);
227 request.put(4612, 0);
228 request.put(4613, 0);
230 case ValloxMVBindingConstants.STATE_BOOST:
231 // Boost (Length 6, Command to set data 249,
232 // CYC_BOOST_TIMER (4612) = value from CYC_BOOST_TIME,
233 // CYC_FIREPLACE_TIMER (4613) = 0, checksum)
234 // CYC_BOOST_TIME is read during READ_TABLES and stored into outer class variable
235 if (iBoostTime < 1) {
236 // use 30 minutes in case not initialized (should never happen)
239 if (OnOffType.ON.equals(ooBoostTimerEnabled)) {
240 logger.debug("Changing to Boost profile, timer {} minutes", iBoostTime);
242 logger.debug("Changing to Boost profile, timer not enabled");
244 request.put(4612, iBoostTime);
245 request.put(4613, 0);
248 // This should never happen. Let's get back to basic profile.
249 // Clearing boost and fireplace timers.
250 logger.debug("Incorrect profile requested, changing back to basic profile");
251 request.put(4612, 0);
252 request.put(4613, 0);
255 case ValloxMVBindingConstants.CHANNEL_ONOFF:
256 request.put(4610, iUpdateState);
258 case ValloxMVBindingConstants.CHANNEL_EXTR_FAN_BALANCE_BASE:
259 request.put(20485, iUpdateState);
261 case ValloxMVBindingConstants.CHANNEL_SUPP_FAN_BALANCE_BASE:
262 request.put(20486, iUpdateState);
264 case ValloxMVBindingConstants.CHANNEL_HOME_SPEED_SETTING:
265 request.put(20507, iUpdateState);
267 case ValloxMVBindingConstants.CHANNEL_AWAY_SPEED_SETTING:
268 request.put(20501, iUpdateState);
270 case ValloxMVBindingConstants.CHANNEL_BOOST_SPEED_SETTING:
271 request.put(20513, iUpdateState);
273 case ValloxMVBindingConstants.CHANNEL_HOME_AIR_TEMP_TARGET:
274 request.put(20508, iUpdateState);
276 case ValloxMVBindingConstants.CHANNEL_AWAY_AIR_TEMP_TARGET:
277 request.put(20502, iUpdateState);
279 case ValloxMVBindingConstants.CHANNEL_BOOST_AIR_TEMP_TARGET:
280 request.put(20514, iUpdateState);
282 case ValloxMVBindingConstants.CHANNEL_BOOST_TIME:
283 iBoostTime = iUpdateState;
284 request.put(20544, iUpdateState);
286 case ValloxMVBindingConstants.CHANNEL_BOOST_TIMER_ENABLED:
287 ooBoostTimerEnabled = OnOffType.from(Integer.toString(iUpdateState));
288 request.put(21766, iUpdateState);
290 case ValloxMVBindingConstants.CHANNEL_FIREPLACE_EXTR_FAN:
291 request.put(20487, iUpdateState);
293 case ValloxMVBindingConstants.CHANNEL_FIREPLACE_SUPP_FAN:
294 request.put(20488, iUpdateState);
296 case ValloxMVBindingConstants.CHANNEL_FIREPLACE_TIME:
297 iFireplaceTime = iUpdateState;
298 request.put(20545, iUpdateState);
300 case ValloxMVBindingConstants.CHANNEL_FIREPLACE_TIMER_ENABLED:
301 ooFireplaceTimerEnabled = OnOffType.from(Integer.toString(iUpdateState));
302 request.put(21767, iUpdateState);
304 case ValloxMVBindingConstants.CHANNEL_EXTRA_AIR_TEMP_TARGET:
305 request.put(20493, iUpdateState);
307 case ValloxMVBindingConstants.CHANNEL_EXTRA_EXTR_FAN:
308 request.put(20494, iUpdateState);
310 case ValloxMVBindingConstants.CHANNEL_EXTRA_SUPP_FAN:
311 request.put(20495, iUpdateState);
313 case ValloxMVBindingConstants.CHANNEL_EXTRA_TIME:
314 request.put(20496, iUpdateState);
316 case ValloxMVBindingConstants.CHANNEL_EXTRA_TIMER_ENABLED:
317 request.put(21772, iUpdateState);
319 case ValloxMVBindingConstants.CHANNEL_WEEKLY_TIMER_ENABLED:
320 request.put(4615, iUpdateState);
326 return generateCustomRequest(249, request);
330 public void onMessage(String message) {
331 logger.debug("Message from Server: {}", message);
335 public void onError(Throwable cause) {
336 voHandler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
337 logger.debug("Connection failed: {}", cause.getMessage());
341 public void onBinary(InputStream in) {
342 logger.debug("Got binary message");
344 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
347 byte[] data = new byte[16384];
349 while ((nRead = in.read(data, 0, data.length)) != -1) {
350 buffer.write(data, 0, nRead);
355 byte[] bytes = buffer.toByteArray();
357 if ((bytes.length > 5) && (bytes.length % 2 == 0)) {
358 logger.debug("Response length: {} bytes", bytes.length);
360 logger.debug("Response corrupted, length: {} bytes", bytes.length);
364 int iDataLength = bytes.length / 2;
366 // Verify responses to requests
367 if ((iValloxCmd == 249) || (iValloxCmd == 250)) {
368 // COMMAND_WRITE_DATA (249) or COMMAND_READ_DATA (250)
370 int[] arriData = new int[iDataLength];
371 for (int i = 0; i < iDataLength; i++) {
372 arriData[i] = getNumberLE(bytes, (i * 2));
374 for (int i = 0; i < (iDataLength - 1); i++) {
375 iChecksum += arriData[i];
378 if ((arriData[0] != (iDataLength - 1)) || (arriData[iDataLength - 1] != iChecksum)) {
379 // Data length or Checksum do not match
380 logger.debug("Response corrupted, Data length or Checksum do not match");
383 // COMMAND_WRITE_DATA (249)
384 if (iValloxCmd == 249) {
385 String strChannelUIDid = channelUID.getId();
386 if (arriData[1] == 245) {
388 logger.debug("Channel {} successfully updated to {}", strChannelUIDid, updateState);
390 logger.debug("Channel {} could not be updated", strChannelUIDid);
394 // COMMAND_READ_DATA (250)
395 /* Read data command is not implemented, response check is ready for future implementation
397 if ((((iDataLength - 1) % 2) != 0) || (iDataLength < 5)) {
398 logger.debug("Response corrupted, data length is wrong");
401 // number of data pairs (address, value) = (iDataLength - 3) / 2
402 // First data pair (address, value) is in positions 2 and 3
403 // (address, value) pairs could be put into array or hashmap
406 logger.debug("Vallox command {} not implemented", iValloxCmd);
408 } else if (iValloxCmd == 246) {
409 // COMMAND_READ_TABLES (246)
410 if (iDataLength > 704) {
411 logger.debug("Data table response with {} values, updating to channels", iDataLength);
413 logger.debug("Response corrupted, data table response not complete");
417 logger.debug("Vallox command {} not implemented", iValloxCmd);
421 // COMMAND_READ_TABLES (246)
422 // Read values from received tables
423 int iFanspeed = getNumberBE(bytes, 128);
424 int iFanspeedExtract = getNumberBE(bytes, 144);
425 int iFanspeedSupply = getNumberBE(bytes, 146);
426 BigDecimal bdTempInside = getTemperature(bytes, 130);
427 BigDecimal bdTempExhaust = getTemperature(bytes, 132);
428 BigDecimal bdTempOutside = getTemperature(bytes, 134);
429 BigDecimal bdTempIncomingBeforeHeating = getTemperature(bytes, 136);
430 BigDecimal bdTempIncoming = getTemperature(bytes, 138);
432 int iHumidity = getNumberBE(bytes, 148);
433 @SuppressWarnings("unused")
434 int iCo2 = getNumberBE(bytes, 150);
436 int iStateOrig = getNumberBE(bytes, 214);
437 int iBoostTimer = getNumberBE(bytes, 220);
438 int iFireplaceTimer = getNumberBE(bytes, 222);
440 int iCellstate = getNumberBE(bytes, 228);
441 int iUptimeYears = getNumberBE(bytes, 230);
442 int iUptimeHours = getNumberBE(bytes, 232);
443 int iUptimeHoursCurrent = getNumberBE(bytes, 234);
445 int iRemainingTimeForFilter = getNumberBE(bytes, 236);
446 int iFilterChangedDateDay = getNumberBE(bytes, 496);
447 int iFilterChangedDateMonth = getNumberBE(bytes, 498);
448 int iFilterChangedDateYear = getNumberBE(bytes, 500);
450 Calendar cFilterChangedDate = Calendar.getInstance();
451 cFilterChangedDate.set(iFilterChangedDateYear + 2000,
452 iFilterChangedDateMonth - 1 /* Month is 0-based */, iFilterChangedDateDay, 0, 0, 0);
454 int iExtrFanBalanceBase = getNumberBE(bytes, 374);
455 int iSuppFanBalanceBase = getNumberBE(bytes, 376);
457 int iHomeSpeedSetting = getNumberBE(bytes, 418);
458 int iAwaySpeedSetting = getNumberBE(bytes, 406);
459 int iBoostSpeedSetting = getNumberBE(bytes, 430);
460 BigDecimal bdHomeAirTempTarget = getTemperature(bytes, 420);
461 BigDecimal bdAwayAirTempTarget = getTemperature(bytes, 408);
462 BigDecimal bdBoostAirTempTarget = getTemperature(bytes, 432);
464 // Using outer class variable for boost time and timer enabled
465 iBoostTime = getNumberBE(bytes, 492);
466 ooBoostTimerEnabled = OnOffType.from(Integer.toString(getNumberBE(bytes, 528)));
467 int iFireplaceExtrFan = getNumberBE(bytes, 378);
468 int iFireplaceSuppFan = getNumberBE(bytes, 380);
469 // Using outer class variable for fireplace time and timer enabled
470 iFireplaceTime = getNumberBE(bytes, 494);
471 ooFireplaceTimerEnabled = OnOffType.from(Integer.toString(getNumberBE(bytes, 530)));
472 BigDecimal bdExtraAirTempTarget = getTemperature(bytes, 390);
473 int iExtraExtrFan = getNumberBE(bytes, 392);
474 int iExtraSuppFan = getNumberBE(bytes, 394);
475 int iExtraTime = getNumberBE(bytes, 396);
476 OnOffType ooExtraTimerEnabled = OnOffType.from(Integer.toString(getNumberBE(bytes, 540)));
477 OnOffType ooWeeklyTimerEnabled = OnOffType.from(Integer.toString(getNumberBE(bytes, 226)));
480 if (iFireplaceTimer > 0) {
481 bdState = new BigDecimal(ValloxMVBindingConstants.STATE_FIREPLACE);
482 } else if (iBoostTimer > 0) {
483 bdState = new BigDecimal(ValloxMVBindingConstants.STATE_BOOST);
484 } else if (iStateOrig == 1) {
485 bdState = new BigDecimal(ValloxMVBindingConstants.STATE_AWAY);
487 bdState = new BigDecimal(ValloxMVBindingConstants.STATE_ATHOME);
490 OnOffType ooOnOff = OnOffType.from(bytes[217] != 5);
492 // Update channels with read values
493 updateChannel(ValloxMVBindingConstants.CHANNEL_ONOFF, ooOnOff);
494 updateChannel(ValloxMVBindingConstants.CHANNEL_STATE, new DecimalType(bdState));
495 updateChannel(ValloxMVBindingConstants.CHANNEL_FAN_SPEED, new QuantityType<>(iFanspeed, Units.PERCENT));
496 updateChannel(ValloxMVBindingConstants.CHANNEL_FAN_SPEED_EXTRACT, new DecimalType(iFanspeedExtract));
497 updateChannel(ValloxMVBindingConstants.CHANNEL_FAN_SPEED_SUPPLY, new DecimalType(iFanspeedSupply));
498 updateChannel(ValloxMVBindingConstants.CHANNEL_TEMPERATURE_INSIDE,
499 new QuantityType<>(bdTempInside, SIUnits.CELSIUS));
500 updateChannel(ValloxMVBindingConstants.CHANNEL_TEMPERATURE_OUTSIDE,
501 new QuantityType<>(bdTempOutside, SIUnits.CELSIUS));
502 updateChannel(ValloxMVBindingConstants.CHANNEL_TEMPERATURE_EXHAUST,
503 new QuantityType<>(bdTempExhaust, SIUnits.CELSIUS));
504 updateChannel(ValloxMVBindingConstants.CHANNEL_TEMPERATURE_INCOMING_BEFORE_HEATING,
505 new QuantityType<>(bdTempIncomingBeforeHeating, SIUnits.CELSIUS));
506 updateChannel(ValloxMVBindingConstants.CHANNEL_TEMPERATURE_INCOMING,
507 new QuantityType<>(bdTempIncoming, SIUnits.CELSIUS));
508 updateChannel(ValloxMVBindingConstants.CHANNEL_HUMIDITY, new QuantityType<>(iHumidity, Units.PERCENT));
509 updateChannel(ValloxMVBindingConstants.CHANNEL_CO2, new QuantityType<>(iCo2, Units.PARTS_PER_MILLION));
510 updateChannel(ValloxMVBindingConstants.CHANNEL_CELLSTATE, new DecimalType(iCellstate));
511 updateChannel(ValloxMVBindingConstants.CHANNEL_UPTIME_YEARS, new DecimalType(iUptimeYears));
512 updateChannel(ValloxMVBindingConstants.CHANNEL_UPTIME_HOURS, new DecimalType(iUptimeHours));
513 updateChannel(ValloxMVBindingConstants.CHANNEL_UPTIME_HOURS_CURRENT,
514 new DecimalType(iUptimeHoursCurrent));
515 updateChannel(ValloxMVBindingConstants.CHANNEL_FILTER_CHANGED_DATE, new DateTimeType(
516 ZonedDateTime.ofInstant(cFilterChangedDate.toInstant(), TimeZone.getDefault().toZoneId())));
517 updateChannel(ValloxMVBindingConstants.CHANNEL_REMAINING_FILTER_DAYS,
518 new QuantityType<>(iRemainingTimeForFilter, Units.DAY));
519 updateChannel(ValloxMVBindingConstants.CHANNEL_EXTR_FAN_BALANCE_BASE,
520 new QuantityType<>(iExtrFanBalanceBase, Units.PERCENT));
521 updateChannel(ValloxMVBindingConstants.CHANNEL_SUPP_FAN_BALANCE_BASE,
522 new QuantityType<>(iSuppFanBalanceBase, Units.PERCENT));
523 updateChannel(ValloxMVBindingConstants.CHANNEL_HOME_SPEED_SETTING,
524 new QuantityType<>(iHomeSpeedSetting, Units.PERCENT));
525 updateChannel(ValloxMVBindingConstants.CHANNEL_AWAY_SPEED_SETTING,
526 new QuantityType<>(iAwaySpeedSetting, Units.PERCENT));
527 updateChannel(ValloxMVBindingConstants.CHANNEL_BOOST_SPEED_SETTING,
528 new QuantityType<>(iBoostSpeedSetting, Units.PERCENT));
529 updateChannel(ValloxMVBindingConstants.CHANNEL_HOME_AIR_TEMP_TARGET,
530 new QuantityType<>(bdHomeAirTempTarget, SIUnits.CELSIUS));
531 updateChannel(ValloxMVBindingConstants.CHANNEL_AWAY_AIR_TEMP_TARGET,
532 new QuantityType<>(bdAwayAirTempTarget, SIUnits.CELSIUS));
533 updateChannel(ValloxMVBindingConstants.CHANNEL_BOOST_AIR_TEMP_TARGET,
534 new QuantityType<>(bdBoostAirTempTarget, SIUnits.CELSIUS));
535 updateChannel(ValloxMVBindingConstants.CHANNEL_BOOST_TIME, new DecimalType(iBoostTime));
536 updateChannel(ValloxMVBindingConstants.CHANNEL_BOOST_TIMER_ENABLED, ooBoostTimerEnabled);
537 updateChannel(ValloxMVBindingConstants.CHANNEL_FIREPLACE_EXTR_FAN,
538 new QuantityType<>(iFireplaceExtrFan, Units.PERCENT));
539 updateChannel(ValloxMVBindingConstants.CHANNEL_FIREPLACE_SUPP_FAN,
540 new QuantityType<>(iFireplaceSuppFan, Units.PERCENT));
541 updateChannel(ValloxMVBindingConstants.CHANNEL_FIREPLACE_TIME, new DecimalType(iFireplaceTime));
542 updateChannel(ValloxMVBindingConstants.CHANNEL_FIREPLACE_TIMER_ENABLED, ooFireplaceTimerEnabled);
543 updateChannel(ValloxMVBindingConstants.CHANNEL_EXTRA_AIR_TEMP_TARGET,
544 new QuantityType<>(bdExtraAirTempTarget, SIUnits.CELSIUS));
545 updateChannel(ValloxMVBindingConstants.CHANNEL_EXTRA_EXTR_FAN,
546 new QuantityType<>(iExtraExtrFan, Units.PERCENT));
547 updateChannel(ValloxMVBindingConstants.CHANNEL_EXTRA_SUPP_FAN,
548 new QuantityType<>(iExtraSuppFan, Units.PERCENT));
549 updateChannel(ValloxMVBindingConstants.CHANNEL_EXTRA_TIME, new DecimalType(iExtraTime));
550 updateChannel(ValloxMVBindingConstants.CHANNEL_EXTRA_TIMER_ENABLED, ooExtraTimerEnabled);
551 updateChannel(ValloxMVBindingConstants.CHANNEL_WEEKLY_TIMER_ENABLED, ooWeeklyTimerEnabled);
553 voHandler.updateStatus(ThingStatus.ONLINE);
554 logger.debug("Data updated successfully");
556 } catch (IOException e) {
561 private void updateChannel(String strChannelName, State state) {
562 voHandler.updateState(strChannelName, state);
565 private int getNumberBE(byte[] bytes, int pos) {
566 return ((bytes[pos] & 0xff) << 8) | (bytes[pos + 1] & 0xff);
569 private int getNumberLE(byte[] bytes, int pos) {
570 return (bytes[pos] & 0xff) | ((bytes[pos + 1] & 0xff) << 8);
573 @SuppressWarnings("null")
574 private BigDecimal getTemperature(byte[] bytes, int pos) {
575 // Fetch 2 byte number out of bytearray representing the temperature in centiKelvin
576 BigDecimal bdTemperatureCentiKelvin = new BigDecimal(getNumberBE(bytes, pos));
577 // Return number converted to degree celsius (= (centiKelvin - 27315) / 100 )
578 return (new QuantityType<>(bdTemperatureCentiKelvin, MetricPrefix.CENTI(Units.KELVIN))
579 .toUnit(SIUnits.CELSIUS)).toBigDecimal();
583 public void onClose(int statusCode, String reason) {
584 logger.debug("WebSocket Closed. Code: {}; Reason: {}", statusCode, reason);
585 this.closeLatch.countDown();
588 public boolean awaitClose(int duration, TimeUnit unit) throws InterruptedException {
589 return this.closeLatch.await(duration, unit);