2 * Copyright (c) 2010-2021 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.bluetooth.daikinmadoka.handler;
15 import java.util.Arrays;
16 import java.util.Random;
17 import java.util.concurrent.Executor;
18 import java.util.concurrent.ExecutorService;
19 import java.util.concurrent.Executors;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
23 import javax.measure.quantity.Temperature;
24 import javax.measure.quantity.Time;
26 import org.eclipse.jdt.annotation.NonNull;
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.bluetooth.BluetoothCharacteristic;
30 import org.openhab.binding.bluetooth.BluetoothCompletionStatus;
31 import org.openhab.binding.bluetooth.BluetoothDevice.ConnectionState;
32 import org.openhab.binding.bluetooth.ConnectedBluetoothHandler;
33 import org.openhab.binding.bluetooth.daikinmadoka.DaikinMadokaBindingConstants;
34 import org.openhab.binding.bluetooth.daikinmadoka.internal.BRC1HUartProcessor;
35 import org.openhab.binding.bluetooth.daikinmadoka.internal.DaikinMadokaConfiguration;
36 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaMessage;
37 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaParsingException;
38 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaProperties.FanSpeed;
39 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaProperties.OperationMode;
40 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaSettings;
41 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.BRC1HCommand;
42 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.DisableCleanFilterIndicatorCommand;
43 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.EnterPrivilegedModeCommand;
44 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetCleanFilterIndicatorCommand;
45 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetEyeBrightnessCommand;
46 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetFanspeedCommand;
47 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetIndoorOutoorTemperatures;
48 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetOperationHoursCommand;
49 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetOperationmodeCommand;
50 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetPowerstateCommand;
51 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetSetpointCommand;
52 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetVersionCommand;
53 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.ResetCleanFilterTimerCommand;
54 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.ResponseListener;
55 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetEyeBrightnessCommand;
56 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetFanspeedCommand;
57 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetOperationmodeCommand;
58 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetPowerstateCommand;
59 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetSetpointCommand;
60 import org.openhab.core.common.NamedThreadFactory;
61 import org.openhab.core.library.types.DecimalType;
62 import org.openhab.core.library.types.OnOffType;
63 import org.openhab.core.library.types.PercentType;
64 import org.openhab.core.library.types.QuantityType;
65 import org.openhab.core.library.types.StringType;
66 import org.openhab.core.thing.ChannelUID;
67 import org.openhab.core.thing.Thing;
68 import org.openhab.core.types.Command;
69 import org.openhab.core.types.RefreshType;
70 import org.openhab.core.types.State;
71 import org.openhab.core.types.UnDefType;
72 import org.openhab.core.util.HexUtils;
73 import org.slf4j.Logger;
74 import org.slf4j.LoggerFactory;
77 * The {@link DaikinMadokaHandler} is responsible for handling commands, which are
78 * sent to one of the channels as well as updating channel values.
80 * @author Benjamin Lafois - Initial contribution
83 public class DaikinMadokaHandler extends ConnectedBluetoothHandler implements ResponseListener {
85 private final Logger logger = LoggerFactory.getLogger(DaikinMadokaHandler.class);
87 private @Nullable DaikinMadokaConfiguration config;
89 private @Nullable ExecutorService commandExecutor;
91 private @Nullable ScheduledFuture<?> refreshJob;
93 // UART Processor is in charge of reassembling chunks
94 private BRC1HUartProcessor uartProcessor = new BRC1HUartProcessor(this);
96 private volatile @Nullable BRC1HCommand currentCommand = null;
98 private MadokaSettings madokaSettings = new MadokaSettings();
100 public DaikinMadokaHandler(Thing thing) {
105 public void initialize() {
108 logger.debug("[{}] Start initializing!", super.thing.getUID().getId());
110 // Load Configuration
111 config = getConfigAs(DaikinMadokaConfiguration.class);
112 DaikinMadokaConfiguration c = config;
114 logger.debug("[{}] Parameter value [refreshInterval]: {}", super.thing.getUID().getId(), c.refreshInterval);
115 logger.debug("[{}] Parameter value [commandTimeout]: {}", super.thing.getUID().getId(), c.commandTimeout);
117 if (getBridge() == null) {
118 logger.debug("[{}] Bridge is null. Exiting.", super.thing.getUID().getId());
122 this.commandExecutor = Executors
123 .newSingleThreadExecutor(new NamedThreadFactory(thing.getUID().getAsString(), true));
125 this.refreshJob = scheduler.scheduleWithFixedDelay(() -> {
126 // It is useless to refresh version all the time ! Just once.
127 if (this.madokaSettings.getCommunicationControllerVersion() == null
128 || this.madokaSettings.getRemoteControllerVersion() == null) {
129 submitCommand(new GetVersionCommand());
131 submitCommand(new GetIndoorOutoorTemperatures());
132 submitCommand(new GetOperationmodeCommand());
133 submitCommand(new GetPowerstateCommand()); // always keep the "GetPowerState" aftern the "GetOperationMode"
134 submitCommand(new GetSetpointCommand());
135 submitCommand(new GetFanspeedCommand());
136 submitCommand(new GetCleanFilterIndicatorCommand());
139 // As it is a complex operation - it has been extracted to a method.
140 retrieveOperationHours();
141 } catch (InterruptedException e) {
142 // The thread wants to exit!
146 submitCommand(new GetEyeBrightnessCommand());
147 }, new Random().nextInt(30), c.refreshInterval, TimeUnit.SECONDS); // We introduce a random start time, it
148 // avoids when having multiple devices to
149 // have the commands sent simultaneously.
152 private void retrieveOperationHours() throws InterruptedException {
153 // This one is special - and MUST be ran twice, after being in priv mode
154 // run it once an hour is sufficient... TODO
155 submitCommand(new EnterPrivilegedModeCommand());
156 submitCommand(new GetOperationHoursCommand());
157 // a 1second+ delay is necessary
160 submitCommand(new GetOperationHoursCommand());
164 public void dispose() {
165 logger.debug("[{}] dispose()", super.thing.getUID().getId());
168 dispose(commandExecutor);
169 dispose(currentCommand);
171 // Unsubscribe to characteristic notifications
172 if (this.device != null) {
173 BluetoothCharacteristic charNotif = this.device
174 .getCharacteristic(DaikinMadokaBindingConstants.CHAR_NOTIF_UUID);
176 if (charNotif != null) {
178 BluetoothCharacteristic c = charNotif;
179 this.device.disableNotifications(c);
186 private static void dispose(@Nullable ExecutorService executor) {
187 if (executor != null) {
188 executor.shutdownNow();
192 private static void dispose(@Nullable ScheduledFuture<?> future) {
193 if (future != null) {
198 private static void dispose(@Nullable BRC1HCommand command) {
199 if (command != null) {
200 // even if it already completed it doesn't really matter.
201 // on the off chance that the commandExecutor is waiting on the command, we can wake it up and cause it to
203 command.setState(BRC1HCommand.State.FAILED);
208 public void handleCommand(ChannelUID channelUID, Command command) {
209 logger.debug("[{}] Channel: {}, Command: {}", super.thing.getUID().getId(), channelUID, command);
211 if (command instanceof RefreshType) {
212 // The refresh commands are not supported in query mode.
213 // The binding will notify updates on channels
217 switch (channelUID.getId()) {
218 case DaikinMadokaBindingConstants.CHANNEL_ID_CLEAN_FILTER_INDICATOR:
219 OnOffType cleanFilterOrder = (OnOffType) command;
220 if (cleanFilterOrder == OnOffType.OFF) {
221 resetCleanFilterIndicator();
224 case DaikinMadokaBindingConstants.CHANNEL_ID_SETPOINT:
226 QuantityType<Temperature> setpoint = (QuantityType<Temperature>) command;
227 submitCommand(new SetSetpointCommand(setpoint, setpoint));
228 } catch (Exception e) {
229 logger.warn("Data received is not a valid temperature.", e);
232 case DaikinMadokaBindingConstants.CHANNEL_ID_EYE_BRIGHTNESS:
234 logger.debug("Set eye brightness with value {}, {}", command.getClass().getName(), command);
235 PercentType p = (PercentType) command;
236 submitCommand(new SetEyeBrightnessCommand(p));
237 } catch (Exception e) {
238 logger.warn("Data received is not a valid Eye Brightness status", e);
241 case DaikinMadokaBindingConstants.CHANNEL_ID_ONOFF_STATUS:
243 OnOffType oot = (OnOffType) command;
244 submitCommand(new SetPowerstateCommand(oot));
245 } catch (Exception e) {
246 logger.warn("Data received is not a valid on/off status", e);
249 case DaikinMadokaBindingConstants.CHANNEL_ID_FAN_SPEED:
251 DecimalType fanSpeed = (DecimalType) command;
252 FanSpeed fs = FanSpeed.valueOf(fanSpeed.intValue());
253 submitCommand(new SetFanspeedCommand(fs, fs));
254 } catch (Exception e) {
255 logger.warn("Data received is not a valid FanSpeed status", e);
258 case DaikinMadokaBindingConstants.CHANNEL_ID_OPERATION_MODE:
260 StringType operationMode = (StringType) command;
261 OperationMode m = OperationMode.valueOf(operationMode.toFullString());
263 submitCommand(new SetOperationmodeCommand(m));
264 } catch (Exception e) {
265 logger.warn("Data received is not a valid OPERATION MODE", e);
268 case DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE:
270 // Homebridge are discrete value different from Daikin
275 DecimalType homebridgeMode = (DecimalType) command;
276 switch (homebridgeMode.intValue()) {
278 submitCommand(new SetPowerstateCommand(OnOffType.OFF));
281 submitCommand(new SetOperationmodeCommand(OperationMode.HEAT));
282 if (madokaSettings.getOnOffState() == OnOffType.OFF) {
283 submitCommand(new SetPowerstateCommand(OnOffType.ON));
287 submitCommand(new SetOperationmodeCommand(OperationMode.COOL));
288 if (madokaSettings.getOnOffState() == OnOffType.OFF) {
289 submitCommand(new SetPowerstateCommand(OnOffType.ON));
293 submitCommand(new SetOperationmodeCommand(OperationMode.AUTO));
294 if (madokaSettings.getOnOffState() == OnOffType.OFF) {
295 submitCommand(new SetPowerstateCommand(OnOffType.ON));
298 default: // Invalid Value - in case of new FW
299 logger.warn("Invalid value received for channel {}. Ignoring.", channelUID);
302 } catch (Exception e) {
303 logger.warn("Data received is not a valid HOMEBRIDGE OPERATION MODE", e);
306 case DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_TARGET_HEATING_COOLING_MODE:
308 StringType homekitOperationMode = (StringType) command;
310 switch (homekitOperationMode.toString()) {
312 submitCommand(new SetPowerstateCommand(OnOffType.OFF));
315 submitCommand(new SetOperationmodeCommand(OperationMode.COOL));
316 if (madokaSettings.getOnOffState() == OnOffType.OFF) {
317 submitCommand(new SetPowerstateCommand(OnOffType.ON));
321 submitCommand(new SetOperationmodeCommand(OperationMode.HEAT));
322 if (madokaSettings.getOnOffState() == OnOffType.OFF) {
323 submitCommand(new SetPowerstateCommand(OnOffType.ON));
327 submitCommand(new SetOperationmodeCommand(OperationMode.AUTO));
328 if (madokaSettings.getOnOffState() == OnOffType.OFF) {
329 submitCommand(new SetPowerstateCommand(OnOffType.ON));
335 } catch (Exception e) {
336 logger.info("Error while setting mode through HomeKIt received Mode");
344 * 2 actions need to be done: disable the notification AND reset the filter timer
346 private void resetCleanFilterIndicator() {
347 logger.debug("[{}] resetCleanFilterIndicator()", super.thing.getUID().getId());
348 submitCommand(new DisableCleanFilterIndicatorCommand());
349 submitCommand(new ResetCleanFilterTimerCommand());
353 public void onCharacteristicUpdate(BluetoothCharacteristic characteristic) {
354 if (logger.isDebugEnabled()) {
355 logger.debug("[{}] onCharacteristicUpdate({})", super.thing.getUID().getId(),
356 HexUtils.bytesToHex(characteristic.getByteValue()));
358 super.onCharacteristicUpdate(characteristic);
360 // Check that arguments are valid.
361 if (characteristic.getUuid() == null) {
365 // We are only interested in the Notify Characteristic of UART service
366 if (!characteristic.getUuid().equals(DaikinMadokaBindingConstants.CHAR_NOTIF_UUID)) {
370 // A message cannot be null or have a 0-byte length
371 byte[] msgBytes = characteristic.getByteValue();
372 if (msgBytes == null || msgBytes.length == 0) {
376 this.uartProcessor.chunkReceived(msgBytes);
379 private void submitCommand(BRC1HCommand command) {
380 Executor executor = commandExecutor;
382 if (executor != null) {
383 executor.execute(() -> processCommand(command));
387 private void processCommand(BRC1HCommand command) {
388 logger.debug("[{}] ProcessCommand {}", super.thing.getUID().getId(), command.getClass().getSimpleName());
391 currentCommand = command;
392 uartProcessor.abandon();
394 if (device == null || device.getConnectionState() != ConnectionState.CONNECTED) {
395 logger.debug("Unable to send command {} to device {}: not connected",
396 command.getClass().getSimpleName(), address);
397 command.setState(BRC1HCommand.State.FAILED);
402 logger.debug("Unable to send command {} to device {}: services not resolved",
403 command.getClass().getSimpleName(), device.getAddress());
404 command.setState(BRC1HCommand.State.FAILED);
408 BluetoothCharacteristic charWrite = device
409 .getCharacteristic(DaikinMadokaBindingConstants.CHAR_WRITE_WITHOUT_RESPONSE_UUID);
410 if (charWrite == null) {
411 logger.warn("Unable to execute {}. Characteristic '{}' could not be found.",
412 command.getClass().getSimpleName(),
413 DaikinMadokaBindingConstants.CHAR_WRITE_WITHOUT_RESPONSE_UUID);
414 command.setState(BRC1HCommand.State.FAILED);
418 BluetoothCharacteristic charNotif = this.device
419 .getCharacteristic(DaikinMadokaBindingConstants.CHAR_NOTIF_UUID);
421 if (charNotif != null) {
422 device.enableNotifications(charNotif);
425 // Commands can be composed of multiple chunks
426 for (byte[] chunk : command.getRequest()) {
427 charWrite.setValue(chunk);
428 command.setState(BRC1HCommand.State.ENQUEUED);
429 for (int i = 0; i < DaikinMadokaBindingConstants.WRITE_CHARACTERISTIC_MAX_RETRIES; i++) {
430 if (device.writeCharacteristic(charWrite)) {
431 command.setState(BRC1HCommand.State.SENT);
432 synchronized (command) {
441 if (command.getState() == BRC1HCommand.State.SENT && this.config != null) {
442 if (!command.awaitStateChange(this.config.commandTimeout, TimeUnit.MILLISECONDS,
443 BRC1HCommand.State.SUCCEEDED, BRC1HCommand.State.FAILED)) {
444 logger.debug("[{}] Command {} to device {} timed out", super.thing.getUID().getId(), command,
445 device.getAddress());
446 command.setState(BRC1HCommand.State.FAILED);
449 } catch (Exception e) {
450 currentCommand = null;
451 // Let the exception bubble the stack!
452 throw new RuntimeException(e);
457 } catch (InterruptedException e) {
458 Thread.currentThread().interrupt();
463 public void onCharacteristicWriteComplete(BluetoothCharacteristic characteristic,
464 BluetoothCompletionStatus status) {
465 super.onCharacteristicWriteComplete(characteristic, status);
467 byte[] request = characteristic.getByteValue();
468 BRC1HCommand command = currentCommand;
470 if (command != null) {
472 byte[] lastChunk = command.getRequest()[command.getRequest().length - 1];
473 if (!Arrays.equals(request, lastChunk)) {
474 logger.debug("Write completed for a chunk, but not a complete command.");
475 synchronized (command) {
482 command.setState(BRC1HCommand.State.SENT);
485 command.setState(BRC1HCommand.State.FAILED);
489 if (logger.isDebugEnabled()) {
490 logger.debug("No command found that matches request {}", HexUtils.bytesToHex(request));
496 * When the method is triggered, it means that all message chunks have been received, re-assembled in the right
497 * order and that the payload is ready to be processed.
500 public void receivedResponse(byte[] response) {
501 logger.debug("Received Response");
502 BRC1HCommand command = currentCommand;
504 if (command == null) {
505 if (logger.isDebugEnabled()) {
506 logger.debug("No command present to handle response {}", HexUtils.bytesToHex(response));
510 command.handleResponse(scheduler, this, MadokaMessage.parse(response));
511 } catch (MadokaParsingException e) {
512 logger.debug("Response message could not be parsed correctly ({}): {}. Reason: {}",
513 command.getClass().getSimpleName(), HexUtils.bytesToHex(response), e.getMessage());
519 public void receivedResponse(GetVersionCommand command) {
520 String commCtrlVers = command.getCommunicationControllerVersion();
521 if (commCtrlVers != null) {
522 this.madokaSettings.setCommunicationControllerVersion(commCtrlVers);
523 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_COMMUNICATION_CONTROLLER_VERSION,
524 new StringType(commCtrlVers));
527 String remoteCtrlVers = command.getRemoteControllerVersion();
528 if (remoteCtrlVers != null) {
529 this.madokaSettings.setRemoteControllerVersion(remoteCtrlVers);
530 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_REMOTE_CONTROLLER_VERSION,
531 new StringType(remoteCtrlVers));
536 public void receivedResponse(GetFanspeedCommand command) {
537 if (command.getCoolingFanSpeed() == null || command.getHeatingFanSpeed() == null) {
541 // We need the current operation mode to determine which Fan Speed we use (cooling or heating)
542 OperationMode operationMode = this.madokaSettings.getOperationMode();
543 if (operationMode == null) {
549 switch (operationMode) {
551 logger.debug("In AutoMode, CoolingFanSpeed = {}, HeatingFanSpeed = {}", command.getCoolingFanSpeed(),
552 command.getHeatingFanSpeed());
553 fs = command.getHeatingFanSpeed();
556 fs = command.getHeatingFanSpeed();
559 fs = command.getCoolingFanSpeed();
569 // No need to re-set if it is the same value
570 if (fs.equals(this.madokaSettings.getFanspeed())) {
574 this.madokaSettings.setFanspeed(fs);
575 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_FAN_SPEED, new DecimalType(fs.value()));
579 public void receivedResponse(GetSetpointCommand command) {
580 if (command.getCoolingSetpoint() == null || command.getHeatingSetpoint() == null) {
584 // We need the current operation mode to determine which Fan Speed we use (cooling or heating)
585 OperationMode operationMode = this.madokaSettings.getOperationMode();
586 if (operationMode == null) {
590 QuantityType<Temperature> sp;
592 switch (operationMode) {
594 logger.debug("In AutoMode, CoolingSetpoint = {}, HeatingSetpoint = {}", command.getCoolingSetpoint(),
595 command.getHeatingSetpoint());
596 sp = command.getHeatingSetpoint();
599 sp = command.getHeatingSetpoint();
602 sp = command.getCoolingSetpoint();
612 // No need to re-set if it is the same value
613 if (sp.equals(this.madokaSettings.getSetpoint())) {
617 this.madokaSettings.setSetpoint(sp);
619 QuantityType<Temperature> dt = this.madokaSettings.getSetpoint();
621 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_SETPOINT, dt);
626 public void receivedResponse(GetOperationmodeCommand command) {
627 if (command.getOperationMode() == null) {
628 logger.debug("OperationMode is null.");
632 OperationMode newMode = command.getOperationMode();
634 if (newMode == null) {
638 this.madokaSettings.setOperationMode(newMode);
640 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_OPERATION_MODE, new StringType(newMode.name()));
642 // For HomeKit channel, we need to map it to HomeKit supported strings
643 OnOffType ooStatus = madokaSettings.getOnOffState();
645 if (ooStatus != null && ooStatus == OnOffType.ON) {
648 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
649 new StringType("Cooling"));
650 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(2));
653 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
654 new StringType("Heating"));
655 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(1));
658 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
659 new StringType("Auto"));
660 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(3));
666 // If this is the first channel update - then we set target = current mode
667 if (this.madokaSettings.getHomekitTargetMode() == null) {
668 String newHomekitTargetStatus = null;
670 // For HomeKit channel, we need to map it to HomeKit supported strings
673 newHomekitTargetStatus = "CoolOn";
676 newHomekitTargetStatus = "HeatOn";
682 if (ooStatus != null && ooStatus == OnOffType.ON) {
683 this.madokaSettings.setHomekitTargetMode(newHomekitTargetStatus);
684 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_TARGET_HEATING_COOLING_MODE,
685 new StringType(newHomekitTargetStatus));
686 } else if (ooStatus != null && ooStatus == OnOffType.OFF) {
687 newHomekitTargetStatus = "Off";
688 this.madokaSettings.setHomekitTargetMode(newHomekitTargetStatus);
689 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_TARGET_HEATING_COOLING_MODE,
690 new StringType(newHomekitTargetStatus));
697 public void receivedResponse(GetPowerstateCommand command) {
698 if (command.isPowerState() == null) {
702 OnOffType oot = command.isPowerState() ? OnOffType.ON : OnOffType.OFF;
704 this.madokaSettings.setOnOffState(oot);
706 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_ONOFF_STATUS, oot);
708 if (oot.equals(OnOffType.OFF)) {
709 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
710 new StringType("Off"));
711 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_TARGET_HEATING_COOLING_MODE,
712 new StringType("Off"));
713 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(0));
718 public void receivedResponse(GetIndoorOutoorTemperatures command) {
719 QuantityType<Temperature> newIndoorTemp = command.getIndoorTemperature();
720 if (newIndoorTemp != null) {
721 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_TEMPERATURE, newIndoorTemp);
722 this.madokaSettings.setIndoorTemperature(newIndoorTemp);
725 QuantityType<Temperature> newOutdoorTemp = command.getOutdoorTemperature();
726 if (newOutdoorTemp == null) {
727 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_OUTDOOR_TEMPERATURE, UnDefType.UNDEF);
729 this.madokaSettings.setOutdoorTemperature(newOutdoorTemp);
730 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_OUTDOOR_TEMPERATURE, newOutdoorTemp);
735 public void receivedResponse(GetEyeBrightnessCommand command) {
736 PercentType eyeBrightnessTemp = command.getEyeBrightness();
737 if (eyeBrightnessTemp != null) {
738 this.madokaSettings.setEyeBrightness(eyeBrightnessTemp);
739 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_EYE_BRIGHTNESS, eyeBrightnessTemp);
740 logger.debug("Notified {} channel with value {}", DaikinMadokaBindingConstants.CHANNEL_ID_EYE_BRIGHTNESS,
746 public void receivedResponse(SetEyeBrightnessCommand command) {
747 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_EYE_BRIGHTNESS, command.getEyeBrightness());
748 madokaSettings.setEyeBrightness(command.getEyeBrightness());
752 public void receivedResponse(SetPowerstateCommand command) {
753 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_ONOFF_STATUS, command.getPowerState());
755 madokaSettings.setOnOffState(command.getPowerState());
757 if (command.getPowerState() == OnOffType.ON) {
758 // Depending on the state
760 OperationMode operationMode = madokaSettings.getOperationMode();
761 if (operationMode == null) {
765 switch (operationMode) {
767 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
768 new StringType("Auto"));
769 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(3));
772 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
773 new StringType("Heating"));
774 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(1));
777 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
778 new StringType("Cooling"));
779 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(2));
781 default: // Other Modes are not [yet] supported
785 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
786 new StringType("Off"));
787 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(0));
792 public void receivedResponse(GetOperationHoursCommand command) {
793 logger.debug("receivedResponse(GetOperationHoursCommand command)");
795 QuantityType<Time> indoorPowerHours = command.getIndoorPowerHours();
796 QuantityType<Time> indoorOperationHours = command.getIndoorOperationHours();
797 QuantityType<Time> indoorFanHours = command.getIndoorFanHours();
799 if (indoorPowerHours != null) {
800 this.madokaSettings.setIndoorPowerHours(indoorPowerHours);
801 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_POWER_HOURS, indoorPowerHours);
802 logger.debug("Notified {} channel with value {}",
803 DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_POWER_HOURS, indoorPowerHours);
806 if (indoorOperationHours != null) {
807 this.madokaSettings.setIndoorOperationHours(indoorOperationHours);
808 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_OPERATION_HOURS, indoorOperationHours);
809 logger.debug("Notified {} channel with value {}",
810 DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_OPERATION_HOURS, indoorOperationHours);
813 if (indoorFanHours != null) {
814 this.madokaSettings.setIndoorFanHours(indoorFanHours);
815 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_FAN_HOURS, indoorFanHours);
816 logger.debug("Notified {} channel with value {}", DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_FAN_HOURS,
822 public void receivedResponse(SetSetpointCommand command) {
823 // The update depends on the mode - so if not set - skip
824 OperationMode operationMode = this.madokaSettings.getOperationMode();
825 if (operationMode == null) {
829 switch (operationMode) {
831 this.madokaSettings.setSetpoint(command.getHeatingSetpoint());
834 this.madokaSettings.setSetpoint(command.getCoolingSetpoint());
837 // Here we don't really care if we are taking cooling or heating...
838 this.madokaSettings.setSetpoint(command.getCoolingSetpoint());
844 QuantityType<Temperature> dt = madokaSettings.getSetpoint();
846 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_SETPOINT, dt);
851 public void receivedResponse(GetCleanFilterIndicatorCommand command) {
852 Boolean indicatorStatus = command.getCleanFilterIndicator();
853 if (indicatorStatus != null) {
854 this.madokaSettings.setCleanFilterIndicator(indicatorStatus);
855 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_CLEAN_FILTER_INDICATOR,
856 indicatorStatus == true ? OnOffType.ON : OnOffType.OFF);
861 * Received response to "SetOperationmodeCommand" command
864 public void receivedResponse(SetOperationmodeCommand command) {
865 this.madokaSettings.setOperationMode(command.getOperationMode());
866 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_OPERATION_MODE,
867 new StringType(command.getOperationMode().toString()));
871 * Received response to "SetFanSpeed" command
874 public void receivedResponse(SetFanspeedCommand command) {
875 // The update depends on the mode - so if not set - skip
876 OperationMode operationMode = this.madokaSettings.getOperationMode();
877 if (operationMode == null) {
882 switch (operationMode) {
884 fanSpeed = command.getHeatingFanSpeed();
885 this.madokaSettings.setFanspeed(fanSpeed);
888 fanSpeed = command.getCoolingFanSpeed();
889 this.madokaSettings.setFanspeed(fanSpeed);
892 fanSpeed = command.getCoolingFanSpeed(); // Arbitrary cooling or heating... They are the same!
893 this.madokaSettings.setFanspeed(fanSpeed);
899 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_FAN_SPEED, new DecimalType(fanSpeed.value()));
902 private void updateStateIfLinked(String channelId, State state) {
903 ChannelUID channelUID = new ChannelUID(getThing().getUID(), channelId);
904 if (isLinked(channelUID)) {
905 updateState(channelUID, state);