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.bluetooth.daikinmadoka.handler;
15 import java.util.Random;
16 import java.util.concurrent.ExecutionException;
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;
22 import java.util.concurrent.TimeoutException;
24 import javax.measure.quantity.Temperature;
25 import javax.measure.quantity.Time;
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.BluetoothDevice.ConnectionState;
31 import org.openhab.binding.bluetooth.ConnectedBluetoothHandler;
32 import org.openhab.binding.bluetooth.daikinmadoka.DaikinMadokaBindingConstants;
33 import org.openhab.binding.bluetooth.daikinmadoka.internal.BRC1HUartProcessor;
34 import org.openhab.binding.bluetooth.daikinmadoka.internal.DaikinMadokaConfiguration;
35 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaMessage;
36 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaParsingException;
37 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaProperties.FanSpeed;
38 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaProperties.OperationMode;
39 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaSettings;
40 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.BRC1HCommand;
41 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.DisableCleanFilterIndicatorCommand;
42 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.EnterPrivilegedModeCommand;
43 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetCleanFilterIndicatorCommand;
44 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetEyeBrightnessCommand;
45 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetFanspeedCommand;
46 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetIndoorOutoorTemperatures;
47 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetOperationHoursCommand;
48 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetOperationmodeCommand;
49 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetPowerstateCommand;
50 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetSetpointCommand;
51 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetVersionCommand;
52 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.ResetCleanFilterTimerCommand;
53 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.ResponseListener;
54 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetEyeBrightnessCommand;
55 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetFanspeedCommand;
56 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetOperationmodeCommand;
57 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetPowerstateCommand;
58 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetSetpointCommand;
59 import org.openhab.core.common.NamedThreadFactory;
60 import org.openhab.core.library.types.DecimalType;
61 import org.openhab.core.library.types.OnOffType;
62 import org.openhab.core.library.types.PercentType;
63 import org.openhab.core.library.types.QuantityType;
64 import org.openhab.core.library.types.StringType;
65 import org.openhab.core.thing.ChannelUID;
66 import org.openhab.core.thing.Thing;
67 import org.openhab.core.types.Command;
68 import org.openhab.core.types.RefreshType;
69 import org.openhab.core.types.State;
70 import org.openhab.core.types.UnDefType;
71 import org.openhab.core.util.HexUtils;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
76 * The {@link DaikinMadokaHandler} is responsible for handling commands, which are
77 * sent to one of the channels as well as updating channel values.
79 * @author Benjamin Lafois - Initial contribution
82 public class DaikinMadokaHandler extends ConnectedBluetoothHandler implements ResponseListener {
84 private final Logger logger = LoggerFactory.getLogger(DaikinMadokaHandler.class);
86 private DaikinMadokaConfiguration config = new DaikinMadokaConfiguration();
88 private @Nullable ExecutorService commandExecutor;
90 private @Nullable ScheduledFuture<?> refreshJob;
92 // UART Processor is in charge of reassembling chunks
93 private BRC1HUartProcessor uartProcessor = new BRC1HUartProcessor(this);
95 private volatile @Nullable BRC1HCommand currentCommand = null;
97 private MadokaSettings madokaSettings = new MadokaSettings();
99 public DaikinMadokaHandler(Thing thing) {
104 public void initialize() {
107 logger.debug("[{}] Start initializing!", super.thing.getUID().getId());
109 // Load Configuration
110 config = getConfigAs(DaikinMadokaConfiguration.class);
112 logger.debug("[{}] Parameter value [refreshInterval]: {}", super.thing.getUID().getId(),
113 config.refreshInterval);
114 logger.debug("[{}] Parameter value [commandTimeout]: {}", super.thing.getUID().getId(), config.commandTimeout);
116 if (getBridge() == null) {
117 logger.debug("[{}] Bridge is null. Exiting.", super.thing.getUID().getId());
121 this.commandExecutor = Executors
122 .newSingleThreadExecutor(new NamedThreadFactory(thing.getUID().getAsString(), true));
124 this.refreshJob = scheduler.scheduleWithFixedDelay(() -> {
125 // It is useless to refresh version all the time ! Just once.
126 if (this.madokaSettings.getCommunicationControllerVersion() == null
127 || this.madokaSettings.getRemoteControllerVersion() == null) {
128 submitCommand(new GetVersionCommand());
130 submitCommand(new GetIndoorOutoorTemperatures());
131 submitCommand(new GetOperationmodeCommand());
132 submitCommand(new GetPowerstateCommand()); // always keep the "GetPowerState" aftern the "GetOperationMode"
133 submitCommand(new GetSetpointCommand());
134 submitCommand(new GetFanspeedCommand());
135 submitCommand(new GetCleanFilterIndicatorCommand());
138 // As it is a complex operation - it has been extracted to a method.
139 retrieveOperationHours();
140 } catch (InterruptedException e) {
141 // The thread wants to exit!
145 submitCommand(new GetEyeBrightnessCommand());
146 }, new Random().nextInt(30), config.refreshInterval, TimeUnit.SECONDS); // We introduce a random start time, it
147 // avoids when having multiple devices to
148 // have the commands sent simultaneously.
151 private void retrieveOperationHours() throws InterruptedException {
152 // This one is special - and MUST be ran twice, after being in priv mode
153 // run it once an hour is sufficient... TODO
154 submitCommand(new EnterPrivilegedModeCommand());
155 submitCommand(new GetOperationHoursCommand());
156 // a 1second+ delay is necessary
159 submitCommand(new GetOperationHoursCommand());
163 public void dispose() {
164 logger.debug("[{}] dispose()", super.thing.getUID().getId());
167 dispose(commandExecutor);
168 dispose(currentCommand);
170 // Unsubscribe to characteristic notifications
171 if (this.device != null) {
172 BluetoothCharacteristic charNotif = this.device
173 .getCharacteristic(DaikinMadokaBindingConstants.CHAR_NOTIF_UUID);
175 if (charNotif != null) {
176 BluetoothCharacteristic c = charNotif;
177 this.device.disableNotifications(c);
184 private static void dispose(@Nullable ExecutorService executor) {
185 if (executor != null) {
186 executor.shutdownNow();
190 private static void dispose(@Nullable ScheduledFuture<?> future) {
191 if (future != null) {
196 private static void dispose(@Nullable BRC1HCommand command) {
197 if (command != null) {
198 // even if it already completed it doesn't really matter.
199 // on the off chance that the commandExecutor is waiting on the command, we can wake it up and cause it to
201 command.setState(BRC1HCommand.State.FAILED);
206 public void handleCommand(ChannelUID channelUID, Command command) {
207 logger.debug("[{}] Channel: {}, Command: {}", super.thing.getUID().getId(), channelUID, command);
209 if (command instanceof RefreshType) {
210 // The refresh commands are not supported in query mode.
211 // The binding will notify updates on channels
215 switch (channelUID.getId()) {
216 case DaikinMadokaBindingConstants.CHANNEL_ID_CLEAN_FILTER_INDICATOR:
217 OnOffType cleanFilterOrder = (OnOffType) command;
218 if (cleanFilterOrder == OnOffType.OFF) {
219 resetCleanFilterIndicator();
222 case DaikinMadokaBindingConstants.CHANNEL_ID_SETPOINT:
224 QuantityType<Temperature> setpoint = (QuantityType<Temperature>) command;
225 submitCommand(new SetSetpointCommand(setpoint, setpoint));
226 } catch (Exception e) {
227 logger.warn("Data received is not a valid temperature.", e);
230 case DaikinMadokaBindingConstants.CHANNEL_ID_EYE_BRIGHTNESS:
232 logger.debug("Set eye brightness with value {}, {}", command.getClass().getName(), command);
233 PercentType p = (PercentType) command;
234 submitCommand(new SetEyeBrightnessCommand(p));
235 } catch (Exception e) {
236 logger.warn("Data received is not a valid Eye Brightness status", e);
239 case DaikinMadokaBindingConstants.CHANNEL_ID_ONOFF_STATUS:
241 OnOffType oot = (OnOffType) command;
242 submitCommand(new SetPowerstateCommand(oot));
243 } catch (Exception e) {
244 logger.warn("Data received is not a valid on/off status", e);
247 case DaikinMadokaBindingConstants.CHANNEL_ID_FAN_SPEED:
249 DecimalType fanSpeed = (DecimalType) command;
250 FanSpeed fs = FanSpeed.valueOf(fanSpeed.intValue());
251 submitCommand(new SetFanspeedCommand(fs, fs));
252 } catch (Exception e) {
253 logger.warn("Data received is not a valid FanSpeed status", e);
256 case DaikinMadokaBindingConstants.CHANNEL_ID_OPERATION_MODE:
258 StringType operationMode = (StringType) command;
259 OperationMode m = OperationMode.valueOf(operationMode.toFullString());
261 submitCommand(new SetOperationmodeCommand(m));
262 } catch (Exception e) {
263 logger.warn("Data received is not a valid OPERATION MODE", e);
266 case DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE:
268 // Homebridge are discrete value different from Daikin
273 DecimalType homebridgeMode = (DecimalType) command;
274 switch (homebridgeMode.intValue()) {
276 submitCommand(new SetPowerstateCommand(OnOffType.OFF));
279 submitCommand(new SetOperationmodeCommand(OperationMode.HEAT));
280 if (madokaSettings.getOnOffState() == OnOffType.OFF) {
281 submitCommand(new SetPowerstateCommand(OnOffType.ON));
285 submitCommand(new SetOperationmodeCommand(OperationMode.COOL));
286 if (madokaSettings.getOnOffState() == OnOffType.OFF) {
287 submitCommand(new SetPowerstateCommand(OnOffType.ON));
291 submitCommand(new SetOperationmodeCommand(OperationMode.AUTO));
292 if (madokaSettings.getOnOffState() == OnOffType.OFF) {
293 submitCommand(new SetPowerstateCommand(OnOffType.ON));
296 default: // Invalid Value - in case of new FW
297 logger.warn("Invalid value received for channel {}. Ignoring.", channelUID);
300 } catch (Exception e) {
301 logger.warn("Data received is not a valid HOMEBRIDGE OPERATION MODE", e);
304 case DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_TARGET_HEATING_COOLING_MODE:
306 StringType homekitOperationMode = (StringType) command;
308 switch (homekitOperationMode.toString()) {
310 submitCommand(new SetPowerstateCommand(OnOffType.OFF));
313 submitCommand(new SetOperationmodeCommand(OperationMode.COOL));
314 if (madokaSettings.getOnOffState() == OnOffType.OFF) {
315 submitCommand(new SetPowerstateCommand(OnOffType.ON));
319 submitCommand(new SetOperationmodeCommand(OperationMode.HEAT));
320 if (madokaSettings.getOnOffState() == OnOffType.OFF) {
321 submitCommand(new SetPowerstateCommand(OnOffType.ON));
325 submitCommand(new SetOperationmodeCommand(OperationMode.AUTO));
326 if (madokaSettings.getOnOffState() == OnOffType.OFF) {
327 submitCommand(new SetPowerstateCommand(OnOffType.ON));
333 } catch (Exception e) {
334 logger.info("Error while setting mode through HomeKIt received Mode");
342 * 2 actions need to be done: disable the notification AND reset the filter timer
344 private void resetCleanFilterIndicator() {
345 logger.debug("[{}] resetCleanFilterIndicator()", super.thing.getUID().getId());
346 submitCommand(new DisableCleanFilterIndicatorCommand());
347 submitCommand(new ResetCleanFilterTimerCommand());
351 public void onCharacteristicUpdate(BluetoothCharacteristic characteristic, byte[] msgBytes) {
352 if (logger.isDebugEnabled()) {
353 logger.debug("[{}] onCharacteristicUpdate({})", super.thing.getUID().getId(),
354 HexUtils.bytesToHex(msgBytes));
356 super.onCharacteristicUpdate(characteristic, msgBytes);
358 // Check that arguments are valid.
359 if (characteristic.getUuid() == null) {
363 // We are only interested in the Notify Characteristic of UART service
364 if (!characteristic.getUuid().equals(DaikinMadokaBindingConstants.CHAR_NOTIF_UUID)) {
368 // A message cannot have a 0-byte length
369 if (msgBytes.length == 0) {
373 this.uartProcessor.chunkReceived(msgBytes);
376 private void submitCommand(BRC1HCommand command) {
377 Executor executor = commandExecutor;
379 if (executor != null) {
380 executor.execute(() -> processCommand(command));
384 private void processCommand(BRC1HCommand command) {
385 logger.debug("[{}] ProcessCommand {}", super.thing.getUID().getId(), command.getClass().getSimpleName());
388 currentCommand = command;
389 uartProcessor.abandon();
391 if (device == null || device.getConnectionState() != ConnectionState.CONNECTED) {
392 logger.debug("Unable to send command {} to device {}: not connected",
393 command.getClass().getSimpleName(), address);
394 command.setState(BRC1HCommand.State.FAILED);
398 if (!device.isServicesDiscovered()) {
399 logger.debug("Unable to send command {} to device {}: services not resolved",
400 command.getClass().getSimpleName(), device.getAddress());
401 command.setState(BRC1HCommand.State.FAILED);
405 BluetoothCharacteristic charWrite = device
406 .getCharacteristic(DaikinMadokaBindingConstants.CHAR_WRITE_WITHOUT_RESPONSE_UUID);
407 if (charWrite == null) {
408 logger.warn("Unable to execute {}. Characteristic '{}' could not be found.",
409 command.getClass().getSimpleName(),
410 DaikinMadokaBindingConstants.CHAR_WRITE_WITHOUT_RESPONSE_UUID);
411 command.setState(BRC1HCommand.State.FAILED);
415 BluetoothCharacteristic charNotif = this.device
416 .getCharacteristic(DaikinMadokaBindingConstants.CHAR_NOTIF_UUID);
418 if (charNotif != null) {
419 device.enableNotifications(charNotif);
422 // Commands can be composed of multiple chunks
423 for (byte[] chunk : command.getRequest()) {
424 command.setState(BRC1HCommand.State.ENQUEUED);
425 for (int i = 0; i < DaikinMadokaBindingConstants.WRITE_CHARACTERISTIC_MAX_RETRIES; i++) {
427 device.writeCharacteristic(charWrite, chunk).get(100, TimeUnit.MILLISECONDS);
428 } catch (InterruptedException ex) {
430 } catch (ExecutionException ex) {
431 logger.debug("Error while writing message {}: {}", command.getClass().getSimpleName(),
435 } catch (TimeoutException ex) {
439 command.setState(BRC1HCommand.State.SENT);
444 if (command.getState() == BRC1HCommand.State.SENT) {
445 if (!command.awaitStateChange(config.commandTimeout, TimeUnit.MILLISECONDS,
446 BRC1HCommand.State.SUCCEEDED, BRC1HCommand.State.FAILED)) {
447 logger.debug("[{}] Command {} to device {} timed out", super.thing.getUID().getId(), command,
448 device.getAddress());
449 command.setState(BRC1HCommand.State.FAILED);
452 } catch (Exception e) {
453 currentCommand = null;
454 // Let the exception bubble the stack!
455 throw new RuntimeException(e);
460 } catch (InterruptedException e) {
461 Thread.currentThread().interrupt();
466 * When the method is triggered, it means that all message chunks have been received, re-assembled in the right
467 * order and that the payload is ready to be processed.
470 public void receivedResponse(byte[] response) {
471 logger.debug("Received Response");
472 BRC1HCommand command = currentCommand;
474 if (command == null) {
475 if (logger.isDebugEnabled()) {
476 logger.debug("No command present to handle response {}", HexUtils.bytesToHex(response));
480 command.handleResponse(scheduler, this, MadokaMessage.parse(response));
481 } catch (MadokaParsingException e) {
482 logger.debug("Response message could not be parsed correctly ({}): {}. Reason: {}",
483 command.getClass().getSimpleName(), HexUtils.bytesToHex(response), e.getMessage());
489 public void receivedResponse(GetVersionCommand command) {
490 String commCtrlVers = command.getCommunicationControllerVersion();
491 if (commCtrlVers != null) {
492 this.madokaSettings.setCommunicationControllerVersion(commCtrlVers);
493 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_COMMUNICATION_CONTROLLER_VERSION,
494 new StringType(commCtrlVers));
497 String remoteCtrlVers = command.getRemoteControllerVersion();
498 if (remoteCtrlVers != null) {
499 this.madokaSettings.setRemoteControllerVersion(remoteCtrlVers);
500 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_REMOTE_CONTROLLER_VERSION,
501 new StringType(remoteCtrlVers));
506 public void receivedResponse(GetFanspeedCommand command) {
507 if (command.getCoolingFanSpeed() == null || command.getHeatingFanSpeed() == null) {
511 // We need the current operation mode to determine which Fan Speed we use (cooling or heating)
512 OperationMode operationMode = this.madokaSettings.getOperationMode();
513 if (operationMode == null) {
519 switch (operationMode) {
521 logger.debug("In AutoMode, CoolingFanSpeed = {}, HeatingFanSpeed = {}", command.getCoolingFanSpeed(),
522 command.getHeatingFanSpeed());
523 fs = command.getHeatingFanSpeed();
526 fs = command.getHeatingFanSpeed();
529 fs = command.getCoolingFanSpeed();
539 // No need to re-set if it is the same value
540 if (fs.equals(this.madokaSettings.getFanspeed())) {
544 this.madokaSettings.setFanspeed(fs);
545 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_FAN_SPEED, new DecimalType(fs.value()));
549 public void receivedResponse(GetSetpointCommand command) {
550 if (command.getCoolingSetpoint() == null || command.getHeatingSetpoint() == null) {
554 // We need the current operation mode to determine which Fan Speed we use (cooling or heating)
555 OperationMode operationMode = this.madokaSettings.getOperationMode();
556 if (operationMode == null) {
560 QuantityType<Temperature> sp;
562 switch (operationMode) {
564 logger.debug("In AutoMode, CoolingSetpoint = {}, HeatingSetpoint = {}", command.getCoolingSetpoint(),
565 command.getHeatingSetpoint());
566 sp = command.getHeatingSetpoint();
569 sp = command.getHeatingSetpoint();
572 sp = command.getCoolingSetpoint();
582 // No need to re-set if it is the same value
583 if (sp.equals(this.madokaSettings.getSetpoint())) {
587 this.madokaSettings.setSetpoint(sp);
589 QuantityType<Temperature> dt = this.madokaSettings.getSetpoint();
591 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_SETPOINT, dt);
596 public void receivedResponse(GetOperationmodeCommand command) {
597 if (command.getOperationMode() == null) {
598 logger.debug("OperationMode is null.");
602 OperationMode newMode = command.getOperationMode();
604 if (newMode == null) {
608 this.madokaSettings.setOperationMode(newMode);
610 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_OPERATION_MODE, new StringType(newMode.name()));
612 // For HomeKit channel, we need to map it to HomeKit supported strings
613 OnOffType ooStatus = madokaSettings.getOnOffState();
615 if (ooStatus != null && ooStatus == OnOffType.ON) {
618 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
619 new StringType("Cooling"));
620 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(2));
623 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
624 new StringType("Heating"));
625 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(1));
628 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
629 new StringType("Auto"));
630 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(3));
636 // If this is the first channel update - then we set target = current mode
637 if (this.madokaSettings.getHomekitTargetMode() == null) {
638 String newHomekitTargetStatus = null;
640 // For HomeKit channel, we need to map it to HomeKit supported strings
643 newHomekitTargetStatus = "CoolOn";
646 newHomekitTargetStatus = "HeatOn";
652 if (ooStatus != null && ooStatus == OnOffType.ON) {
653 this.madokaSettings.setHomekitTargetMode(newHomekitTargetStatus);
654 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_TARGET_HEATING_COOLING_MODE,
655 new StringType(newHomekitTargetStatus));
656 } else if (ooStatus != null && ooStatus == OnOffType.OFF) {
657 newHomekitTargetStatus = "Off";
658 this.madokaSettings.setHomekitTargetMode(newHomekitTargetStatus);
659 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_TARGET_HEATING_COOLING_MODE,
660 new StringType(newHomekitTargetStatus));
667 public void receivedResponse(GetPowerstateCommand command) {
668 if (command.isPowerState() == null) {
672 OnOffType oot = command.isPowerState() ? OnOffType.ON : OnOffType.OFF;
674 this.madokaSettings.setOnOffState(oot);
676 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_ONOFF_STATUS, oot);
678 if (oot.equals(OnOffType.OFF)) {
679 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
680 new StringType("Off"));
681 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_TARGET_HEATING_COOLING_MODE,
682 new StringType("Off"));
683 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(0));
688 public void receivedResponse(GetIndoorOutoorTemperatures command) {
689 QuantityType<Temperature> newIndoorTemp = command.getIndoorTemperature();
690 if (newIndoorTemp != null) {
691 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_TEMPERATURE, newIndoorTemp);
692 this.madokaSettings.setIndoorTemperature(newIndoorTemp);
695 QuantityType<Temperature> newOutdoorTemp = command.getOutdoorTemperature();
696 if (newOutdoorTemp == null) {
697 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_OUTDOOR_TEMPERATURE, UnDefType.UNDEF);
699 this.madokaSettings.setOutdoorTemperature(newOutdoorTemp);
700 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_OUTDOOR_TEMPERATURE, newOutdoorTemp);
705 public void receivedResponse(GetEyeBrightnessCommand command) {
706 PercentType eyeBrightnessTemp = command.getEyeBrightness();
707 if (eyeBrightnessTemp != null) {
708 this.madokaSettings.setEyeBrightness(eyeBrightnessTemp);
709 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_EYE_BRIGHTNESS, eyeBrightnessTemp);
710 logger.debug("Notified {} channel with value {}", DaikinMadokaBindingConstants.CHANNEL_ID_EYE_BRIGHTNESS,
716 public void receivedResponse(SetEyeBrightnessCommand command) {
717 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_EYE_BRIGHTNESS, command.getEyeBrightness());
718 madokaSettings.setEyeBrightness(command.getEyeBrightness());
722 public void receivedResponse(SetPowerstateCommand command) {
723 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_ONOFF_STATUS, command.getPowerState());
725 madokaSettings.setOnOffState(command.getPowerState());
727 if (command.getPowerState() == OnOffType.ON) {
728 // Depending on the state
730 OperationMode operationMode = madokaSettings.getOperationMode();
731 if (operationMode == null) {
735 switch (operationMode) {
737 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
738 new StringType("Auto"));
739 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(3));
742 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
743 new StringType("Heating"));
744 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(1));
747 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
748 new StringType("Cooling"));
749 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(2));
751 default: // Other Modes are not [yet] supported
755 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
756 new StringType("Off"));
757 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(0));
762 public void receivedResponse(GetOperationHoursCommand command) {
763 logger.debug("receivedResponse(GetOperationHoursCommand command)");
765 QuantityType<Time> indoorPowerHours = command.getIndoorPowerHours();
766 QuantityType<Time> indoorOperationHours = command.getIndoorOperationHours();
767 QuantityType<Time> indoorFanHours = command.getIndoorFanHours();
769 if (indoorPowerHours != null) {
770 this.madokaSettings.setIndoorPowerHours(indoorPowerHours);
771 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_POWER_HOURS, indoorPowerHours);
772 logger.debug("Notified {} channel with value {}",
773 DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_POWER_HOURS, indoorPowerHours);
776 if (indoorOperationHours != null) {
777 this.madokaSettings.setIndoorOperationHours(indoorOperationHours);
778 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_OPERATION_HOURS, indoorOperationHours);
779 logger.debug("Notified {} channel with value {}",
780 DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_OPERATION_HOURS, indoorOperationHours);
783 if (indoorFanHours != null) {
784 this.madokaSettings.setIndoorFanHours(indoorFanHours);
785 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_FAN_HOURS, indoorFanHours);
786 logger.debug("Notified {} channel with value {}", DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_FAN_HOURS,
792 public void receivedResponse(SetSetpointCommand command) {
793 // The update depends on the mode - so if not set - skip
794 OperationMode operationMode = this.madokaSettings.getOperationMode();
795 if (operationMode == null) {
799 switch (operationMode) {
801 this.madokaSettings.setSetpoint(command.getHeatingSetpoint());
804 this.madokaSettings.setSetpoint(command.getCoolingSetpoint());
807 // Here we don't really care if we are taking cooling or heating...
808 this.madokaSettings.setSetpoint(command.getCoolingSetpoint());
814 QuantityType<Temperature> dt = madokaSettings.getSetpoint();
816 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_SETPOINT, dt);
821 public void receivedResponse(GetCleanFilterIndicatorCommand command) {
822 Boolean indicatorStatus = command.getCleanFilterIndicator();
823 if (indicatorStatus != null) {
824 this.madokaSettings.setCleanFilterIndicator(indicatorStatus);
825 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_CLEAN_FILTER_INDICATOR,
826 OnOffType.from(indicatorStatus));
831 * Received response to "SetOperationmodeCommand" command
834 public void receivedResponse(SetOperationmodeCommand command) {
835 this.madokaSettings.setOperationMode(command.getOperationMode());
836 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_OPERATION_MODE,
837 new StringType(command.getOperationMode().toString()));
841 * Received response to "SetFanSpeed" command
844 public void receivedResponse(SetFanspeedCommand command) {
845 // The update depends on the mode - so if not set - skip
846 OperationMode operationMode = this.madokaSettings.getOperationMode();
847 if (operationMode == null) {
852 switch (operationMode) {
854 fanSpeed = command.getHeatingFanSpeed();
855 this.madokaSettings.setFanspeed(fanSpeed);
858 fanSpeed = command.getCoolingFanSpeed();
859 this.madokaSettings.setFanspeed(fanSpeed);
862 fanSpeed = command.getCoolingFanSpeed(); // Arbitrary cooling or heating... They are the same!
863 this.madokaSettings.setFanspeed(fanSpeed);
869 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_FAN_SPEED, new DecimalType(fanSpeed.value()));
872 private void updateStateIfLinked(String channelId, State state) {
873 ChannelUID channelUID = new ChannelUID(getThing().getUID(), channelId);
874 if (isLinked(channelUID)) {
875 updateState(channelUID, state);