]> git.basschouten.com Git - openhab-addons.git/blob
506f117db6e6aac08b3154330e58a26a3234127e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.bluetooth.daikinmadoka.handler;
14
15 import java.util.Arrays;
16 import java.util.concurrent.Executor;
17 import java.util.concurrent.ExecutorService;
18 import java.util.concurrent.Executors;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.bluetooth.BluetoothCharacteristic;
26 import org.openhab.binding.bluetooth.BluetoothCompletionStatus;
27 import org.openhab.binding.bluetooth.BluetoothDevice.ConnectionState;
28 import org.openhab.binding.bluetooth.ConnectedBluetoothHandler;
29 import org.openhab.binding.bluetooth.daikinmadoka.DaikinMadokaBindingConstants;
30 import org.openhab.binding.bluetooth.daikinmadoka.internal.BRC1HUartProcessor;
31 import org.openhab.binding.bluetooth.daikinmadoka.internal.DaikinMadokaConfiguration;
32 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaMessage;
33 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaParsingException;
34 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaProperties.FanSpeed;
35 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaProperties.OperationMode;
36 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.MadokaSettings;
37 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.BRC1HCommand;
38 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetFanspeedCommand;
39 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetIndoorOutoorTemperatures;
40 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetOperationmodeCommand;
41 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetPowerstateCommand;
42 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetSetpointCommand;
43 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.GetVersionCommand;
44 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.ResponseListener;
45 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetFanspeedCommand;
46 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetOperationmodeCommand;
47 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetPowerstateCommand;
48 import org.openhab.binding.bluetooth.daikinmadoka.internal.model.commands.SetSetpointCommand;
49 import org.openhab.core.common.NamedThreadFactory;
50 import org.openhab.core.library.types.DecimalType;
51 import org.openhab.core.library.types.OnOffType;
52 import org.openhab.core.library.types.QuantityType;
53 import org.openhab.core.library.types.StringType;
54 import org.openhab.core.thing.ChannelUID;
55 import org.openhab.core.thing.Thing;
56 import org.openhab.core.types.Command;
57 import org.openhab.core.types.RefreshType;
58 import org.openhab.core.types.State;
59 import org.openhab.core.types.UnDefType;
60 import org.openhab.core.util.HexUtils;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63
64 /**
65  * The {@link DaikinMadokaHandler} is responsible for handling commands, which are
66  * sent to one of the channels as well as updating channel values.
67  *
68  * @author Benjamin Lafois - Initial contribution
69  */
70 @NonNullByDefault
71 public class DaikinMadokaHandler extends ConnectedBluetoothHandler implements ResponseListener {
72
73     private final Logger logger = LoggerFactory.getLogger(DaikinMadokaHandler.class);
74
75     private @Nullable DaikinMadokaConfiguration config;
76
77     private @Nullable ExecutorService commandExecutor;
78
79     private @Nullable ScheduledFuture<?> refreshJob;
80
81     // UART Processor is in charge of reassembling chunks
82     private BRC1HUartProcessor uartProcessor = new BRC1HUartProcessor(this);
83
84     private volatile @Nullable BRC1HCommand currentCommand = null;
85
86     private MadokaSettings madokaSettings = new MadokaSettings();
87
88     public DaikinMadokaHandler(Thing thing) {
89         super(thing);
90     }
91
92     @Override
93     public void initialize() {
94         super.initialize();
95
96         logger.debug("[{}] Start initializing!", super.thing.getUID().getId());
97
98         // Load Configuration
99         config = getConfigAs(DaikinMadokaConfiguration.class);
100         DaikinMadokaConfiguration c = config;
101
102         logger.debug("[{}] Parameter value [refreshInterval]: {}", super.thing.getUID().getId(), c.refreshInterval);
103         logger.debug("[{}] Parameter value [commandTimeout]: {}", super.thing.getUID().getId(), c.commandTimeout);
104
105         if (getBridge() == null) {
106             logger.debug("[{}] Bridge is null. Exiting.", super.thing.getUID().getId());
107             return;
108         }
109
110         this.commandExecutor = Executors
111                 .newSingleThreadExecutor(new NamedThreadFactory(thing.getUID().getAsString(), true));
112
113         this.refreshJob = scheduler.scheduleWithFixedDelay(() -> {
114             // It is useless to refresh version all the time ! Just once.
115             if (this.madokaSettings.getCommunicationControllerVersion() == null
116                     || this.madokaSettings.getRemoteControllerVersion() == null) {
117                 submitCommand(new GetVersionCommand());
118             }
119             submitCommand(new GetIndoorOutoorTemperatures());
120             submitCommand(new GetOperationmodeCommand());
121             submitCommand(new GetPowerstateCommand()); // always keep the "GetPowerState" aftern the "GetOperationMode"
122             submitCommand(new GetSetpointCommand());
123             submitCommand(new GetFanspeedCommand());
124         }, 10, c.refreshInterval, TimeUnit.SECONDS);
125     }
126
127     @Override
128     public void dispose() {
129         logger.debug("[{}] dispose()", super.thing.getUID().getId());
130
131         dispose(refreshJob);
132         dispose(commandExecutor);
133         dispose(currentCommand);
134
135         // Unsubscribe to characteristic notifications
136         if (this.device != null) {
137             BluetoothCharacteristic charNotif = this.device
138                     .getCharacteristic(DaikinMadokaBindingConstants.CHAR_NOTIF_UUID);
139
140             if (charNotif != null) {
141                 @NonNull
142                 BluetoothCharacteristic c = charNotif;
143                 this.device.disableNotifications(c);
144             }
145         }
146
147         super.dispose();
148     }
149
150     private static void dispose(@Nullable ExecutorService executor) {
151         if (executor != null) {
152             executor.shutdownNow();
153         }
154     }
155
156     private static void dispose(@Nullable ScheduledFuture<?> future) {
157         if (future != null) {
158             future.cancel(true);
159         }
160     }
161
162     private static void dispose(@Nullable BRC1HCommand command) {
163         if (command != null) {
164             // even if it already completed it doesn't really matter.
165             // on the off chance that the commandExecutor is waiting on the command, we can wake it up and cause it to
166             // terminate
167             command.setState(BRC1HCommand.State.FAILED);
168         }
169     }
170
171     @Override
172     public void handleCommand(ChannelUID channelUID, Command command) {
173         logger.debug("[{}] Channel: {}, Command: {}", super.thing.getUID().getId(), channelUID, command);
174
175         if (command instanceof RefreshType) {
176             // The refresh commands are not supported in query mode.
177             // The binding will notify updates on channels
178             return;
179         }
180
181         switch (channelUID.getId()) {
182             case DaikinMadokaBindingConstants.CHANNEL_ID_SETPOINT:
183                 try {
184                     QuantityType<?> setpoint = (QuantityType<?>) command;
185                     DecimalType dt = new DecimalType(setpoint.intValue());
186                     submitCommand(new SetSetpointCommand(dt, dt));
187                 } catch (Exception e) {
188                     logger.warn("Data received is not a valid temperature.", e);
189                 }
190                 break;
191             case DaikinMadokaBindingConstants.CHANNEL_ID_ONOFF_STATUS:
192                 try {
193                     OnOffType oot = (OnOffType) command;
194                     submitCommand(new SetPowerstateCommand(oot));
195                 } catch (Exception e) {
196                     logger.warn("Data received is not a valid on/off status", e);
197                 }
198                 break;
199             case DaikinMadokaBindingConstants.CHANNEL_ID_FAN_SPEED:
200                 try {
201                     DecimalType fanSpeed = (DecimalType) command;
202                     FanSpeed fs = FanSpeed.valueOf(fanSpeed.intValue());
203                     submitCommand(new SetFanspeedCommand(fs, fs));
204                 } catch (Exception e) {
205                     logger.warn("Data received is not a valid FanSpeed status", e);
206                 }
207                 break;
208             case DaikinMadokaBindingConstants.CHANNEL_ID_OPERATION_MODE:
209                 try {
210                     StringType operationMode = (StringType) command;
211                     OperationMode m = OperationMode.valueOf(operationMode.toFullString());
212
213                     submitCommand(new SetOperationmodeCommand(m));
214                 } catch (Exception e) {
215                     logger.warn("Data received is not a valid OPERATION MODE", e);
216                 }
217                 break;
218             case DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE:
219                 try {
220                     // Homebridge are discrete value different from Daikin
221                     // 0 - Off
222                     // 1 - Heating
223                     // 2 - Cooling
224                     // 3 - Auto
225                     DecimalType homebridgeMode = (DecimalType) command;
226                     switch (homebridgeMode.intValue()) {
227                         case 0: // Off
228                             submitCommand(new SetPowerstateCommand(OnOffType.OFF));
229                             break;
230                         case 1: // Heating
231                             submitCommand(new SetOperationmodeCommand(OperationMode.HEAT));
232                             if (madokaSettings.getOnOffState() == OnOffType.OFF) {
233                                 submitCommand(new SetPowerstateCommand(OnOffType.ON));
234                             }
235                             break;
236                         case 2: // Cooling
237                             submitCommand(new SetOperationmodeCommand(OperationMode.COOL));
238                             if (madokaSettings.getOnOffState() == OnOffType.OFF) {
239                                 submitCommand(new SetPowerstateCommand(OnOffType.ON));
240                             }
241                             break;
242                         case 3: // Auto
243                             submitCommand(new SetOperationmodeCommand(OperationMode.AUTO));
244                             if (madokaSettings.getOnOffState() == OnOffType.OFF) {
245                                 submitCommand(new SetPowerstateCommand(OnOffType.ON));
246                             }
247                             break;
248                         default: // Invalid Value - in case of new FW
249                             logger.warn("Invalid value received for channel {}. Ignoring.", channelUID);
250                             break;
251                     }
252                 } catch (Exception e) {
253                     logger.warn("Data received is not a valid HOMEBRIDGE OPERATION MODE", e);
254                 }
255                 break;
256             case DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_TARGET_HEATING_COOLING_MODE:
257                 try {
258                     StringType homekitOperationMode = (StringType) command;
259
260                     switch (homekitOperationMode.toString()) {
261                         case "Off":
262                             submitCommand(new SetPowerstateCommand(OnOffType.OFF));
263                             break;
264                         case "CoolOn":
265                             submitCommand(new SetOperationmodeCommand(OperationMode.COOL));
266                             if (madokaSettings.getOnOffState() == OnOffType.OFF) {
267                                 submitCommand(new SetPowerstateCommand(OnOffType.ON));
268                             }
269                             break;
270                         case "HeatOn":
271                             submitCommand(new SetOperationmodeCommand(OperationMode.HEAT));
272                             if (madokaSettings.getOnOffState() == OnOffType.OFF) {
273                                 submitCommand(new SetPowerstateCommand(OnOffType.ON));
274                             }
275                             break;
276                         case "Auto":
277                             submitCommand(new SetOperationmodeCommand(OperationMode.AUTO));
278                             if (madokaSettings.getOnOffState() == OnOffType.OFF) {
279                                 submitCommand(new SetPowerstateCommand(OnOffType.ON));
280                             }
281                             break;
282                         default:
283                             break;
284                     }
285                 } catch (Exception e) {
286                     logger.info("Error while setting mode through HomeKIt received Mode");
287                 }
288             default:
289                 break;
290         }
291     }
292
293     @Override
294     public void onCharacteristicUpdate(BluetoothCharacteristic characteristic) {
295         super.onCharacteristicUpdate(characteristic);
296
297         // Check that arguments are valid.
298         if (characteristic.getUuid() == null) {
299             return;
300         }
301
302         // We are only interested in the Notify Characteristic of UART service
303         if (!characteristic.getUuid().equals(DaikinMadokaBindingConstants.CHAR_NOTIF_UUID)) {
304             return;
305         }
306
307         // A message cannot be null or have a 0-byte length
308         byte[] msgBytes = characteristic.getByteValue();
309         if (msgBytes == null || msgBytes.length == 0) {
310             return;
311         }
312
313         this.uartProcessor.chunkReceived(msgBytes);
314     }
315
316     private void submitCommand(BRC1HCommand command) {
317         Executor executor = commandExecutor;
318
319         if (executor != null) {
320             executor.execute(() -> processCommand(command));
321         }
322     }
323
324     private void processCommand(BRC1HCommand command) {
325         logger.debug("[{}] ProcessCommand {}", super.thing.getUID().getId(), command.getClass().getSimpleName());
326
327         try {
328             currentCommand = command;
329             uartProcessor.abandon();
330
331             if (device == null || device.getConnectionState() != ConnectionState.CONNECTED) {
332                 logger.debug("Unable to send command {} to device {}: not connected",
333                         command.getClass().getSimpleName(), address);
334                 command.setState(BRC1HCommand.State.FAILED);
335                 return;
336             }
337
338             if (!resolved) {
339                 logger.debug("Unable to send command {} to device {}: services not resolved",
340                         command.getClass().getSimpleName(), device.getAddress());
341                 command.setState(BRC1HCommand.State.FAILED);
342                 return;
343             }
344
345             BluetoothCharacteristic charWrite = device
346                     .getCharacteristic(DaikinMadokaBindingConstants.CHAR_WRITE_WITHOUT_RESPONSE_UUID);
347             if (charWrite == null) {
348                 logger.warn("Unable to execute {}. Characteristic '{}' could not be found.",
349                         command.getClass().getSimpleName(),
350                         DaikinMadokaBindingConstants.CHAR_WRITE_WITHOUT_RESPONSE_UUID);
351                 command.setState(BRC1HCommand.State.FAILED);
352                 return;
353             }
354
355             BluetoothCharacteristic charNotif = this.device
356                     .getCharacteristic(DaikinMadokaBindingConstants.CHAR_NOTIF_UUID);
357
358             if (charNotif != null) {
359                 device.enableNotifications(charNotif);
360             }
361
362             charWrite.setValue(command.getRequest());
363             command.setState(BRC1HCommand.State.ENQUEUED);
364             device.writeCharacteristic(charWrite);
365
366             if (this.config != null) {
367                 if (!command.awaitStateChange(this.config.commandTimeout, TimeUnit.MILLISECONDS,
368                         BRC1HCommand.State.SUCCEEDED, BRC1HCommand.State.FAILED)) {
369                     logger.debug("Command {} to device {} timed out", command, device.getAddress());
370                     command.setState(BRC1HCommand.State.FAILED);
371                 }
372             }
373         } catch (Exception e) {
374             currentCommand = null;
375             // Let the exception bubble the stack!
376             throw new RuntimeException(e);
377         }
378
379         try {
380             Thread.sleep(200);
381         } catch (InterruptedException e) {
382             Thread.currentThread().interrupt();
383         }
384     }
385
386     @Override
387     public void onCharacteristicWriteComplete(BluetoothCharacteristic characteristic,
388             BluetoothCompletionStatus status) {
389         super.onCharacteristicWriteComplete(characteristic, status);
390
391         byte[] request = characteristic.getByteValue();
392         BRC1HCommand command = currentCommand;
393
394         if (command != null) {
395             if (!Arrays.equals(request, command.getRequest())) {
396                 logger.debug("Write completed for unknown command");
397                 return;
398             }
399             switch (status) {
400                 case SUCCESS:
401                     command.setState(BRC1HCommand.State.SENT);
402                     break;
403                 case ERROR:
404                     command.setState(BRC1HCommand.State.FAILED);
405                     break;
406             }
407         } else {
408             if (logger.isDebugEnabled()) {
409                 logger.debug("No command found that matches request {}", HexUtils.bytesToHex(request));
410             }
411         }
412     }
413
414     /**
415      * When the method is triggered, it means that all message chunks have been received, re-assembled in the right
416      * order and that the payload is ready to be processed.
417      */
418     @Override
419     public void receivedResponse(byte[] response) {
420         logger.debug("Received Response");
421         BRC1HCommand command = currentCommand;
422
423         if (command == null) {
424             if (logger.isDebugEnabled()) {
425                 logger.debug("No command present to handle response {}", HexUtils.bytesToHex(response));
426             }
427         } else {
428             try {
429                 command.handleResponse(scheduler, this, MadokaMessage.parse(response));
430             } catch (MadokaParsingException e) {
431                 logger.debug("Response message could not be parsed correctly ({}): {}. Reason: {}",
432                         command.getClass().getSimpleName(), HexUtils.bytesToHex(response), e.getMessage());
433             }
434         }
435     }
436
437     @Override
438     public void receivedResponse(GetVersionCommand command) {
439         String commCtrlVers = command.getCommunicationControllerVersion();
440         if (commCtrlVers != null) {
441             this.madokaSettings.setCommunicationControllerVersion(commCtrlVers);
442             updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_COMMUNICATION_CONTROLLER_VERSION,
443                     new StringType(commCtrlVers));
444         }
445
446         String remoteCtrlVers = command.getRemoteControllerVersion();
447         if (remoteCtrlVers != null) {
448             this.madokaSettings.setRemoteControllerVersion(remoteCtrlVers);
449             updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_REMOTE_CONTROLLER_VERSION,
450                     new StringType(remoteCtrlVers));
451         }
452     }
453
454     @Override
455     public void receivedResponse(GetFanspeedCommand command) {
456         if (command.getCoolingFanSpeed() == null || command.getHeatingFanSpeed() == null) {
457             return;
458         }
459
460         // We need the current operation mode to determine which Fan Speed we use (cooling or heating)
461         OperationMode operationMode = this.madokaSettings.getOperationMode();
462         if (operationMode == null) {
463             return;
464         }
465
466         FanSpeed fs;
467
468         switch (operationMode) {
469             case AUTO:
470                 logger.debug("In AutoMode, CoolingFanSpeed = {}, HeatingFanSpeed = {}", command.getCoolingFanSpeed(),
471                         command.getHeatingFanSpeed());
472                 fs = command.getHeatingFanSpeed();
473                 break;
474             case HEAT:
475                 fs = command.getHeatingFanSpeed();
476                 break;
477             case COOL:
478                 fs = command.getCoolingFanSpeed();
479                 break;
480             default:
481                 return;
482         }
483
484         if (fs == null) {
485             return;
486         }
487
488         // No need to re-set if it is the same value
489         if (fs.equals(this.madokaSettings.getFanspeed())) {
490             return;
491         }
492
493         this.madokaSettings.setFanspeed(fs);
494         updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_FAN_SPEED, new DecimalType(fs.value()));
495     }
496
497     @Override
498     public void receivedResponse(GetSetpointCommand command) {
499         if (command.getCoolingSetpoint() == null || command.getHeatingSetpoint() == null) {
500             return;
501         }
502
503         // We need the current operation mode to determine which Fan Speed we use (cooling or heating)
504         OperationMode operationMode = this.madokaSettings.getOperationMode();
505         if (operationMode == null) {
506             return;
507         }
508
509         DecimalType sp;
510
511         switch (operationMode) {
512             case AUTO:
513                 logger.debug("In AutoMode, CoolingSetpoint = {}, HeatingSetpoint = {}", command.getCoolingSetpoint(),
514                         command.getHeatingSetpoint());
515                 sp = command.getHeatingSetpoint();
516                 break;
517             case HEAT:
518                 sp = command.getHeatingSetpoint();
519                 break;
520             case COOL:
521                 sp = command.getCoolingSetpoint();
522                 break;
523             default:
524                 return;
525         }
526
527         if (sp == null) {
528             return;
529         }
530
531         // No need to re-set if it is the same value
532         if (sp.equals(this.madokaSettings.getSetpoint())) {
533             return;
534         }
535
536         this.madokaSettings.setSetpoint(sp);
537
538         DecimalType dt = this.madokaSettings.getSetpoint();
539         if (dt != null) {
540             updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_SETPOINT, dt);
541         }
542     }
543
544     @Override
545     public void receivedResponse(GetOperationmodeCommand command) {
546         if (command.getOperationMode() == null) {
547             logger.debug("OperationMode is null.");
548             return;
549         }
550
551         OperationMode newMode = command.getOperationMode();
552
553         if (newMode == null) {
554             return;
555         }
556
557         this.madokaSettings.setOperationMode(newMode);
558
559         updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_OPERATION_MODE, new StringType(newMode.name()));
560
561         // For HomeKit channel, we need to map it to HomeKit supported strings
562         OnOffType ooStatus = madokaSettings.getOnOffState();
563
564         if (ooStatus != null && ooStatus == OnOffType.ON) {
565             switch (newMode) {
566                 case COOL:
567                     updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
568                             new StringType("Cooling"));
569                     updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(2));
570                     break;
571                 case HEAT:
572                     updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
573                             new StringType("Heating"));
574                     updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(1));
575                     break;
576                 case AUTO:
577                     updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
578                             new StringType("Auto"));
579                     updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(3));
580                 default:
581                     break;
582             }
583         }
584
585         // If this is the first channel update - then we set target = current mode
586         if (this.madokaSettings.getHomekitTargetMode() == null) {
587             String newHomekitTargetStatus = null;
588
589             // For HomeKit channel, we need to map it to HomeKit supported strings
590             switch (newMode) {
591                 case COOL:
592                     newHomekitTargetStatus = "CoolOn";
593                     break;
594                 case HEAT:
595                     newHomekitTargetStatus = "HeatOn";
596                     break;
597                 default:
598                     return;
599             }
600
601             if (ooStatus != null && ooStatus == OnOffType.ON) {
602                 this.madokaSettings.setHomekitTargetMode(newHomekitTargetStatus);
603                 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_TARGET_HEATING_COOLING_MODE,
604                         new StringType(newHomekitTargetStatus));
605             } else if (ooStatus != null && ooStatus == OnOffType.OFF) {
606                 newHomekitTargetStatus = "Off";
607                 this.madokaSettings.setHomekitTargetMode(newHomekitTargetStatus);
608                 updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_TARGET_HEATING_COOLING_MODE,
609                         new StringType(newHomekitTargetStatus));
610             }
611
612         }
613     }
614
615     @Override
616     public void receivedResponse(GetPowerstateCommand command) {
617         if (command.isPowerState() == null) {
618             return;
619         }
620
621         OnOffType oot = command.isPowerState() ? OnOffType.ON : OnOffType.OFF;
622
623         this.madokaSettings.setOnOffState(oot);
624
625         updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_ONOFF_STATUS, oot);
626
627         if (oot.equals(OnOffType.OFF)) {
628             updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
629                     new StringType("Off"));
630             updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_TARGET_HEATING_COOLING_MODE,
631                     new StringType("Off"));
632             updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(0));
633         }
634     }
635
636     @Override
637     public void receivedResponse(GetIndoorOutoorTemperatures command) {
638         DecimalType newIndoorTemp = command.getIndoorTemperature();
639         if (newIndoorTemp != null) {
640             updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_INDOOR_TEMPERATURE, newIndoorTemp);
641             this.madokaSettings.setIndoorTemperature(newIndoorTemp);
642         }
643
644         DecimalType newOutdoorTemp = command.getOutdoorTemperature();
645         if (newOutdoorTemp == null) {
646             updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_OUTDOOR_TEMPERATURE, UnDefType.UNDEF);
647         } else {
648             this.madokaSettings.setOutdoorTemperature(newOutdoorTemp);
649             updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_OUTDOOR_TEMPERATURE, newOutdoorTemp);
650         }
651     }
652
653     @Override
654     public void receivedResponse(SetPowerstateCommand command) {
655         updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_ONOFF_STATUS, command.getPowerState());
656
657         madokaSettings.setOnOffState(command.getPowerState());
658
659         if (command.getPowerState() == OnOffType.ON) {
660             // Depending on the state
661
662             OperationMode operationMode = madokaSettings.getOperationMode();
663             if (operationMode == null) {
664                 return;
665             }
666
667             switch (operationMode) {
668                 case AUTO:
669                     updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
670                             new StringType("Auto"));
671                     updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(3));
672                     break;
673                 case HEAT:
674                     updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
675                             new StringType("Heating"));
676                     updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(1));
677                     break;
678                 case COOL:
679                     updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
680                             new StringType("Cooling"));
681                     updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(2));
682                     break;
683                 default: // Other Modes are not [yet] supported
684                     break;
685             }
686         } else {
687             updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEKIT_CURRENT_HEATING_COOLING_MODE,
688                     new StringType("Off"));
689             updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_HOMEBRIDGE_MODE, new DecimalType(0));
690         }
691     }
692
693     @Override
694     public void receivedResponse(SetSetpointCommand command) {
695         // The update depends on the mode - so if not set - skip
696         OperationMode operationMode = this.madokaSettings.getOperationMode();
697         if (operationMode == null) {
698             return;
699         }
700
701         switch (operationMode) {
702             case HEAT:
703                 this.madokaSettings.setSetpoint(command.getHeatingSetpoint());
704                 break;
705             case COOL:
706                 this.madokaSettings.setSetpoint(command.getCoolingSetpoint());
707                 break;
708             case AUTO:
709                 // Here we don't really care if we are taking cooling or heating...
710                 this.madokaSettings.setSetpoint(command.getCoolingSetpoint());
711                 break;
712             default:
713                 return;
714         }
715
716         DecimalType dt = madokaSettings.getSetpoint();
717         if (dt != null) {
718             updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_SETPOINT, dt);
719         }
720     }
721
722     /**
723      * Received response to "SetOperationmodeCommand" command
724      */
725     @Override
726     public void receivedResponse(SetOperationmodeCommand command) {
727         this.madokaSettings.setOperationMode(command.getOperationMode());
728         updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_OPERATION_MODE,
729                 new StringType(command.getOperationMode().toString()));
730     }
731
732     /**
733      * Received response to "SetFanSpeed" command
734      */
735     @Override
736     public void receivedResponse(SetFanspeedCommand command) {
737         // The update depends on the mode - so if not set - skip
738         OperationMode operationMode = this.madokaSettings.getOperationMode();
739         if (operationMode == null) {
740             return;
741         }
742
743         FanSpeed fanSpeed;
744         switch (operationMode) {
745             case HEAT:
746                 fanSpeed = command.getHeatingFanSpeed();
747                 this.madokaSettings.setFanspeed(fanSpeed);
748                 break;
749             case COOL:
750                 fanSpeed = command.getCoolingFanSpeed();
751                 this.madokaSettings.setFanspeed(fanSpeed);
752                 break;
753             case AUTO:
754                 fanSpeed = command.getCoolingFanSpeed(); // Arbitrary cooling or heating... They are the same!
755                 this.madokaSettings.setFanspeed(fanSpeed);
756                 break;
757             default:
758                 return;
759         }
760
761         updateStateIfLinked(DaikinMadokaBindingConstants.CHANNEL_ID_FAN_SPEED, new DecimalType(fanSpeed.value()));
762     }
763
764     private void updateStateIfLinked(String channelId, State state) {
765         ChannelUID channelUID = new ChannelUID(getThing().getUID(), channelId);
766         if (isLinked(channelUID)) {
767             updateState(channelUID, state);
768         }
769     }
770 }